本文深入解析 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、用户活动更新等。
- 新事件到达:InputReader 唤醒(通过
二、事件分发核心流程:从取件到派件的全流程
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寻找当前焦点窗口,类似 “按快递地址查配送路线”:
-
权限检查:确保应用有权限接收事件(如非焦点窗口可能被拒绝)。
-
窗口状态检查:
- 窗口是否暂停(paused)或已销毁。
- 输入通道(InputChannel)是否正常(如 socket 未阻塞)。
-
超时处理:若窗口未就绪(如应用启动中),等待超时(默认 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 秒未处理输入事件,或窗口状态异常(如卡死)。
-
流程:
handleTargetsNotReadyLocked检测到窗口未就绪,开始计时。- 超时后调用
onANRLocked,通知系统显示 ANR 对话框。
4.2 应用切换优化
当用户按下 Home 键等切换 APP 的按键时:
- InputDispatcher 设置
mAppSwitchDueTime(当前时间 + 500ms)。 - 若超时未完成切换,丢弃当前所有事件,优先处理切换逻辑,确保界面快速响应。
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查看队列状态。
六、流程图解
通过本文,可清晰理解 InputDispatcher 如何通过 “取件→分拣→派件” 流程完成事件分发,以及其在系统稳定性和响应速度中的关键作用。后续文章将深入探讨 InputChannel 的跨进程通信机制(如 socket 建立与数据传输),进一步揭示输入系统的底层实现。