Android:坑-dialog失焦主动关闭崩溃

dialog失焦主动关闭崩溃

欢迎关注我的代码库:wuxu_future

开发时,有这样一个需求浮窗需要在另一个浮窗出现后,自动关闭,也就是在window失焦时,主动关闭。可是出现了空指针:

java 复制代码
 E  FATAL EXCEPTION: main
Process: com.wuxu.floatwindow.cient2, PID: 29521
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.View.findFocus()' on a null object reference
    at android.view.ViewRootImpl.handleWindowFocusChanged(ViewRootImpl.java:3272)
    at android.view.ViewRootImpl.access$1100(ViewRootImpl.java:191)
    at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:5053)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7656)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:949)

解决方案

最终解决方案:使用 postDelayed 解决。

kotlin 复制代码
override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    if (!hasFocus) {
        // 延迟处理避免焦点切换过程中的竞争条件
        window?.decorView?.postDelayed({
            safeDismiss()
        }, 200)
    }
}

这段 postDelayed 代码的作用是 延迟处理焦点丢失事件,避免窗口状态冲突。具体来说:

关键作用说明

  1. 防竞态条件(Race Condition)

    • 当窗口焦点快速切换时(如用户快速点击其他窗口),立即调用 dismiss() 可能导致:
      • 窗口尚未完成焦点丢失流程
      • 视图树处于中间状态
    • 延迟 200ms 确保系统完成焦点变更的完整周期
  2. 规避异步操作残留

    • onWindowFocusChanged 中直接操作,可能与其他异步任务(如动画、布局计算)产生冲突
    • 延迟到主线程消息队列尾部执行,保证操作顺序性
  3. 视图状态验证窗口期

    • 200ms 的延迟为视图系统提供缓冲时间,可在此期间:
      • 检测是否焦点重新获得(如用户快速切回)
      • 验证 window.decorView 是否仍有效

参数选择依据

延迟时间 适用场景 风险
200ms 通用焦点切换场景 较平衡用户体验与稳定性
更短 (50-100ms) 对响应速度敏感场景 可能残留竞态条件
更长 (300-500ms) 复杂窗口系统(如 TV 大屏) 用户感知延迟

典型应用场景示例

  1. 全局悬浮窗失焦关闭

    当用户点击其他应用窗口时,延迟确保悬浮窗关闭操作不会与系统焦点管理冲突

  2. 游戏悬浮工具栏

    kotlin 复制代码
    // 在游戏暂停菜单中
    override fun onPause() {
        window.decorView.postDelayed(::hideToolbar, 200) 
    }
  3. TV Launcher 焦点管理

    kotlin 复制代码
    // 处理遥控器方向键快速导航
    fun onFocusChange() {
        postDelayed({ updateFocusHighlight() }, 200)
    }

我来讲两句

上面的代码中,postDelayed 的作用是延迟处理焦点丢失事件,避免窗口状态冲突,但AI回答的并不是准确原因。先看源码

  1. 首先是ViewRootImpl.java

    java 复制代码
    private void handleWindowFocusChanged() {
        //...
    
            if (mView != null) {
                mAttachInfo.mKeyDispatchState.reset();
                mView.dispatchWindowFocusChanged(hasWindowFocus);
                mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); // 这里1
                if (mAttachInfo.mTooltipHost != null) {
                    mAttachInfo.mTooltipHost.hideTooltip();
                }
            }
    
            // Note: must be done after the focus change callbacks,
            // so all of the view state is set up correctly.
            mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus, // 这里 2
                    mWindowAttributes);
    
        //-----------------
        //...
    }

    由上就可以看到,1是分发状态,2是报错的地方,findFocus()

  2. 再看dialog.java源码

    java 复制代码
    @UnsupportedAppUsage
    void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }
    
        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }
    
        try {
            mWindowManager.removeViewImmediate(mDecor); // 这里 3
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;
    
            sendDismissMessage();
        }
    }

    看到3的位置,就能知道,mWindowManager直接移除了mDecor。

  3. 结合上面两块代码,我们已经可以知道,我在dialog的onWindowFocusChanged回调直接调用了dismiss(),后面viewRootImpl的mView.findFocus()为空,所以导致空指针。

相关推荐
LittleLoveBoy1 小时前
踩坑:uiautomatorviewer.bat 打不开
android
居然是阿宋1 小时前
Android核心系统服务:AMS、WMS、PMS 与 system_server 进程解析
android
CGG924 小时前
【单例模式】
android·java·单例模式
kp000005 小时前
PHP弱类型安全漏洞解析与防范指南
android·开发语言·安全·web安全·php·漏洞
编程乐学(Arfan开发工程师)10 小时前
06、基础入门-SpringBoot-依赖管理特性
android·spring boot·后端
androidwork10 小时前
使用 Kotlin 和 Jetpack Compose 开发 Wear OS 应用的完整指南
android·kotlin
繁依Fanyi11 小时前
Animaster:一次由 CodeBuddy 主导的 CSS 动画编辑器诞生记
android·前端·css·编辑器·codebuddy首席试玩官
奔跑吧 android13 小时前
【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
android·bluetooth·bt·aosp13·storagemodule
田一一一17 小时前
Android framework 中间件开发(三)
android·中间件·framework·jni
androidwork21 小时前
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
android·kotlin