Android Activity中的Token机制解析与内存泄漏避坑指南

1次阅读
没有评论

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

image.webp

核心概念

  1. 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)。

    Android Activity 中的 Token 机制解析与内存泄漏避坑指南

  2. 跨进程传递流程

  3. AMS 通过 Binder 驱动将 Token 传递给客户端进程
  4. ActivityThread 通过 attachApplication() 完成绑定(见 ActivityThread.java 的 handleBindApplication())
  5. 关键代码段(Android 10):

    // ActivityThread.java
    final void handleBindApplication(...) {
        // 建立 AMS 与 Activity 的关联
        app.bindApplication(...);
        mActivities.put(token, activityClient);
    }

  6. Token 的双向绑定

  7. AMS 端:通过 ActivityRecord 维护 Token 到 Activity 的映射
  8. Client 端:ActivityThread 的 mActivities 集合存储 Token 对应关系

痛点分析

  1. 典型泄漏场景
  2. Dialog 未关闭时持有了 Activity 的 Token(WindowManagerGlobal.sViews 集合)
  3. 输入法服务通过 Token 强引用 Activity

  4. GCRoot 链分析

    GCRoot → WindowManagerService → Token → Activity 实例 

    MAT 工具中表现为:

  5. dominator_tree 显示 Activity 被 BinderProxy 持有
  6. Path to GC Roots 显示跨越进程的引用链

  7. SurfaceFlinger 特例
    图形合成器进程会通过 SurfaceSession 持有 Token,即使 Activity 销毁仍可能保持引用(需额外处理)

解决方案

  1. 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)
        }
    }
  2. 注意:必须处理 get() 返回 null 的情况

  3. 主动解绑方案

    // BaseActivity.java
    @Override
    protected void onDestroy() {if (!isChangingConfigurations()) {windowManager.removeViewImmediate(decorView);
        }
        super.onDestroy();}

  4. 需区分配置变更和真实销毁场景

  5. 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();}
    }

避坑指南

  1. 自动化检测工具

    class TokenLeakDetector(private val handler: Handler) {fun watch(activity: Activity) {
            handler.postDelayed({if (activity.isDestroyed && !activity.isChangingConfigurations) {reportLeak(activity)
                }
            }, 5000) // 延迟 5 秒检测
        }
    }

  2. 性能数据
    | 方案 | 启动耗时增加 | 内存开销 |
    |—————|————–|———-|
    | WeakReference | <1ms | 低 |
    | 主动解绑 | 2-5ms | 无 |

  3. 厂商适配

  4. 部分 ROM 会修改 Token 回收逻辑(如 MIUI 的 MemoryOptimizer)
  5. 需要单独测试 onDestroy() 后 Token 是否及时释放

延伸思考

  1. 设计哲学问题
    Google 未直接使用 WeakReference 的原因:
  2. 保证跨进程通信的可靠性
  3. 避免频繁 Binder 重建带来的性能损耗

  4. 进阶改造建议
    通过 ASM 修改 ActivityThread.H:

    // 在 handleDestroyActivity 注入检测代码
    MethodVisitor mv = ...
    mv.visitMethodInsn(
        INVOKESTATIC, 
        "com/example/TokenGuard", 
        "checkLeak", 
        "(Landroid/os/IBinder;)V"
    );

资源链接

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