第四板块:Android 输入系统与触控事件 | 第十五篇:InputReader 与 InputDispatcher 的触控流水线

第四板块: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)的窗口协同

相关推荐
三少爷的鞋1 天前
Java 线程自上而下,Kotlin 协程自下而上
android
好的佩奇1 天前
Dart 之增强枚举
android·flutter·dart
梦影_2 天前
MySQL进阶教程(五)—— SQL调优(下)
android·大数据·数据库·后端·sql·mysql·database
好的佩奇2 天前
蓝牙之无线电与通信距离
android·flutter·蓝牙
vilya2 天前
我把一套完整的 Python Agent 运行时塞进手机 APK:依赖闭包、Rust 轮子和三个真机坑
android
呆呆敲代码的小Y2 天前
【实战精选面试题】C# 面试题详细解答,按难易程度排序
android·java·游戏·c#·游戏开发·unity面试题·c#面试题
思想觉悟2 天前
SEAndroid实战
android·linux·c++
思想觉悟2 天前
Aosp开发磨刀石之ADB速查
android·c++
应用市场2 天前
用本机跑的开源音乐模型,给安卓小游戏做 20 秒无缝循环背景音乐
android·python·游戏开发·soundpool·音频处理
企业数字化笔记2 天前
表单发布前怎么验收?版本引用、字段兼容和历史数据回归
android·数据挖掘·回归