泄漏根源定位流程:从 WorkManager.Worker.onStop 到 Context 引用链追踪

当 Worker.onStop() 被系统调度后,如果应用仍持有 Activity 或 Fragment 的 Context 引用,便构成泄漏根源。本章按照“现象识别 → 数据采集 → 原因定位 → 修复动作 → 复测 → 预防”的排查通路,精确追踪引用链。

1. 现象识别:onStop 后内存未释放

通过 LeakCanary 或 Android Profiler 观察以下典型泄漏迹象:

  • 泄漏堆栈顶部:始终指向 Worker.onStop() 调用栈中的匿名内部类或 lambda。
  • GC Root 路径:Worker 对象通过 mContext 字段 -> Activity -> Window -> DecorView 形成不可达链。
  • 手动触发 GC:onStop() 执行完毕后,Activity 实例仍存活在堆中。
快捷判断规则:
若 onStop() 内调用了 getApplicationContext() 之外的 Context 方法(如 getBaseContext()),或直接持有 activity.this,即视为高风险代码。

2. 数据采集:获取堆转储与引用链

使用 Android Studio Memory Profiler 或 LeakCanary 的 heap dump,导出 .hprof 文件。在 MAT 或 Android Studio 中搜索 Worker 子类实例,查看其 GC Root 到对象的引用路径。

采集工具 关键操作 输出内容
LeakCanary 3.x 自动检测 + 堆转储 引用链路径、泄漏对象数量
Android Memory Profiler 手动触发 GC + Dump Java Heap 对象实例列表、引用树
Eclipse MAT 加载 .hprof,执行 OQL GC Root 路径、最短引用链

典型的 OQL 查询语句:

SELECT * FROM com.example.MyWorker WHERE retainedHeapSize > 0

3. 原因定位:引用链追踪

将堆转储中的引用链映射为逻辑模型,常见的泄漏路径如下:

WorkManager Context 泄漏引用链追踪 泄漏引用链追踪图 Worker.onStop() mContext(Activity) Activity.this 生命周期管理 WeakReference ApplicationContext 关键根因:onStop 中未主动释放对 Activity Context 的强引用 追踪路径:Worker → mContext → Activity → Window → DecorView(不可回收)
常见误解:即使 Worker 在 onStop 中不再使用 Context,只要字段仍持有引用,GC 就不会回收 Activity。LeakCanary 的引用链会显示 mContext 为 GC Root 的最近跳板。

4. 修复动作:切断引用链

根据定位到的引用类型,采用三类修复手段:

  • 弱引用 + 生命周期回调:在 onStop() 中清空 Context 引用,或使用 WeakReference。
  • ApplicationContext 替换:仅使用 getApplicationContext() 完成非 UI 操作。
  • 显式释放:在 onStop() 末尾将 mContext 置为 null。

代码示例(修复前后的对比):

// ❌ 泄漏代码
public class MyWorker extends Worker {
    private Context mContext;
    public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
        this.mContext = context;  // 持有 Activity Context
    }
    @NonNull
    @Override
    public Result doWork() {
        // ... 执行任务
        return Result.success();
    }
    @Override
    public void onStop() {
        super.onStop();
        // 未释放 mContext
    }
}

// ✅ 修复代码
public class MyWorker extends Worker {
    private final WeakReference<Context> contextRef;
    public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
        this.contextRef = new WeakReference<>(context);  // 弱引用
    }
    @NonNull
    @Override
    public Result doWork() {
        Context ctx = contextRef.get();
        if (ctx == null) return Result.failure();
        // ... 安全使用 ctx
        return Result.success();
    }
    @Override
    public void onStop() {
        contextRef.clear();  // 主动清空引用
        super.onStop();
    }
}

5. 复测:验证泄漏是否消除

执行以下复测步骤:

  1. 在模拟器或真机上触发 Worker 的 onStop() 场景(如快速切换应用、关闭 Activity)。
  2. 使用 LeakCanary 观测泄漏通知:若修复正确,泄漏报告不再出现。
  3. 手动触发 GC 后,通过 Profiler 确认 Activity 实例已回收。
复测禁忌:不要在代码中添加 Thread.sleep 来等待 GC,应使用 Android Profiler 的“Force GC”按钮确保客观。

6. 预防:在开发流程中建立屏障

将以下规则纳入工程规范与 CI 检查:

检查点 规则描述 强制等级
Worker 构造方法 禁止将参数 Context 直接赋值给成员变量(除非使用 WeakReference) Error(Lint)
onStop() 方法 所有 Context 引用必须在方法返回前清空(置 null 或 clear) Warning(Code Review)
LeakCanary 集成 所有 Debug 构建必须集成 LeakCanary,且触发泄漏则 CI 失败 Error(CI Pipeline)

最终,通过“现象 → 数据 → 定位 → 修复 → 复测 → 预防”的闭环,将 WorkManager Context 泄漏的根源彻底切断。