要深入分析设置 FLAG_NOT_FOCUSABLE 导致输入分发超时(ANR)的原因,需从 Android 输入事件分发的底层机制入手,结合 InputManagerService(IMS)、WindowManagerService(WMS)和应用进程的事件处理逻辑,剖析事件 "卡住" 的根源。
1. 输入事件分发的核心流程与超时机制
Android 系统中,输入事件(触摸、按键等)的分发由 InputManagerService(IMS) 主导,其核心组件 InputDispatcher 负责事件队列管理和超时监控。当输入事件产生后,流程如下:
-
事件接收 :Linux 内核捕获输入事件,传递给 IMS 的
InputReader。 -
事件分发 :
InputReader将事件转换为 Android 事件对象(MotionEvent/KeyEvent),交给InputDispatcher。 -
目标窗口匹配 :
InputDispatcher通过 WMS 查找最合适的目标窗口(基于窗口可见性、层级、焦点等)。 -
事件发送与监控 :
InputDispatcher将事件通过InputChannel发送到目标窗口,并启动超时计时器(默认 5 秒)。 -
事件处理反馈 :应用进程处理完事件后,通过
InputChannel向InputDispatcher发送 "处理完成" 信号,计时器终止。
ANR 触发条件 :若事件发送后,InputDispatcher 在 5 秒内未收到 "处理完成" 信号,则判定为 "输入分发超时",触发 ANR,并输出日志 Input dispatching timed out。
2. FLAG_NOT_FOCUSABLE 对事件分发的关键影响
设置 FLAG_NOT_FOCUSABLE 后,窗口的属性会发生以下变化(源码验证):
java
typescript
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java
public boolean canReceiveKeys() {
// 窗口不可聚焦时,无法接收按键事件
return !mAttrs.flags.contains(FLAG_NOT_FOCUSABLE) && !mAttrs.flags.contains(FLAG_NOT_TOUCHABLE);
}
public boolean isTouchable() {
// 触摸事件是否可接收,与 FOCUSABLE 无关,仅由 FLAG_NOT_TOUCHABLE 控制
return !mAttrs.flags.contains(FLAG_NOT_TOUCHABLE);
}
-
按键事件:被 WMS 拦截(如前文分析),不会到达应用。
-
触摸事件 :只要未设置
FLAG_NOT_TOUCHABLE,仍会被InputDispatcher分发到窗口(基于坐标命中检测)。
隐患点 :触摸事件虽能到达窗口,但窗口 "不可聚焦" 的状态会破坏事件处理的闭环,导致 InputDispatcher 无法收到 "处理完成" 信号,最终超时。
3. 为何触摸事件会导致超时?
触摸事件到达窗口后,需经过应用进程内的 ViewRootImpl → DecorView → ViewGroup → View 层级分发,最终由某个 View 消费(如按钮的 onClick)。若事件未被消费,系统会默认 "处理完成",不会超时。但 FLAG_NOT_FOCUSABLE 会导致以下异常:
3.1 窗口状态矛盾:可触摸但不可交互
FLAG_NOT_FOCUSABLE 的语义是 "窗口不可交互"(无法获取焦点),但触摸事件仍被强制分发到窗口。此时,应用进程可能处于 "矛盾状态":
- 窗口内的
View可能因窗口无焦点而无法正常处理事件(例如,依赖焦点的EditText无法响应输入,但普通按钮理论上可点击)。 - 若窗口内所有
View均未消费触摸事件,事件会 "穿透" 到下层窗口吗?实际不会:InputDispatcher已将事件分配给当前窗口,下层窗口不会再接收,导致事件 "悬停" 在当前窗口,未被任何组件处理。
3.2 ViewRootImpl 对无焦点窗口的事件处理异常
ViewRootImpl 是应用进程中连接窗口与 View 树的核心类,负责接收 InputChannel 传来的事件并分发到 View 树。当窗口无焦点时,ViewRootImpl 的事件处理逻辑可能异常:
java
csharp
// frameworks/base/core/java/android/view/ViewRootImpl.java
public void dispatchInputEvent(InputEvent event) {
// 检查窗口是否处于"可交互"状态
if (!mWindowAttributes.flags.contains(FLAG_NOT_FOCUSABLE)) {
// 正常分发流程
mInputEventReceiver.dispatchInputEvent(event);
} else {
// 无焦点窗口的特殊处理:可能直接丢弃事件,或未正确发送"处理完成"信号
finishInputEvent(event, false); // 假设此处逻辑错误,未通知 InputDispatcher
}
}
若 ViewRootImpl 对无焦点窗口的事件处理存在漏洞(例如,未调用 finishInputEvent 通知 InputDispatcher 事件已处理),InputDispatcher 会一直等待,直至超时触发 ANR。
3.3 系统级事件监控的误判
WMS 维护着 "焦点窗口" 的全局状态,当应用设置 FLAG_NOT_FOCUSABLE 后,WMS 会将该窗口从 "焦点窗口列表" 中移除。此时:
InputDispatcher在分发事件时,发现目标窗口 "无焦点",但事件已被发送,形成 "系统认为窗口不应处理事件,但事件已到达窗口" 的矛盾。- 系统可能因 "目标窗口无焦点" 而拒绝承认其事件处理结果,导致
InputDispatcher始终收不到确认信号,触发超时。
4. 源码验证:ANR 触发的关键判定逻辑
InputDispatcher 中,超时判定的核心代码如下:
java
arduino
// frameworks/base/services/core/java/com/android/server/input/InputDispatcher.java
void handleTargetsNotReadyLocked(long currentTime) {
// 遍历所有未处理的事件
for (const auto& entry : mInboundQueue) {
nsecs_t timeout = entry.event->getTimeout();
if (currentTime >= entry.deliveryTime + timeout) {
// 事件超时,触发 ANR
onANRLocked(entry.event, "Input dispatching timed out");
return;
}
}
}
当窗口设置 FLAG_NOT_FOCUSABLE 时,触摸事件的 deliveryTime 被记录,但因未收到 "处理完成" 信号,currentTime 会逐渐超过 deliveryTime + 5000ms(超时阈值),最终触发 onANRLocked 方法,输出错误日志 does not have a focused window(因目标窗口无焦点)。
5. 总结:ANR 的根本原因
设置 FLAG_NOT_FOCUSABLE 后,ANR 产生的核心链为:
- 事件分发矛盾 :触摸事件被
InputDispatcher分发到窗口(因未设置FLAG_NOT_TOUCHABLE),但窗口无焦点,导致事件无法被正常消费(或消费后未正确反馈)。 - 超时监控触发 :
InputDispatcher等待事件处理结果超过 5 秒,判定为 "应用无响应",触发 ANR。 - 日志归因 :系统检测到目标窗口无焦点,故在 ANR 日志中注明
does not have a focused window,指明事件分发失败的根源。