第四板块: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 小时前
Vibe Coding 四个核心阶段
android·前端·app
潘潘潘3 小时前
Android网络结构分析——有线网络
android
踏雪羽翼3 小时前
Android OpenGL实现十几种美颜功能
android
Android小码家5 小时前
BootAnimation+SE+开机MP4动画播放
android·framework
加农炮手Jinx5 小时前
Flutter for OpenHarmony:pub_updater 命令行工具自动更新专家(DevOps 运维必备) 深度解析与鸿蒙适配指南
android·运维·网络·flutter·华为·harmonyos·devops
2601_957418806 小时前
告别OTG碎片化!Android MTP协议深度解析与高性能通信方案
android
故渊at6 小时前
第二板块:Android 四大组件标准化学理 | 第七篇:Activity 页面载体与任务栈算法
android·算法·生命周期·activity·任务栈
QING6187 小时前
Kotlin 协程新手指南 —— 协程上下文与调度器
android·kotlin·android jetpack