Acitivity的Window慎用FLAG_NOT_FOCUSABLE

要深入分析设置 FLAG_NOT_FOCUSABLE 导致输入分发超时(ANR)的原因,需从 Android 输入事件分发的底层机制入手,结合 InputManagerService(IMS)、WindowManagerService(WMS)和应用进程的事件处理逻辑,剖析事件 "卡住" 的根源。

1. 输入事件分发的核心流程与超时机制

Android 系统中,输入事件(触摸、按键等)的分发由 InputManagerService(IMS) 主导,其核心组件 InputDispatcher 负责事件队列管理和超时监控。当输入事件产生后,流程如下:

  1. 事件接收 :Linux 内核捕获输入事件,传递给 IMS 的 InputReader

  2. 事件分发InputReader 将事件转换为 Android 事件对象(MotionEvent/KeyEvent),交给 InputDispatcher

  3. 目标窗口匹配InputDispatcher 通过 WMS 查找最合适的目标窗口(基于窗口可见性、层级、焦点等)。

  4. 事件发送与监控InputDispatcher 将事件通过 InputChannel 发送到目标窗口,并启动超时计时器(默认 5 秒)。

  5. 事件处理反馈 :应用进程处理完事件后,通过 InputChannelInputDispatcher 发送 "处理完成" 信号,计时器终止。

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. 为何触摸事件会导致超时?

触摸事件到达窗口后,需经过应用进程内的 ViewRootImplDecorViewViewGroupView 层级分发,最终由某个 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 产生的核心链为:

  1. 事件分发矛盾 :触摸事件被 InputDispatcher 分发到窗口(因未设置 FLAG_NOT_TOUCHABLE),但窗口无焦点,导致事件无法被正常消费(或消费后未正确反馈)。
  2. 超时监控触发InputDispatcher 等待事件处理结果超过 5 秒,判定为 "应用无响应",触发 ANR。
  3. 日志归因 :系统检测到目标窗口无焦点,故在 ANR 日志中注明 does not have a focused window,指明事件分发失败的根源。
相关推荐
程序员JerrySUN2 小时前
Valgrind Memcheck 全解析教程:6个程序说明基础内存错误
android·java·linux·运维·开发语言·学习
经典19923 小时前
mysql 性能优化之Explain讲解
android·mysql·性能优化
Kiri霧4 小时前
Kotlin集合与空值
android·开发语言·kotlin
Glacien6 小时前
compose动画从底层基础到顶层高级应用(三)核心API之--Transition
android
亿刀6 小时前
为什么要学习Flutter编译过程
android·flutter
suqingxiao6 小时前
android虚拟机(AVD)报错The emulator process for AVD xxx has terminated
android
whysqwhw6 小时前
OkHttp Cookie 处理机制全解析
android
Evan_ZGYF丶7 小时前
【RK3576】【Android14】ADB工具说明与使用
android·驱动开发·android14·rk3576
幻雨様7 小时前
UE5多人MOBA+GAS 番外篇:移植Lyra的伤害特效(没用GameplayCue,因为我失败了┭┮﹏┭┮)
android·ue5
狂浪天涯8 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 4 | GraphicBuffer & Gralloc
android·操作系统