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

相关推荐
启雀AI5 小时前
培训平台移动端离线学习方案设计与实现:视频缓存、断点续传与进度同步的工程实践
android·学习·缓存·音视频·企业lms
我命由我123456 小时前
RxJava - 冷数据流与热数据流
android·java·java-ee·android studio·rxjava·android-studio·android runtime
2601_966563528 小时前
愤怒的小鸟单机版 去广告 安卓+PC端离线纯净版 益智耐玩小游戏 不需联网 老旧手机都可以玩 怀旧手机小游戏
android·智能手机
hunterandroid8 小时前
[Android 从零到一] Retrofit 请求取消与生命周期绑定:从 Call.cancel 到协程可取消设计
android
爱跑马的程序员8 小时前
安卓专有的通信子系统-Binder IPC
android·binder·ipc·安卓间通信机制
ue星空9 小时前
【安卓逆向】为什么用Frida?
android
小驰行动派10 小时前
Camx架构全景图:从V4L2到Pipeline的完整拆解
android·camera·android camera
hunterandroid10 小时前
[Android 从零到一] Custom View 触摸反馈与手势冲突解决
android
coderSong256811 小时前
Android | 四大组件之 BroadcastReceiver(广播接收器)
android
爱笑鱼14 小时前
Android 系统启动机制(二):init.rc 不是普通脚本,service、action 和 property 怎样驱动启动?
android