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()为空,所以导致空指针。

相关推荐
JarvanMo42 分钟前
flutter工程化之动态配置
android·flutter·ios
时光少年3 小时前
Android 副屏录制方案
android·前端
时光少年3 小时前
Android 局域网NIO案例实践
android·前端
alexhilton4 小时前
Jetpack Compose的性能优化建议
android·kotlin·android jetpack
流浪汉kylin4 小时前
Android TextView SpannableString 如何插入自定义View
android
火柴就是我5 小时前
git rebase -i,执行 squash 操作 进行提交合并
android
你说你说你来说6 小时前
安卓广播接收器(Broadcast Receiver)的介绍与使用
android·笔记
你说你说你来说6 小时前
安卓Content Provider介绍及使用
android·笔记
RichardLai886 小时前
[Flutter学习之Dart基础] - 类
android·flutter
_一条咸鱼_7 小时前
深度解析 Android MVI 架构原理
android·面试·kotlin