共计 2411 个字符,预计需要花费 7 分钟才能阅读完成。
核心概念:Activity Token 的本质与作用
Activity Token 是 Android 系统为每个 Activity 实例分配的唯一标识符,本质上是一个 Binder 对象的引用。它在窗口管理系统中扮演着以下关键角色:

- 身份凭证 :向 WindowManagerService(WMS) 证明 Activity 的合法性,类似 ” 窗口身份证 ”
- 通信桥梁:作为 Activity 与 WMS 跨进程通信的媒介
- 生命周期纽带:关联 Activity 与对应的窗口 Surface
官方文档中将其描述为 IBinder 类型,通过 Activity.getActivityToken() 可获取(API 24+)。注意:Application Context 没有 Token,这是引发许多问题的根源。
痛点分析:Token 相关崩溃场景
1. BadTokenException 经典案例
// 错误示例:在非 Activity Context 中使用窗口相关操作
new AlertDialog.Builder(getApplicationContext()).show();
// 抛出:BadTokenException: Unable to add window -- token null is not valid
根本原因:
– Application Context 没有关联的 Window Token
– 对话框 /Toast 等 UI 组件必须依附 Activity 的窗口层级
2. Token 泄露导致的内存问题
- 持有 Activity 的 Token 引用会阻止 GC 回收(如静态存储 Dialog 实例)
- 表现为内存泄漏后再次使用失效 Token
3. 异步操作中的 Token 失效
// 在协程中延迟显示 Toast
lifecycleScope.launch {delay(5000)
Toast.makeText(this@Activity, "Hi", Toast.LENGTH_SHORT).show()
// 可能崩溃:Activity 已销毁但 Token 仍被使用
}
技术方案:正确管理 Token 的实践
1. Context 选择原则
- 必须使用 Activity Context 的场景:
- 创建对话框(AlertDialog/ProgressDialog)
- 显示 Toast(除非自定义视图)
-
PopupWindow 显示
-
可使用 Application Context 的场景:
- 启动 Service
- 获取系统服务
- 不涉及窗口的操作
2. 防御性编程示例
// 安全显示 Dialog 方案
fun showDialogSafely(activity: Activity) {if (activity.isFinishing || activity.isDestroyed) return
AlertDialog.Builder(activity)
.setTitle("Tips")
.setPositiveButton("OK") {_, _ ->}
.show()}
// 协程环境安全示例
lifecycleScope.launchWhenResumed {if (!isActive) return@launchWhenResumed
Toast.makeText(this@Activity, "Safe!", Toast.LENGTH_SHORT).show()}
代码实验室:关键场景解决方案
案例 1:正确处理 Application Context 需求
// 需要 Context 但不确定是否 Activity 时
public static void safeStartActivity(Context context, Intent intent) {if (context instanceof Activity) {context.startActivity(intent);
} else {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
案例 2:View.post()的 Token 陷阱
// 错误做法:可能发生在 Activity 销毁后
view.post {Toast.makeText(context, "Loaded", Toast.LENGTH_SHORT).show() // 风险!}
// 正确做法:view.post {if (view.context is Activity) {
val activity = view.context as Activity
if (!activity.isDestroyed) {Toast.makeText(activity, "Safe", Toast.LENGTH_SHORT).show()}
}
}
性能与安全进阶
1. Token 与内存泄漏
- 检测工具:使用 Android Profiler 观察 Activity 实例
- 典型陷阱:
- 静态持有 Dialog 实例
- 单例中存储 Context
- Handler 延迟消息未清理
2. 多进程场景
- 每个进程有独立的 Window Token 体系
- 跨进程使用 Token 需要显式传递(参考
ActivityOptions.makeBasic())
开发者避坑指南
- Context 选择三原则:
- UI 操作必用 Activity Context
- 生命周期无关用 Application Context
-
不确定时先判空再类型检查
-
异步操作四步检查:
- isFinishing
- isDestroyed
- isChangingConfigurations
-
协程用 lifecycleScope
-
内存管理要点:
- 避免静态持有 View/Dialog
- 及时取消异步任务
- 使用 WeakReference 传递 Context
延伸思考
- 为什么 Android 不允许用 Application Context 显示对话框?从系统安全角度思考
- 如何设计一个全局的 Toast 工具类,既能避免 Context 问题又能统一样式?
- 当遇到 Token 相关崩溃时,你的排查步骤是怎样的?
欢迎在评论区分享你遇到的 Token 相关问题和解决经验,共同完善 Android 开发的稳定性实践。
正文完
