为何 FLAG_NOT_FOCUSABLE 导致 ANR

ANR 根本原因:InputDispatcher 超时机制

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
void updateInputWindowsLw(boolean force) {
    // 关键流程:构建可接收输入的窗口列表
    for (WindowState w : mService.mWindowManager.getWindowListLocked()) {
        if (w.canReceiveKeys()) {  // FLAG_NOT_FOCUSABLE 窗口被排除
            addInputWindowHandleLw(handles, w);
        }
    }
    // 将窗口列表同步到 InputDispatcher
    mService.mInputManager.setInputWindows(handles);
}

事件分发核心流程


关键源码路径分析

1. 窗口焦点状态更新 (WindowManagerService)

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean canReceiveKeys() {
    return (mAttrs.flags & FLAG_NOT_FOCUSABLE) == 0; // 关键判断
}

// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
void setFocusedWindow(WindowState newFocus) {
    if (newFocus == null || !newFocus.canReceiveKeys()) {
        mInputMonitor.setFocusedAppLw(null); // 清除焦点应用
    }
}

2. InputDispatcher 焦点处理 (Native 层)

cpp 复制代码
// frameworks/native/services/inputflinger/InputDispatcher.cpp
bool InputDispatcher::findFocusedWindowTargetsLocked(...) {
    if (mFocusedWindowHandle == nullptr) {
        // ANR 触发点:记录超时原因
        reason = StringPrintf("because there is no focused window");
        return false;
    }
    // ...正常分发流程...
}

3. ANR 检测机制

cpp 复制代码
// frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::processAnrsLocked() {
    if (currentTime > mAnrTracker.firstTimeout()) {
        // 生成 ANR 报告
        dispatchUnresponsive = createAnrMessageLocked();
        // 回调到 Java 层
        mPolicy->notifyUnresponsive(dispatchUnresponsive);
    }
}

多模块协同故障链


解决方案:避免 ANR 的架构设计

方案 1:动态焦点控制 + 心跳机制

java 复制代码
// 在 Activity 中实现焦点状态机
private val focusStateMachine = object : FocusStateListener {
    override fun onNeedFocus() {
        window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
        window.decorView.post { 
            window.decorView.requestFocus() 
        }
    }
    
    override fun onCanReleaseFocus() {
        window.setFlags(
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        )
    }
}

// 添加输入事件心跳检测
class InputHeartbeatTracker(context: Context) {
    fun start() {
        Handler(Looper.getMainLooper()).postDelayed(heartbeatRunnable, 1000)
    }
    
    private val heartbeatRunnable = object : Runnable {
        override fun run() {
            if (SystemClock.uptimeMillis() - lastEventTime > 3000) {
                focusStateMachine.onNeedFocus() // 防止长期无焦点
            }
            handler.postDelayed(this, 1000)
        }
    }
}

方案 2:自定义 InputChannel 代理

java 复制代码
// 创建透明输入代理窗口
fun createInputProxyWindow() {
    val params = WindowManager.LayoutParams(
        TYPE_APPLICATION_OVERLAY,
        FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
        PixelFormat.TRANSPARENT
    ).apply {
        inputFeatures = INPUT_FEATURE_PROXY_INPUT
    }
    
    val proxyView = View(context).apply {
        setOnKeyListener { _, keyCode, event ->
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                handleBackPressed()
                true
            } else false
        }
    }
    
    windowManager.addView(proxyView, params)
}

方案 3:WMS 层 Hook (需系统权限)

java 复制代码
// 通过 AIDL 定制 WindowManagerPolicy
interface ICustomWindowPolicy : IInterface {
    fun shouldSkipFocusCheck(attrs: WindowManager.LayoutParams): Boolean
}

// 在 PhoneWindowManager 中注入
override fun interceptKeyBeforeQueueing(event: KeyEvent, policyFlags: Int): Int {
    if (customPolicy.shouldSkipFocusCheck(topWindow.attrs)) {
        return ACTION_PASS_TO_USER // 绕过焦点检查
    }
    return super.interceptKeyBeforeQueueing(event, policyFlags)
}

性能优化表:焦点控制策略对比

策略 ANR 风险 系统负担 兼容性 实现复杂度
动态焦点控制 ★☆☆☆☆ API 1+ ★★☆☆☆
InputChannel 代理 ★★☆☆☆ API 26+ ★★★★☆
WMS 层 Hook ☆☆☆☆☆ 需ROOT ★★★★★
透明Activity桥接 ★★★☆☆ API 1+ ★★★☆☆

核心设计原则 :在 Android 输入子系统中,FLAG_NOT_FOCUSABLE 会破坏 WMS(WindowManagerService) 和 InputDispatcher 之间的焦点契约。任何长期处于无焦点窗口的状态都会触发系统的安全保护机制(ANR),必须通过焦点状态心跳输入通道代理维持事件流的连续性。

相关推荐
lichong9516 小时前
Android studio 修改包名
android·java·前端·ide·android studio·大前端·大前端++
爱学习的大牛1238 小时前
MVVM 架构 android
android·mvvm
alexhilton10 小时前
理解retain{}的内部机制:Jetpack Compose中基于作用域的状态保存
android·kotlin·android jetpack
꒰ঌ 安卓开发໒꒱11 小时前
Mysql 坏表修复
android·mysql·adb
_李小白11 小时前
【Android Gradle学习笔记】第八天:NDK的使用
android·笔记·学习
袁震11 小时前
Android-Compose 列表组件详解
android·recyclerview·compose
2501_9160074713 小时前
提升 iOS 26 系统流畅度的实战指南,多工具组合监控
android·macos·ios·小程序·uni-app·cocoa·iphone
zh_xuan13 小时前
android 利用反射和注解绑定控件id和点击事件
android·注解·反射·控件绑定
这个杀手不太累15 小时前
Android ProcessLifecycleOwner
android·lifecycle
SRC_BLUE_1716 小时前
NSSCTF - Web | 【第五空间 2021】pklovecloud
android·前端