1. Context 泄漏现象识别:WorkManager 后台任务导致 App 无法被回收
📌 现象识别:从用户感知到工具告警
Context 泄漏的识别通常经历三个递进阶段:用户端异常 → 工具告警 → 内存快照确认。以下排查表列出了各阶段的关键判断规则。
| 阶段 | 判断规则 | 典型表现 |
|---|---|---|
| 用户端异常 | App 退出后,进程仍然存活(adb shell ps 可见) | Android 12+ 的“正在运行的服务”列表中仍显示该应用 |
| 工具告警 | LeakCanary 弹出通知:“X Activity(s) retained” | 通知内容包含“com.example.MainActivity instance leaked” |
| 内存快照确认 | hprof 分析显示 Activity/Fragment 实例持有强引用链指向 WorkManager 任务 | 引用链末端为 WorkManager 的 Worker 或 ListenableWorker 对象 |
💡 快速自查命令
在 adb shell 中运行
在 adb shell 中运行
ps | grep your.package 查看进程是否存在;若 App 已退出手动滑动清除,但进程仍存活 >10 秒,高度怀疑泄漏。
🔍 数据采集:使用 LeakCanary 捕获泄漏快照
LeakCanary 2.x 自动检测泄漏并生成 .hprof 文件。采集步骤如下:
- 集成 LeakCanary(debug 版本)
// build.gradle (app)
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
- 模拟触发 WorkManager 任务:在 Activity 中启动一个后台 Worker。
val work = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(this).enqueue(work)
- 关闭 Activity(按返回键或 finish())。
- 等待 5 秒(LeakCanary 默认延迟检测)。若通知弹出,点击通知可查看泄漏引用链。
📊 关键数据点:泄漏链长度通常为 3–5 跳,例如:
MainActivity → ViewModel → WorkManager → Worker → 匿名内部类 → 外部 Context
⚙️ 原因定位:故障树分析
下图展示了从“App 无法回收”到“WorkManager 持有 Activity Context”的典型故障路径。每个节点对应一条判断规则。
🔧 修复动作(本章概述)
确认泄漏属于 WorkManager 持有 Activity Context 后,临时修复方案如下(完整修复见第 5 章、第 12 章):
- 立即止血:将 Worker 构造函数中的 Context 改为
applicationContext。 - 检查 lambda:确保 Worker 内部没有引用 Activity 或 View,必要时使用
WeakReference<Context>。
// 反例:泄漏写法
class MyWorker(private val context: Context) : Worker(context) {
override fun doWork(): Result {
// 这里隐式持有 Activity context
val activity = context as? MainActivity
activity?.showToast()
return Result.success()
}
}
// 正确写法:使用 applicationContext
class MyWorker(appContext: Context, params: WorkerParameters) : Worker(appContext, params) {
override fun doWork(): Result {
// applicationContext 不持有 Activity,安全
val sharedPref = applicationContext.getSharedPreferences("x", 0)
return Result.success()
}
}
✅ 复测:验证泄漏已消除
- 关闭所有 Activity,等待 5 秒。
- 查看 LeakCanary 通知:应无泄漏报告。
- 执行
adb shell dumpsys meminfo your.package,观察 Activities 计数是否归零。 - 强制 GC(
adb shell am force-stop your.package),再次ps,进程应消失。
⚠️ 常见误判:若 LeakCanary 泄漏链不经过 WorkManager,而是指向 ViewModel 或 Fragment 自身,请先按通用 Context 泄漏处理(参考第 3 章)。本章仅聚焦 WorkManager 引发的 Activity Context 残留。
🛡️ 预防策略(编码规范)
| 场景 | 预防规则 |
|---|---|
| Worker 初始化 | 始终使用 Worker(context, params) 而非传递 Activity Context |
| 匿名内部类 / lambda | 避免在 Worker 内部直接引用外部 View 或 Activity;若必须,使用 WeakReference |
| 任务生命周期 | 在 Activity/Fragment 的 onStop() 中取消未完成的 WorkManager 任务 |
| 代码审查 | PR 检查:Worker 中不允许出现 context as? Activity 或 context.startActivity() |