要深入分析设置 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
,指明事件分发失败的根源。