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 泄漏”到具体原因的典型路径:
从上图可以看出,三个工具分别从宏观、微观、自动追踪角度提供证据,最终聚焦到两个主要原因:Worker 中持有 Activity 引用 或 系统回调(如监听器)未及时移除。
判断规则:多工具交叉验证
| 工具 | 输出特征 | 判定泄漏倾向 |
|---|---|---|
| adb dumpsys meminfo | Dalvik Heap Alloc 不回落 | 高 |
| Profiler 快照 | Activity 实例 >1,GC Root 指向 WorkManager | 确定 |
| LeakCanary Dump | 直接给出引用链 | 精确定位 |
修复动作
根据定位结果,实施以下修复:
- 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()
}
}
- 移除监听器:若在 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。