第四板块:Android 输入系统与触控事件 | 第十五篇:InputReader 与 InputDispatcher 的触控流水线
所属板块:第四板块 --- Android 输入系统与触控事件
前置知识:第十三篇中的 SurfaceFlinger 合成机制、VSYNC 信号、第十四篇中的 RenderThread 渲染流程
本篇定位 :这是 Android 交互系统的神经末梢 。如果说图形渲染是视觉,那么输入系统就是触觉。本篇将彻底拆解 Linux 输入子系统(evdev) 、InputReader 的原始事件解析 、InputDispatcher 的投递策略 、ANR 的超时机制 、Touch 事件的预测与插值 。我们将深入 System Server 的输入模块 ,揭示从手指触摸屏幕 到应用收到 MotionEvent 的完整链路。全程无事件处理技巧、无手势分析,仅保留 Android 输入系统的底层定义与分发规范。
1. 核心结论先行(Thesis Statement)
Android 的输入系统是一个事件驱动的生产者-消费者模型。
- InputReader 的本质 :原始数据翻译官 。它运行在 System Server 的独立线程中,不断从
/dev/input/eventX节点读取二进制数据,将其翻译成 Android 定义的 InputEvent(如 KeyEvent, MotionEvent)。 - InputDispatcher 的本质 :交通警察 。它维护着一个 Inbound Queue (待处理队列)和一个 Outbound Queue(待发送队列),根据窗口的焦点状态和 Z-order,决定将事件发给哪个应用进程。
- ANR 的本质 :超时惩罚。如果应用进程在 5 秒内没有处理完 Input 事件,InputDispatcher 会向 AMS 报告,触发 ANR 弹窗。
- 事件同步 :输入事件必须与 VSYNC 信号同步,否则会出现点击与画面不同步的现象。
2. 输入系统的全链路架构
2.1 从硬件中断到应用回调
#mermaid-svg-j2C8CUX5zJ0zCZyO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j2C8CUX5zJ0zCZyO .error-icon{fill:#552222;}#mermaid-svg-j2C8CUX5zJ0zCZyO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j2C8CUX5zJ0zCZyO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .marker.cross{stroke:#333333;}#mermaid-svg-j2C8CUX5zJ0zCZyO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j2C8CUX5zJ0zCZyO p{margin:0;}#mermaid-svg-j2C8CUX5zJ0zCZyO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .cluster-label text{fill:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .cluster-label span{color:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .cluster-label span p{background-color:transparent;}#mermaid-svg-j2C8CUX5zJ0zCZyO .label text,#mermaid-svg-j2C8CUX5zJ0zCZyO span{fill:#333;color:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .node rect,#mermaid-svg-j2C8CUX5zJ0zCZyO .node circle,#mermaid-svg-j2C8CUX5zJ0zCZyO .node ellipse,#mermaid-svg-j2C8CUX5zJ0zCZyO .node polygon,#mermaid-svg-j2C8CUX5zJ0zCZyO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .rough-node .label text,#mermaid-svg-j2C8CUX5zJ0zCZyO .node .label text,#mermaid-svg-j2C8CUX5zJ0zCZyO .image-shape .label,#mermaid-svg-j2C8CUX5zJ0zCZyO .icon-shape .label{text-anchor:middle;}#mermaid-svg-j2C8CUX5zJ0zCZyO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .rough-node .label,#mermaid-svg-j2C8CUX5zJ0zCZyO .node .label,#mermaid-svg-j2C8CUX5zJ0zCZyO .image-shape .label,#mermaid-svg-j2C8CUX5zJ0zCZyO .icon-shape .label{text-align:center;}#mermaid-svg-j2C8CUX5zJ0zCZyO .node.clickable{cursor:pointer;}#mermaid-svg-j2C8CUX5zJ0zCZyO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .arrowheadPath{fill:#333333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j2C8CUX5zJ0zCZyO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j2C8CUX5zJ0zCZyO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j2C8CUX5zJ0zCZyO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j2C8CUX5zJ0zCZyO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .cluster text{fill:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO .cluster span{color:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j2C8CUX5zJ0zCZyO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j2C8CUX5zJ0zCZyO rect.text{fill:none;stroke-width:0;}#mermaid-svg-j2C8CUX5zJ0zCZyO .icon-shape,#mermaid-svg-j2C8CUX5zJ0zCZyO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j2C8CUX5zJ0zCZyO .icon-shape p,#mermaid-svg-j2C8CUX5zJ0zCZyO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j2C8CUX5zJ0zCZyO .icon-shape .label rect,#mermaid-svg-j2C8CUX5zJ0zCZyO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j2C8CUX5zJ0zCZyO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j2C8CUX5zJ0zCZyO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j2C8CUX5zJ0zCZyO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用进程
System Server 进程
Linux 内核
硬件层
触摸
中断处理
写入
poll()
翻译事件
策略拦截
确认
Binder IPC
dispatchInputEvent()
onTouchEvent()
触摸屏
硬件中断 (IRQ)
Input Driver (evdev)
/dev/input/event0
InputReader 线程
InputDispatcher 线程
InputPolicy (PhoneWindowManager)
ViewRootImpl
DecorView
View
2.2 核心角色职责表
| 角色 | 运行进程 | 职责 |
|---|---|---|
| Input Driver | Linux Kernel | 将物理触摸转换为二进制数据流,写入设备节点。 |
| InputReader | System Server | 读取设备节点,解析原始数据,生成 InputEvent。 |
| InputDispatcher | System Server | 管理窗口焦点,分发 InputEvent 到目标进程。 |
| ViewRootImpl | 应用进程 | 接收系统事件,分发到 View 树。 |
3. InputReader:原始数据的解析
3.1 Linux evdev 协议
触摸屏在 Linux 中被视为一个 Character Device。
学术定义:
- 设备节点 :
/dev/input/event0,event1... - 事件结构 :
struct input_event { timeval time; unsigned short type; unsigned short code; unsigned int value; } - 事件类型 :
EV_KEY: 按键事件(电源键、音量键)。EV_ABS: 绝对坐标事件(触摸屏的 X/Y 轴)。EV_SYN: 同步事件(表示一次输入结束)。
3.2 InputReader 的读取循环
InputReader 使用 epoll 监听多个设备节点。
cpp
// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
// 1. 使用 epoll_wait 等待设备节点可读
int pollResult = epoll_wait(mEpollFd, mPendingEvents, EPOLL_MAX_EVENTS, -1);
// 2. 遍历所有可读的设备
for (int i = 0; i < pollResult; i++) {
int fd = mPendingEvents[i].data.fd;
// 3. 读取 raw_event
RawEvent rawEvent;
read(fd, &rawEvent, sizeof(rawEvent));
// 4. 处理 raw_event
processEvent(fd, &rawEvent);
}
// 5. 将处理后的事件发送给 Dispatcher
flush();
}
3.3 触摸屏事件的解析
以触摸屏为例,一次点击会产生一系列事件:
| 时间戳 | 类型 (Type) | 代码 (Code) | 值 (Value) | 含义 |
|---|---|---|---|---|
| t0 | EV_ABS | ABS_MT_TRACKING_ID | 0 | 手指按下,ID=0 |
| t0 | EV_ABS | ABS_MT_POSITION_X | 150 | X 坐标=150 |
| t0 | EV_ABS | ABS_MT_POSITION_Y | 300 | Y 坐标=300 |
| t0 | EV_SYN | SYN_REPORT | 0 | 同步(本次输入结束) |
| t1 | EV_ABS | ABS_MT_POSITION_X | 151 | 手指移动 |
| t2 | EV_ABS | ABS_MT_TRACKING_ID | -1 | 手指抬起 |
学术定义:
- Slot (槽位):多点触控时,每个手指对应一个 Slot。
- Tracking ID:手指的唯一标识,按下时分配,抬起时置为 -1。
4. InputDispatcher:事件的投递与 ANR
4.1 窗口管理与焦点
InputDispatcher 需要知道当前哪个窗口拥有焦点。
学术定义:
- Focus Window: 当前接收按键和触摸事件的窗口。
- Touch Window: 触摸事件发生区域的窗口(可能是多个,如壁纸和应用窗口)。
- Z-order: 窗口的层级,决定事件的派发顺序。
4.2 事件分发流程
应用进程 ActivityManagerService InputDispatcher InputReader 应用进程 ActivityManagerService InputDispatcher InputReader #mermaid-svg-4Dmq7tSEJDncoV7t{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4Dmq7tSEJDncoV7t .error-icon{fill:#552222;}#mermaid-svg-4Dmq7tSEJDncoV7t .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4Dmq7tSEJDncoV7t .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4Dmq7tSEJDncoV7t .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4Dmq7tSEJDncoV7t .marker.cross{stroke:#333333;}#mermaid-svg-4Dmq7tSEJDncoV7t svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4Dmq7tSEJDncoV7t p{margin:0;}#mermaid-svg-4Dmq7tSEJDncoV7t .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4Dmq7tSEJDncoV7t text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4Dmq7tSEJDncoV7t .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-4Dmq7tSEJDncoV7t .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-4Dmq7tSEJDncoV7t #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-4Dmq7tSEJDncoV7t .sequenceNumber{fill:white;}#mermaid-svg-4Dmq7tSEJDncoV7t #sequencenumber{fill:#333;}#mermaid-svg-4Dmq7tSEJDncoV7t #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-4Dmq7tSEJDncoV7t .messageText{fill:#333;stroke:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4Dmq7tSEJDncoV7t .labelText,#mermaid-svg-4Dmq7tSEJDncoV7t .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .loopText,#mermaid-svg-4Dmq7tSEJDncoV7t .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4Dmq7tSEJDncoV7t .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4Dmq7tSEJDncoV7t .noteText,#mermaid-svg-4Dmq7tSEJDncoV7t .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-4Dmq7tSEJDncoV7t .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4Dmq7tSEJDncoV7t .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4Dmq7tSEJDncoV7t .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4Dmq7tSEJDncoV7t .actorPopupMenu{position:absolute;}#mermaid-svg-4Dmq7tSEJDncoV7t .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-4Dmq7tSEJDncoV7t .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4Dmq7tSEJDncoV7t .actor-man circle,#mermaid-svg-4Dmq7tSEJDncoV7t line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-4Dmq7tSEJDncoV7t :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt窗口未就绪或超时窗口就绪 notifyMotion()查找 Focus Window检查窗口是否就绪notifyANR()弹出 ANR 弹窗dispatchMotionEvent() (Binder IPC)处理事件finishedInputEvent() (Binder IPC)
4.3 ANR 机制(5秒超时)
InputDispatcher 维护一个 Wait Queue。
学术定义:
- 分发时间戳 :
dispatchStartedTime。 - 超时阈值:5 秒(按键)/ 5 秒(触摸)。
- ANR 触发条件 :事件发出后 5 秒内没有收到
finishedInputEvent()回调。
5. 输入事件与 VSYNC 的同步
5.1 为什么需要同步?
如果输入事件处理过快,而屏幕刷新慢,会导致点击位置与画面不一致。
学术定义:
- Input-VSYNC Alignment: 输入事件的处理应该与 VSYNC 信号对齐。
- Prediction (预测): 根据历史轨迹,预测手指在下一帧的位置,减少感知延迟。
5.2 Choreographer 的介入
应用进程收到输入事件后,会交给 Choreographer 处理。
java
// ViewRootImpl.java
public void dispatchInputEvent(InputEvent event) {
// 将事件交给 Choreographer
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mInputEventReceiver, event);
}
6. 多点触控与手势
6.1 触摸事件的封装(MotionEvent)
系统将原始事件封装为 MotionEvent。
| 属性 | 含义 |
|---|---|
getActionMasked() |
动作类型(DOWN, MOVE, UP, POINTER_DOWN, POINTER_UP) |
getPointerCount() |
触点数量 |
getX(int pointerIndex) |
第 N 个触点的 X 坐标 |
getHistoricalX() |
历史坐标(用于平滑) |
6.2 手势识别
手势识别发生在应用进程,但系统提供了辅助类。
学术定义:
- GestureDetector: 识别单击、双击、长按。
- ScaleGestureDetector: 识别缩放手势。
- VelocityTracker: 计算滑动速度。
7. 关键源码解析
7.1 InputDispatcher 的分发逻辑
cpp
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// 1. 从 InboundQueue 取出事件
EventEntry* eventEntry = mInboundQueue.head;
// 2. 查找目标窗口
sp<InputWindowHandle> windowHandle = findFocusedWindow(eventEntry);
// 3. 将事件放入 OutboundQueue
DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, windowHandle);
mOutboundQueue.enqueueAtTail(dispatchEntry);
// 4. 通过 Binder 发送
status_t status = windowHandle->inputChannel->sendMessage(&message);
}
7.2 ViewRootImpl 的事件接收
java
// ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event) {
// 1. 将 InputEvent 包装为 QueuedInputEvent
QueuedInputEvent q = obtainQueuedInputEvent(event);
// 2. 放入队列
mPendingInputEventQueue.enqueue(q);
// 3. 处理队列
doProcessInputEvents();
}
}
8. 输入系统的性能瓶颈
8.1 常见卡顿原因
| 原因 | 学术解释 |
|---|---|
| InputReader 慢 | 设备节点读取阻塞,或触摸采样率过低。 |
| InputDispatcher 慢 | 窗口焦点计算复杂,或 Wait Queue 堆积。 |
| 应用处理慢 | UI 线程在 onTouchEvent() 中执行耗时操作,导致 ANR。 |
| VSYNC 不同步 | 输入事件处理与屏幕刷新不同步,导致视觉滞后。 |
8.2 系统优化手段
| 手段 | 学术定义 |
|---|---|
| Batch (批处理) | 将连续的 MOVE 事件合并,减少 IPC 次数。 |
| Resample (重采样) | 根据历史数据预测位置,平滑轨迹。 |
| Throttle (节流) | 当应用处理不过来时,丢弃部分 MOVE 事件。 |
9. 本篇总结(Knowledge Closure)
| 关键点 | 纯学术定义 |
|---|---|
| InputReader 的本质 | 原始数据翻译官,将 evdev 二进制流转换为 InputEvent。 |
| InputDispatcher 的本质 | 交通警察,根据窗口焦点和 Z-order 分发事件。 |
| ANR 机制 | 5 秒超时惩罚,确保系统响应性。 |
| VSYNC 同步 | 输入事件处理与屏幕刷新对齐,保证视觉一致性。 |
| 事件流 | 硬件中断 -> 内核驱动 -> InputReader -> InputDispatcher -> 应用进程 -> View 树。 |
下一篇预告 :第四板块:Android 输入系统与触控事件 | 第十六篇:按键分发与软键盘(IME)的窗口协同