共计 2316 个字符,预计需要花费 6 分钟才能阅读完成。
核心概念
-
Binder IPC 与 Token 本质
Android 的 ActivityRecord.Token(令牌)本质是 Binder 通信的句柄。当 AMS(ActivityManagerService)需要跨进程控制 Activity 时,会通过这个 Token 标识目标 Activity。在 Android 10 源码中可以看到,Token 继承自 IApplicationToken.Stub,是一个 Binder 对象(frameworks/base/core/java/android/view/IApplicationToken.aidl)。
-
跨进程传递流程
- AMS 通过 Binder 驱动将 Token 传递给客户端进程
- ActivityThread 通过 attachApplication() 完成绑定(见 ActivityThread.java 的 handleBindApplication())
-
关键代码段(Android 10):
// ActivityThread.java final void handleBindApplication(...) { // 建立 AMS 与 Activity 的关联 app.bindApplication(...); mActivities.put(token, activityClient); } -
Token 的双向绑定
- AMS 端:通过 ActivityRecord 维护 Token 到 Activity 的映射
- Client 端:ActivityThread 的 mActivities 集合存储 Token 对应关系
痛点分析
- 典型泄漏场景
- Dialog 未关闭时持有了 Activity 的 Token(WindowManagerGlobal.sViews 集合)
-
输入法服务通过 Token 强引用 Activity
-
GCRoot 链分析
GCRoot → WindowManagerService → Token → Activity 实例MAT 工具中表现为:
- dominator_tree 显示 Activity 被 BinderProxy 持有
-
Path to GC Roots 显示跨越进程的引用链
-
SurfaceFlinger 特例
图形合成器进程会通过 SurfaceSession 持有 Token,即使 Activity 销毁仍可能保持引用(需额外处理)
解决方案
- WeakReference 方案(推荐)
class SafeDialogContext(private val activityRef: WeakReference<Activity>) : ContextWrapper(activityRef.get()!!) {override fun getSystemService(name: String): Any? {val activity = activityRef.get() ?: return null return activity.getSystemService(name) } } -
注意:必须处理 get() 返回 null 的情况
-
主动解绑方案
// BaseActivity.java @Override protected void onDestroy() {if (!isChangingConfigurations()) {windowManager.removeViewImmediate(decorView); } super.onDestroy();} -
需区分配置变更和真实销毁场景
-
AOP 拦截方案
@Aspect public class TokenAspect {@Around("execution(* android.app.Activity.onDestroy(..))") public void checkTokenLeak(ProceedingJoinPoint joinPoint) {Activity activity = (Activity) joinPoint.getThis(); if (activity.isFinishing()) {WindowManager wm = activity.getWindowManager(); // 反射检查 mViews 集合 } joinPoint.proceed();} }
避坑指南
-
自动化检测工具
class TokenLeakDetector(private val handler: Handler) {fun watch(activity: Activity) { handler.postDelayed({if (activity.isDestroyed && !activity.isChangingConfigurations) {reportLeak(activity) } }, 5000) // 延迟 5 秒检测 } } -
性能数据
| 方案 | 启动耗时增加 | 内存开销 |
|—————|————–|———-|
| WeakReference | <1ms | 低 |
| 主动解绑 | 2-5ms | 无 | -
厂商适配
- 部分 ROM 会修改 Token 回收逻辑(如 MIUI 的 MemoryOptimizer)
- 需要单独测试 onDestroy() 后 Token 是否及时释放
延伸思考
- 设计哲学问题
Google 未直接使用 WeakReference 的原因: - 保证跨进程通信的可靠性
-
避免频繁 Binder 重建带来的性能损耗
-
进阶改造建议
通过 ASM 修改 ActivityThread.H:// 在 handleDestroyActivity 注入检测代码 MethodVisitor mv = ... mv.visitMethodInsn( INVOKESTATIC, "com/example/TokenGuard", "checkLeak", "(Landroid/os/IBinder;)V" );

