深入解析ActivityRecord Token机制:从原理到最佳实践

1次阅读
没有评论

共计 2394 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

背景痛点:Token 管理引发的典型问题

在 Android 开发中,ActivityRecord Token 是 Activity 与系统服务(如 AMS、WMS)通信的桥梁。但许多开发者对它的生命周期管理不够重视,导致以下常见问题:

深入解析 ActivityRecord Token 机制:从原理到最佳实践

  • Activity 无法正常销毁 :当 Token 被错误持有时,AMS 无法回收对应的 Activity 实例,表现为onDestroy() 被延迟调用或完全不调用

  • WindowManagerService 报错:在错误的线程使用 Token 进行跨进程调用时,会触发BadTokenException(典型错误日志:”Unable to add window — token null is not valid”)

  • 内存泄漏连锁反应:一个泄漏的 Token 会导致对应的 Activity、DecorView 乃至整个视图树无法被 GC 回收

机制解析:Token 的底层工作原理

Binder 驱动的跨进程通信

ActivityRecord Token 本质上是 Binder 代理对象,其序列化 / 反序列化过程如下:

  1. AMS 通过 ActivityRecord.appToken 生成 IBinder 对象
  2. 通过 Binder 驱动将对象引用转换为扁平化的 Parcel 数据
  3. 客户端进程接收后重建为 IApplicationToken.Stub 代理对象
// AOSP 代码片段(ActivityRecord.java)final IApplicationToken.Stub appToken = new Token();

private class Token extends IApplicationToken.Stub {// 实现 WMS 回调接口}

ActivityRecord 的映射关系

在系统服务端,Token 与组件的关系如下图所示:

[Client Process]           [System Server]
Activity  -----------------> ActivityRecord
  |                           /       \
  |-> ViewRootImpl           AMS       WMS

关键点:

  • 每个 ActivityRecord 对应一个唯一的 Token
  • ViewRootImpl 通过 setView() 将 Token 传递给 WMS
  • Token 的生命周期必须与 Activity 的 onDestroy() 同步

代码实践:安全使用 Token 的方案

正确封装 Token 的 Kotlin 示例

class SafeTokenHandler(activity: Activity) {private val weakToken = WeakReference(activity.window?.attributes?.token)

    @MainThread
    fun performWindowAction(action: (IBinder?) -> Unit) {if (Looper.myLooper() != Looper.getMainLooper()) {throw IllegalThreadStateException("Must call from UI thread")
        }
        action(weakToken.get())
    }
}

LeakCanary 定制检测方案

  1. 添加依赖:

    dependencies {debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9'}

  2. 自定义检测规则:

    class TokenLeakDetector : ObjectInspector {override fun inspect(reporter: ObjectReporter) {reporter.whenInstanceOf("android.view.ViewRootImpl") { instance ->
                val tokenField = instance.javaClass.getDeclaredField("mWindowToken")
                tokenField.isAccessible = true
                val token = tokenField.get(instance) as IBinder?
                if (token != null && token.isBinderAlive) {reporter.reportLeak("ViewRootImpl token leaked", "")
                }
            }
        }
    }

避坑指南:关键注意事项

线程安全准则

  • 永远只在 UI 线程操作 Token(可通过 @MainThread 注解强制约束)
  • 禁止在静态变量或长生命周期对象中持有原始 Token 引用

Configuration 变更处理

当设备旋转等操作触发配置变更时:

  1. 旧 Activity 的 Token 会随 onDestroy() 被回收
  2. 新 Activity 实例会生成新 Token
  3. 需要重写 onSaveInstanceState() 转移必要数据
override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)
    // 保存与窗口无关的临时状态
    outState.putString("KEY_DATA", transientData)
}

进阶思考:Compose 时代的 Token 机制

在 Jetpack Compose 架构下:

  • 传统 WindowToken 的作用被弱化
  • LocalView.current提供了替代访问方式
  • 但底层仍然依赖 Token 进行窗口管理

未来可能的演进方向:

  1. 窗口令牌与组合树的深度集成
  2. 声明式 API 替代显式 Token 传递
  3. 更严格的线程访问限制

总结

理解 ActivityRecord Token 机制需要把握三个维度:

  1. 跨进程通信:作为 Binder 代理的本质
  2. 生命周期同步:与 Activity 销毁的强关联
  3. 线程约束:严格的 UI 线程访问要求

通过弱引用封装、LeakCanary 监控和正确的线程管理,可以避免 90% 以上的 Token 相关问题。在新技术栈中,虽然使用方式可能变化,但核心的进程间通信原理仍然适用。

正文完
 0
评论(没有评论)