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

相关推荐
石山岭15 小时前
自己动手写了一个 Android 虚拟定位 App:GPSSimulate 技术实
android·前端
杉氧17 小时前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
Kapaseker1 天前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
三少爷的鞋1 天前
Android 现代架构不需要事件总线进阶篇
android
杉氧2 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏2 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧2 天前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄2 天前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭2 天前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android
如此风景2 天前
Kotlin Flow操作符学习
android·kotlin