InputDispatcher:事件如何找到目标窗口
InputDispatcher:事件如何找到目标窗口
上一章里,InputReader 把事件放进了队列。这一章回答:InputDispatcher 怎么决定把事件 送给哪个窗口,触摸和按键的目标选择有什么不同?
InputDispatcher 从入站队列取出事件后,需要结合 WMS 同步的窗口信息选择目标。触摸事件 根据坐标、层级和可触摸区域命中窗口;按键事件通常投递给当前输入焦点窗口。
触摸目标与按键目标不是同一规则
| 常见误解 | 正确理解 |
|---|---|
| 所有事件都发给“当前窗口” | 触摸事件按坐标命中窗口,按键事件才发给焦点窗口 |
| dispatcher 直接调用应用代码 | 它只把事件写入窗口的 InputChannel,应用在自己的线程接收 |
| dispatcher 是单队列一把梭 | 它有入站队列,还为每个目标窗口维护出站队列 |
选中目标后,dispatcher 为目标连接建立待发送记录并写入 InputChannel。它不会直接调用 应用 View;应用能否及时取出并完成事件,决定该连接是否持续占用未完成队列并触发超时。
核心结论
- dispatcher 维护一张窗口表,条目由 WMS 注册的
InputWindowHandle提供。 - 触摸事件做命中测试:从最上层窗口开始,找包含坐标且在可触摸区域内的窗口。
- 按键事件发给焦点窗口;焦点由 WMS 维护,dispatcher 只消费这个元数据。
- 事件先进入全局入站队列,再按目标窗口进入各自的出站队列,保证同窗口事件有序。
- 事件写入窗口通道后开始 ANR 计时:应用不及时消费,dispatcher 会报超时。
分发主链
逐步解释:
- 入站队列:InputReader 产出的标准事件先进入全局入站队列。
- 目标选择:触摸事件按窗口的 bounds 与 touchableRegion 命中测试;按键事件按焦点窗口分发。
- 出站队列:每个目标窗口一条出站队列,同一窗口的事件按序投递。
- 写入通道:dispatcher 把事件写进窗口的 InputChannel,应用线程随后接收。
命中测试为什么不是“像素点击”
窗口元数据里除了 bounds,还有 touchableRegion、flags 等信息。命中测试从最上层窗口开始 逐层判断:坐标在窗口范围内且落在可触摸区域内才算命中;窗口还可以通过 FLAG_NOT_TOUCHABLE 把触摸让给下层。因此“显示在最上面”和“能收到触摸”是两件事, 这也是触摸目标与焦点窗口常常不一致的原因。
源码证据
阅读目标:确认入站队列与目标选择入口。
正文指针:InputDispatcher.cpp enqueueInboundEventLocked()/dispatchOnce()/ dispatchPointerEventLocked()/findTouchedWindowAtLocked()。
// InputDispatcher.cpp(伪代码,行号以 r75 为准)
void InputDispatcher::dispatchOnce() {
// 从入站队列取事件,决定目标窗口,再按窗口排队投递
}
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(...) {
// 从最上层窗口开始命中测试
}这段代码证明: findTouchedWindowAtLocked() 是触摸目标选择的实现点;dispatch 循环 把事件从入站队列搬运到目标窗口的出站队列。目标窗口的选择完全依赖 WMS 注册的窗口元数据, dispatcher 自身不创建窗口。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell dumpsys input | sed -n '/Input Dispatcher State/,/Registered input channels/p'
adb shell dumpsys window | grep -i focus
adb logcat -v threadtime | grep -E "InputDispatcher|ANR"dumpsys input 的 “Input Dispatcher State” 段落列出窗口表、焦点、出站队列与未消费事件; dumpsys window 确认焦点窗口;logcat 里的 InputDispatcher 日志会标明分发目标与超时。 事件“到了 dispatcher 但没到应用”,问题在目标选择或通道投递;事件根本没到 dispatcher, 问题在更前面的 reader/EventHub。
常见误区
- “触摸目标就是焦点窗口”:触摸按命中测试,按键按焦点。
- “dispatcher 调用应用代码”:它只写通道,应用线程自己取。
- “一个全局队列串行分发”:入站一个队列,每个目标窗口还有出站队列。
- “窗口在最上层就一定能收到触摸”:还要满足可触摸区域与 flags。
延伸问题
InputWindowHandle里哪些字段影响命中测试?- 同一窗口连续快速触摸事件如何保证顺序?
- dispatcher 如何处理事件注入(
input tap)与真实事件的优先级? - 系统手势监听(gesture monitor)与普通窗口的目标选择有什么不同?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
InputDispatcher.cpp | dispatchOnce() | 分发主循环 |
InputDispatcher.cpp | enqueueInboundEventLocked() | 入站队列 |
InputDispatcher.cpp | findTouchedWindowAtLocked() | 触摸命中测试 |
InputDispatcher.cpp | startDispatchCycleLocked() | 写入窗口通道 |
公共路径:frameworks/base/services/inputflinger/dispatcher/。行号以 r75 检索为准。
目标事件如何跨进程进入应用,见 InputChannel 与 InputEventReceiver。