Input系统之InputDispatcher线程

363 阅读5分钟

本文深入解析 Android 输入系统中InputDispatcher 线程的工作原理,重点阐述其如何从 InputReader 接收事件、筛选目标窗口并完成事件分发。以下以通俗语言结合生活场景类比,带你理解这一关键流程。

一、InputDispatcher 线程的核心使命:事件分发的 “快递分拣中心”

InputDispatcher 是输入系统的 “最后一公里分拣员”,它的职责是将 InputReader 处理好的事件(如按键、触摸)精准派发给目标应用窗口。其工作流程可类比为:
InputReader(收件员)  → 暂存区(mInboundQueue) → InputDispatcher(分拣员)  → 目标地址(应用窗口)

1.1 线程起点:循环分发事件

InputDispatcher 线程通过threadLoop方法持续运行,核心逻辑是循环调用dispatchOnce进行事件分发:

cpp

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce(); // 每次循环处理一次事件分发
    return true; // 无限循环
}
  • Looper 机制:通过Looper.pollOnce进入等待状态,直至以下情况唤醒:

    • 新事件到达:InputReader 唤醒(通过Looper.wake)。
    • 超时:设定的分发超时时间到达(如 APP 切换超时)。
    • 系统命令:如处理 ANR、用户活动更新等。

二、事件分发核心流程:从取件到派件的全流程

2.1 取出事件:从暂存区获取待分发事件

InputDispatcher 从mInboundQueue(InputReader 存放事件的暂存区)取出头部事件,存入mPendingEvent(当前处理事件):

cpp

void InputDispatcher::dispatchOnceInnerLocked(...) {
    if (!mInboundQueue.isEmpty()) {
        mPendingEvent = mInboundQueue.dequeueAtHead(); // 取出事件
        resetANRTimeoutsLocked(); // 重置ANR计时
    }
}
  • ANR 计时:每次处理新事件时重置计时,若后续分发超时(如应用未及时响应),将触发 ANR。

2.2 事件预处理:判断是否需要丢弃事件

分发前检查事件是否需要丢弃(如 APP 切换时丢弃旧事件):

cpp

switch (mPendingEvent->type) {
    case TYPE_KEY: {
        KeyEntry* keyEntry = ...;
        if (isAppSwitchDue) { // APP切换超时,丢弃事件
            dropReason = DROP_REASON_APP_SWITCH;
        }
        ...
    }
}
  • 丢弃原因:策略拦截(如系统键优先)、APP 切换、事件过期等。

2.3 寻找目标窗口:确定事件该发给谁

通过findFocusedWindowTargetsLocked寻找当前焦点窗口,类似 “按快递地址查配送路线”:

  1. 权限检查:确保应用有权限接收事件(如非焦点窗口可能被拒绝)。

  2. 窗口状态检查

    • 窗口是否暂停(paused)或已销毁。
    • 输入通道(InputChannel)是否正常(如 socket 未阻塞)。
  3. 超时处理:若窗口未就绪(如应用启动中),等待超时(默认 5 秒)后触发 ANR:

    cpp

    if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(...); // 触发ANR流程
    }
    

2.4 事件分发:将事件发送到目标窗口

找到窗口后,通过InputChannel(跨进程通信通道)发送事件,类似 “通过快递车运输包裹”:

cpp

void InputDispatcher::startDispatchCycleLocked(...) {
    // 生成DispatchEntry(带分发模式的事件)
    DispatchEntry* dispatchEntry = new DispatchEntry(...);
    // 通过InputChannel发送事件
    status = connection->inputPublisher.publishKeyEvent(...);
    // 事件从outboundQueue转移到waitQueue,等待应用响应
    connection->outboundQueue.dequeue(dispatchEntry);
    connection->waitQueue.enqueueAtTail(dispatchEntry);
}
  • 分发模式:根据事件类型(如按键、触摸)选择不同模式(如前台优先、悬停事件等)。
  • socket 通信:InputChannel 基于 socket 实现,确保事件跨进程传递的可靠性。

三、命令队列:处理系统级指令

InputDispatcher 通过mCommandQueue处理系统级命令(如更新用户活动、通知 ANR),类似 “处理分拣中心的特殊任务”:

cpp

bool InputDispatcher::runCommandsLockedInterruptible() {
    while (!mCommandQueue.isEmpty()) {
        CommandEntry* command = mCommandQueue.dequeueAtHead();
        (this->*command->command)(command); // 执行命令(如唤醒屏幕)
    }
}
  • 典型命令

    • 用户活动更新:调用 PowerManagerService 保持设备唤醒(如触摸屏幕后防止锁屏)。
    • ANR 通知:通知系统应用无响应,触发 ANR 对话框。
    • 策略拦截:如 Home 键事件优先由系统处理,拦截后不再派发给应用。

四、关键机制解析

4.1 ANR 触发条件

  • 场景:应用超过 5 秒未处理输入事件,或窗口状态异常(如卡死)。

  • 流程

    1. handleTargetsNotReadyLocked检测到窗口未就绪,开始计时。
    2. 超时后调用onANRLocked,通知系统显示 ANR 对话框。

4.2 应用切换优化

当用户按下 Home 键等切换 APP 的按键时:

  1. InputDispatcher 设置mAppSwitchDueTime(当前时间 + 500ms)。
  2. 若超时未完成切换,丢弃当前所有事件,优先处理切换逻辑,确保界面快速响应。

4.3 事件可靠性

  • 重试机制:若事件发送失败(如 socket 阻塞),会重新放入队列等待下一次分发。
  • 状态跟踪:通过waitQueue记录已发送但未响应的事件,确保应用处理后才标记事件完成。

五、总结:InputDispatcher 的角色与协作

1. 核心角色

  • 事件分发枢纽:衔接 InputReader 与应用窗口,确保事件按策略高效传递。
  • 系统守护者:通过超时检测、策略拦截等机制维护系统稳定性(如防止 ANR、优先处理关键事件)。

2. 协作流程

plaintext

InputReader → mInboundQueue(暂存事件) → InputDispatcher → 
findFocusedWindowTargetsLocked(找目标窗口) → 
InputChannel(跨进程发送事件) → 应用窗口(ViewRootImpl处理)

3. 延伸思考

  • Q:为什么 InputDispatcher 需要独立线程?
    A:避免事件分发阻塞其他操作,确保输入响应的实时性(如游戏触控需要低延迟)。
  • Q:如何调试分发延迟?
    A:通过日志跟踪InputDispatcher的 ANR 计时、窗口状态检查结果,或使用adb shell dumpsys input查看队列状态。

六、流程图解

exported_image.png

通过本文,可清晰理解 InputDispatcher 如何通过 “取件→分拣→派件” 流程完成事件分发,以及其在系统稳定性和响应速度中的关键作用。后续文章将深入探讨 InputChannel 的跨进程通信机制(如 socket 建立与数据传输),进一步揭示输入系统的底层实现。