2. 日志与工具采集:adb shell dumpsys meminfo、Profiler内存快照、LeakCanary heap dump分析

现象识别

WorkManager 的 Context 泄漏通常表现为以下现象:

  • Activity 无法被 GC 回收:当 WorkManager 任务持有 Activity 引用时,退出页面后 Activity 实例仍存活。
  • 内存持续增长:重复触发 WorkManager 任务后,通过 adb shell dumpsys meminfo 观察到 Native Heap / Java Heap 线性上升。
  • LeakCanary 弹出泄漏报告:报告指向 WorkManager 的内部 Worker 或回调持有 Activity Context。

这些现象是本节工具采集的输入信号。只有确认了上述现象,才有必要进入系统性的数据采集环节。

数据采集

本环节使用三件核心工具:adb shell dumpsys meminfo(宏观内存快照)、Android Profiler 内存快照(微观对象分析)、LeakCanary heap dump(自动泄漏追踪)。三者互补,形成完整的证据链。

2.1 adb shell dumpsys meminfo

快速获取进程内存概览,重点关注 Java Heap、Native Heap、Graphics 等区域。执行命令:

adb shell dumpsys meminfo com.your.app.package -d

输出片段示例:

Applications Memory Usage (in Kilobytes):
Uptime: 12345678 Realtime: 12345678

** MEMINFO in pid 1234 [com.your.app.package] **
                   Pss  Private  Private  SwapPss      Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap    12345    12345        0        0    65536    45678    19858
  Dalvik Heap     9876     9876        0        0    32768    22345    10423
  Dalvik Other     123      123        0        0
        ...

判断规则:

  • 若每次执行 WorkManager 任务后 Dalvik Heap Alloc 不回落到基准值,很可能存在泄漏。
  • 对比泄漏前后两次 dump 的 Pss Total 差值,可估算泄漏量。

2.2 Android Profiler 内存快照

在 Android Studio 中打开 Profiler,选择 Memory 面板,点击 “Dump Java heap” 按钮生成 .hprof 文件。导入 Analysis 工具后:

  • 搜索 Activity 类名,查看实例数。
  • 查看 Reference Tree,定位 GC Root 路径。
  • 重点排查 WorkManager、Worker、ListenableFuture 相关对象。

常用过滤器:

com.your.app.MainActivity  // 检查是否有多个实例
androidx.work.impl.WorkerWrapper  // 检查 WorkManager 内部持有引用

2.3 LeakCanary heap dump 分析

LeakCanary 自动触发 Dump 并提供可视化分析。集成方式(Gradle):

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'

当泄漏发生时,通知栏会显示图标,点击即进入 LeakCanary 详情页,展示:

  • 泄漏对象:如 MainActivity
  • 引用链:如 MainActivity -> mContext -> WorkManagerImpl -> ...
  • 疑似根因:高亮持有 Context 的静态字段或长时间存活的对象。

原因定位

结合三个工具的输出,我们使用故障树(Fault Tree)来系统化排查泄漏源头。下图展示了从“Context 泄漏”到具体原因的典型路径:

WorkManager Context 泄漏故障树 Context 泄漏 dumpsys meminfo Profiler 快照 LeakCanary Dump 定位泄漏源 (持有 Context 的对象) WorkManager 内部监听 (Observer 未移除) Worker 中持有 Activity (作为成员变量) 修复:改用 Application Context

从上图可以看出,三个工具分别从宏观、微观、自动追踪角度提供证据,最终聚焦到两个主要原因:Worker 中持有 Activity 引用 或 系统回调(如监听器)未及时移除。

判断规则:多工具交叉验证

工具输出特征判定泄漏倾向
adb dumpsys meminfoDalvik Heap Alloc 不回落高
Profiler 快照Activity 实例 >1,GC Root 指向 WorkManager确定
LeakCanary Dump直接给出引用链精确定位

修复动作

根据定位结果,实施以下修复:

  1. Worker 内部不再持有 Activity Context:改用 ApplicationContext。示例:
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    private val appContext = context.applicationContext  // 正确做法
    // 不要写:private val activityContext = context as? Activity
    override fun doWork(): Result {
        // 使用 appContext 进行操作
        return Result.success()
    }
}
  1. 移除监听器:若在 WorkManager 回调中注册了 Observer,务必在 onStopped() 中解注册:
override fun onStopped() {
    super.onStopped()
    lifecycleObserver?.let { lifecycle.removeObserver(it) }
    // 或停止协程、取消 Future
}

复测

修复后需要确认泄漏是否消除:

  • 重启应用,执行同一 WorkManager 任务,等待任务结束后退出页面。
  • 使用 adb shell dumpsys meminfo 检查 Heap Alloc 是否回到修复前基准线。
  • 触发 LeakCanary 手动检测:LeakCanary.INSTANCE.dumpHeap(),若不产生报告则成功。
  • 在 Profiler 中连续执行任务 3 次,观察内存曲线是否平稳。

预防

最佳实践清单

  • 始终使用 applicationContext 而非 Activity Context 初始化 WorkManager。
  • 在 Worker 的 doWork() 中禁止保存任何 Activity 引用。
  • 集成 LeakCanary 到 debug 构建,作为 CI 冒烟测试的一部分。
  • 定期(如每次 Release 前)使用 Profiler 对核心 WorkManager 流程做一轮内存快照对比。

Tip:若项目中存在自定义 WorkerFactory,务必检查其 createWorker() 方法是否借道传入了 Activity Context。