为何 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),必须通过焦点状态心跳输入通道代理维持事件流的连续性。

相关推荐
d***9352 小时前
springboot3.X 无法解析parameter参数问题
android·前端·后端
s***11707 小时前
Mysql convert函数、convert用法、字符串转数字、字符串转日期、类型转换函数
android·数据库·mysql
n***26568 小时前
【MySQL】MVCC详解, 图文并茂简单易懂
android·数据库·mysql
程序猿陌名!8 小时前
Android-EDLA RK3576谷歌ATTESTION-KEY从申请到烧录以及验证谷歌认证标志全流程
android
安卓理事人8 小时前
安卓版本升级功能
android
s***35308 小时前
怎么下载安装yarn
android·前端·后端
z***94848 小时前
使用rustDesk搭建私有远程桌面
android·前端·后端
q***06298 小时前
【细如狗】记录一次使用MySQL的Binlog进行数据回滚的完整流程
android·数据库·mysql
0***86338 小时前
图文详述:MySQL的下载、安装、配置、使用
android·mysql·adb
9***44638 小时前
SQLyog安装配置(注册码)连接MySQL
android·mysql·adb