共计 3147 个字符,预计需要花费 8 分钟才能阅读完成。
理解 WindowToken 与窗口系统
在 Android 系统中,每个窗口(Window)都必须关联一个 WindowToken,这是窗口管理系统(WindowManagerService)识别窗口归属的重要标识。WindowToken 本质上是一个 Binder 对象,主要作用包括:

- 标识窗口所属的应用程序
- 管理窗口的 Z -order(显示层级)
- 控制窗口的生命周期
当系统抛出 android.view.WindowManager$BadTokenException: Unable to add window -- token 异常时,意味着当前尝试添加的窗口无法找到有效的 WindowToken。这通常发生在以下两种情况下:
- 使用的 Context 已经失效(如 Activity 已销毁)
- 缺少必要的窗口权限(如 SYSTEM_ALERT_WINDOW)
典型异常场景分析
1. Activity 已销毁但仍在操作 UI
这是最常见的触发场景。当 Activity 执行 onDestroy() 后,其关联的 WindowToken 会被销毁。此时如果仍尝试执行如下操作就会触发异常:
// 错误示例:在 Activity 销毁后仍尝试显示 Dialog
class MyActivity : AppCompatActivity() {
private lateinit var dialog: Dialog
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
dialog = Dialog(this).apply {setContentView(R.layout.my_dialog)
}
// 模拟异步操作后显示对话框
lifecycleScope.launch {delay(5000)
dialog.show() // 如果 Activity 已销毁,这里会抛出 BadTokenException}
}
}
2. 使用 Application Context 显示系统窗口
尝试使用 Application Context 来显示需要附加到 Activity 的窗口时:
// 错误示例:使用 Application Context 显示 Toast
Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_SHORT).show();
3. 缺少 SYSTEM_ALERT_WINDOW 权限
当尝试显示系统级窗口(如悬浮窗)但未声明权限时:
<!-- 必须先在 AndroidManifest.xml 中声明权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
解决方案与最佳实践
方案 1:检查 Activity 状态
在显示任何窗口前,都应该检查宿主 Activity 是否仍有效:
fun showDialogSafely(activity: Activity) {if (!activity.isFinishing && !activity.isDestroyed) {Dialog(activity).apply {setContentView(R.layout.safe_dialog)
show()}
} else {Log.w("WindowManager", "Activity is destroyed, skip showing dialog")
}
}
方案 2:正确处理生命周期
结合 Lifecycle 组件确保窗口操作在正确的生命周期状态下执行:
class LifecycleAwareDialog(private val context: Context) : DefaultLifecycleObserver {
private var dialog: Dialog? = null
init {if (context is LifecycleOwner) {context.lifecycle.addObserver(this)
}
}
fun show() {dialog = Dialog(context).apply {setContentView(R.layout.lifecycle_dialog)
show()}
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
override fun onDestroy(owner: LifecycleOwner) {dialog?.dismiss()
dialog = null
}
}
方案 3:Context 使用策略
根据不同场景选择合适的 Context:
| 窗口类型 | 推荐 Context | 注意事项 |
|---|---|---|
| Activity 附属对话框 | Activity Context | 必须检查 Activity 状态 |
| 系统 Toast | Application Context | API 25+ 建议使用 getApplication() |
| 系统悬浮窗 | 任何 Context | 需动态请求 SYSTEM_ALERT_WINDOW 权限 |
生产环境避坑指南
-
异步操作风险:所有可能跨越生命周期的异步操作(如网络请求、数据库查询)完成后,必须检查 Context 有效性
-
内存泄漏预防:
- 始终在
onDestroy()中释放窗口资源 -
避免在单例或静态对象中持有 Activity 引用
-
多进程注意事项:
- 跨进程显示窗口时需确保目标进程存活
-
使用
Binder.isBinderAlive()检查跨进程 token 有效性 -
兼容性问题:
- API 26+ 对悬浮窗有更严格的限制
- 某些厂商 ROM 会修改窗口管理策略
架构设计建议
在 MVVM/MVI 架构中,建议采用以下模式管理窗口:
- 将窗口操作封装在 View 层
- 通过 LiveData/StateFlow 驱动界面变化
- 示例架构:
class MyViewModel : ViewModel() {private val _dialogEvent = MutableSharedFlow<DialogEvent>()
val dialogEvent: SharedFlow<DialogEvent> = _dialogEvent
fun showSpecialDialog() {
viewModelScope.launch {_dialogEvent.emit(DialogEvent.Show("special"))
}
}
}
class MyActivity : AppCompatActivity() {private val viewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
lifecycleScope.launch {
viewModel.dialogEvent.collect { event ->
when (event) {is DialogEvent.Show -> showDialogSafely(event.type)
}
}
}
}
private fun showDialogSafely(type: String) {if (!isDestroyed) {// 安全显示对话框}
}
}
总结与思考
BadTokenException本质上是 Android 窗口系统的安全机制,强制开发者正确处理 Context 生命周期。通过本文介绍的各种解决方案,开发者可以:
- 从根本上理解 WindowToken 的工作机制
- 掌握不同场景下的正确 Context 使用策略
- 建立生命周期敏感的窗口管理意识
在更复杂的应用场景中,建议考虑:
- 实现统一的 WindowManager 代理层
- 结合 DI 框架管理 Context 依赖
- 开发阶段使用 StrictMode 检测潜在问题
希望本文能帮助开发者构建更稳定的 Android 窗口交互体验。
