输入焦点与触摸目标:WMS 与 IMS 如何协作
输入焦点与触摸目标:WMS 与 IMS 如何协作
上一章我们看到事件从通道进了应用。这一章回答:dispatcher 用到的窗口元数据——焦点、 bounds、可触摸区域——是谁维护的,怎么更新到 IMS?
InputDispatcher 做目标选择时依赖窗口层级、边界、可触摸区域、焦点和通道状态。这些信息 源自 WMS,但命中计算与事件投递发生在 IMS。两边看到的是同一窗口关系的不同阶段。
WMS 提供窗口状态,IMS 消费快照
| 常见误解 | 正确理解 |
|---|---|
| 焦点窗口就是触摸目标窗口 | 触摸按命中测试,按键按焦点;两者常常不一致 |
| 输入分发由 WMS 决定 | WMS 只提供窗口元数据,命中测试与投递由 IMS 的 dispatcher 完成 |
| 窗口不可触摸就是“不显示” | 是“不接收触摸”,显示可能完全正常 |
按键焦点与触摸目标不能互换。前者依赖焦点状态,后者由坐标命中决定;不可触摸窗口仍可 正常显示。排障时需要同时对照 dumpsys window 的生产侧状态和 dumpsys input 的消费侧快照。
核心结论
- WMS 维护窗口顺序、bounds、可触摸区域与焦点,并把它们封装进
InputWindowHandle注册到 IMS。 - dispatcher 对触摸事件做命中测试:从最上层窗口开始,坐标在 bounds 且在 touchableRegion 内才命中。
- dispatcher 对按键事件使用焦点窗口;焦点由 WMS 在窗口切换时更新。
- 窗口属性(如
FLAG_NOT_TOUCHABLE、不可聚焦)直接改变输入目标选择。 - 窗口移动、大小变化、焦点切换都会触发窗口元数据更新,dispatcher 随之调整目标。
元数据如何流动
逐步解释:
- WMS 维护状态:窗口添加、移动、焦点切换时更新窗口信息。
- 封装元数据:每个输入窗口对应一个
InputWindowHandle。 - 同步到 IMS:WMS 把窗口表更新到 IMS,dispatcher 读这张表做决策。
- 两类消费:触摸按表命中,按键按表中焦点分发。
常见窗口属性对输入的影响
| 属性/标志 | 对输入的影响 |
|---|---|
| bounds | 窗口的外接矩形,命中测试的第一层判断 |
| touchableRegion | 实际可接收触摸的区域,默认等于 bounds,可被裁剪 |
FLAG_NOT_TOUCHABLE | 该窗口不参与触摸命中,事件落到下层窗口 |
FLAG_NOT_FOCUSABLE | 不参与按键焦点,按键给其他焦点窗口 |
| 窗口类型 | 影响层级与部分系统窗口的输入策略 |
调试时先用 dumpsys window 看窗口属性,再结合 dumpsys input 的窗口表判断目标选择, 不要只凭“它在屏幕上”推断“它能收到触摸”。
源码证据
阅读目标:确认元数据同步与使用两侧的入口。
正文指针:WMS 侧 WindowManagerService.java 中窗口输入窗口的更新逻辑; InputManagerService.java 的窗口注册接口;InputWindowHandle.java 字段定义。
// InputWindowHandle.java(伪代码,行号以 r75 为准)
public final class InputWindowHandle {
final InputApplicationHandle inputApplicationHandle;
final IBinder windowToken;
long frameLeft, frameTop, frameRight, frameBottom; // bounds
final Region touchableRegion = new Region(); // 可触摸区域
boolean hasFocus;
int flags;
}这段代码证明: InputWindowHandle 就是 dispatcher 决策所需的最小元数据集合; bounds 与 touchableRegion 直接服务于触摸命中,hasFocus 服务于按键分发。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell dumpsys window | grep -iE "focus|mCurrentFocus"
adb shell dumpsys input | sed -n '/Input Dispatcher State/,/Pending events/p'
adb shell dumpsys window windows | grep -iE "Window #|touchable|FLAG_NOT_TOUCHABLE"先用 dumpsys window 确认焦点与窗口属性,再用 dumpsys input 看 dispatcher 窗口表里 的 bounds 与 flags。触摸“点到但没反应”时,优先确认目标窗口是否命中、是否有 FLAG_NOT_TOUCHABLE,而不是直接怀疑 View 代码。
常见误区
- “焦点窗口一定收到触摸”:触摸看命中,不看焦点。
- “WMS 决定事件发给谁”:WMS 提供元数据,IMS 做分发。
- “不可触摸 = 隐藏”:只影响输入,不影响显示。
- “窗口表永远最新”:元数据同步有窗口刷新时机,极端场景下可能滞后。
延伸问题
- 全屏手势与普通窗口触摸目标如何共存?
- 多窗口分屏时,两个应用的窗口元数据如何并列存在?
- 弹出窗口(Dialog/菜单)如何影响命中测试顺序?
- 窗口动画期间,bounds 是动画值还是目标值?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
InputWindowHandle.java | 字段定义 | 窗口输入元数据 |
WindowManagerService.java | 窗口信息更新 | 维护窗口表 |
InputManagerService.java | 窗口注册接口 | 元数据同步到 IMS |
InputDispatcher.cpp | findTouchedWindowAtLocked() | 消费元数据做命中 |
公共路径:WMS/IMS Java 在 frameworks/base/services/core/java/com/android/server/, dispatcher 在 frameworks/base/services/inputflinger/dispatcher/。行号以 r75 检索为准。
事件进入应用后的处理路径,见 View 事件分发。