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
代码的作用是 延迟处理焦点丢失事件,避免窗口状态冲突。具体来说:
关键作用说明
-
防竞态条件(Race Condition)
- 当窗口焦点快速切换时(如用户快速点击其他窗口),立即调用
dismiss()
可能导致:- 窗口尚未完成焦点丢失流程
- 视图树处于中间状态
- 延迟 200ms 确保系统完成焦点变更的完整周期
- 当窗口焦点快速切换时(如用户快速点击其他窗口),立即调用
-
规避异步操作残留
- 在
onWindowFocusChanged
中直接操作,可能与其他异步任务(如动画、布局计算)产生冲突 - 延迟到主线程消息队列尾部执行,保证操作顺序性
- 在
-
视图状态验证窗口期
- 200ms 的延迟为视图系统提供缓冲时间,可在此期间:
- 检测是否焦点重新获得(如用户快速切回)
- 验证
window.decorView
是否仍有效
- 200ms 的延迟为视图系统提供缓冲时间,可在此期间:
参数选择依据
延迟时间 | 适用场景 | 风险 |
---|---|---|
200ms | 通用焦点切换场景 | 较平衡用户体验与稳定性 |
更短 (50-100ms) | 对响应速度敏感场景 | 可能残留竞态条件 |
更长 (300-500ms) | 复杂窗口系统(如 TV 大屏) | 用户感知延迟 |
典型应用场景示例
-
全局悬浮窗失焦关闭
当用户点击其他应用窗口时,延迟确保悬浮窗关闭操作不会与系统焦点管理冲突
-
游戏悬浮工具栏
kotlin// 在游戏暂停菜单中 override fun onPause() { window.decorView.postDelayed(::hideToolbar, 200) }
-
TV Launcher 焦点管理
kotlin// 处理遥控器方向键快速导航 fun onFocusChange() { postDelayed({ updateFocusHighlight() }, 200) }
我来讲两句
上面的代码中,postDelayed
的作用是延迟处理焦点丢失事件,避免窗口状态冲突,但AI回答的并不是准确原因。先看源码
-
首先是
ViewRootImpl.java
javaprivate 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()
-
再看
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。
-
结合上面两块代码,我们已经可以知道,我在dialog的
onWindowFocusChanged
回调直接调用了dismiss()
,后面viewRootImpl的mView.findFocus()
为空,所以导致空指针。