共计 2738 个字符,预计需要花费 7 分钟才能阅读完成。
异常背景与常见场景
在 Android 开发中,WindowManager$BadTokenException异常通常会在尝试显示对话框、弹出窗口或添加系统窗口时抛出。错误信息一般为:android.view.WindowManager$BadTokenException: Unable to add window -- token。这个异常虽然不复杂,但如果不了解其根本原因,很容易让开发者陷入反复调试的困境。

常见触发场景包括:
- 在非 Activity 的 Context(如 Application Context)上显示对话框
- 在 Activity 已经 finish 后尝试显示对话框
- 在异步回调中显示 UI 时未检查 Activity 状态
- 使用无效的 Window token 添加系统窗口
异常根本原因分析
这个异常的核心原因是:窗口需要一个有效的窗口 token,而这个 token 只能由 Activity 提供。当出现以下情况时,token 会失效:
- 使用了错误的 Context 类型:Application Context 没有关联的窗口 token
- Activity 生命周期问题 :Activity 已经 finish() 但仍在尝试显示 UI
- 异步操作时序问题:在回调中显示 UI 时,Activity 可能已经不可见
- 系统窗口权限问题:尝试添加系统窗口但没有正确权限
多种解决方案对比
1. 使用 Activity Context
这是最基本也是最有效的解决方案。确保所有需要显示窗口的操作都使用 Activity Context,而不是 Application Context。
// 正确做法
AlertDialog.Builder(this@MyActivity) // 使用 Activity Context
.setTitle("提示")
.setMessage("这是一个对话框")
.show()
2. 检查 Activity 状态
在显示对话框前,检查 Activity 是否正在 finish:
if (!isFinishing && !isDestroyed) {
// 安全显示对话框
dialog.show()}
3. 延迟显示机制
对于可能在 Activity 销毁后触发的回调,可以使用 Handler 延迟显示:
Handler(Looper.getMainLooper()).post {if (!isFinishing) {dialog.show()
}
}
4. 使用 DialogFragment
DialogFragment 能更好地处理生命周期问题,是官方推荐的方式:
class MyDialogFragment : DialogFragment() {override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {return AlertDialog.Builder(requireActivity())
.setTitle("提示")
.setMessage("使用 DialogFragment 更安全")
.create()}
}
// 显示 DialogFragment
MyDialogFragment().show(supportFragmentManager, "dialog_tag")
完整代码示例
以下是结合多种解决方案的最佳实践示例:
class SafeDialogHelper(private val activity: Activity) {
private var dialog: AlertDialog? = null
fun showSafeDialog() {
// 确保在主线程执行
if (Looper.myLooper() != Looper.getMainLooper()) {activity.runOnUiThread { showSafeDialog() }
return
}
// 检查 Activity 状态
if (activity.isFinishing || activity.isDestroyed) {return}
// 避免重复创建
if (dialog?.isShowing == true) {return}
// 创建并显示对话框
dialog = AlertDialog.Builder(activity)
.setTitle("安全对话框")
.setMessage("这是一个考虑了各种边界条件的对话框")
.setOnDismissListener {dialog = null}
.create()
try {dialog?.show()
} catch (e: WindowManager.BadTokenException) {
// 异常处理
Log.e("SafeDialogHelper", "Failed to show dialog", e)
dialog = null
}
}
fun dismissSafeDialog() {if (dialog?.isShowing == true && !activity.isDestroyed) {dialog?.dismiss()
}
dialog = null
}
}
性能与稳定性考量
- 内存泄漏预防:
- 避免在对话框内部持有 Activity 的强引用
-
在 Activity 销毁时取消所有待显示的对话框
-
生命周期同步:
- 在 onPause()中考虑是否需要隐藏对话框
-
在 onDestroy()中确保释放所有对话框资源
-
线程安全:
- 所有 UI 操作必须在主线程执行
- 使用 runOnUiThread 或 Handler 确保线程安全
避坑指南:常见错误及修复方法
-
错误:使用 Application Context 显示对话框
// 错误示例 AlertDialog.Builder(applicationContext).show()修复:始终使用 Activity Context
-
错误:未检查 Activity 状态
// 在异步回调中直接显示 networkCallback { response -> dialog.show() // 可能 Activity 已经销毁}修复:添加状态检查或使用 View.post()
-
错误:忽略异常捕获
try {dialog.show() } catch (e: WindowManager.BadTokenException) {// 未做任何处理}修复:至少记录日志,最好有恢复机制
总结与思考
WindowManager$BadTokenException虽然常见,但只要理解了其背后的机制,完全可以避免。关键在于:
- 正确使用 Context 类型
- 严格管理生命周期
- 处理好异步操作的时序问题
- 采用更安全的 DialogFragment
在实际项目中,建议建立一个统一的对话框工具类,封装这些安全逻辑。同时,在代码审查时,应该特别注意任何直接使用 Application Context 显示 UI 的情况。
你是怎么处理这类问题的?有没有遇到过特别棘手的场景?欢迎分享你的实践经验,我们一起探讨更优的解决方案。
