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 中运行 ps | grep your.package 查看进程是否存在;若 App 已退出手动滑动清除,但进程仍存活 >10 秒,高度怀疑泄漏。

🔍 数据采集:使用 LeakCanary 捕获泄漏快照

LeakCanary 2.x 自动检测泄漏并生成 .hprof 文件。采集步骤如下:

  1. 集成 LeakCanary(debug 版本)
// build.gradle (app)
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
  1. 模拟触发 WorkManager 任务:在 Activity 中启动一个后台 Worker。
val work = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(this).enqueue(work)
  1. 关闭 Activity(按返回键或 finish())。
  2. 等待 5 秒(LeakCanary 默认延迟检测)。若通知弹出,点击通知可查看泄漏引用链。
📊 关键数据点:泄漏链长度通常为 3–5 跳,例如:
MainActivity → ViewModel → WorkManager → Worker → 匿名内部类 → 外部 Context

⚙️ 原因定位:故障树分析

下图展示了从“App 无法回收”到“WorkManager 持有 Activity Context”的典型故障路径。每个节点对应一条判断规则。

WorkManager Context 泄漏故障树 App 进程无法回收 LeakCanary 报告 Activity 泄漏 hprof 引用链指向 WorkManager Worker Worker 内部匿名类 / lambda 持有外部 Context Worker 构造函数或 doWork() 传入了 Activity Context ✅ 判断规则: 1. 仅当 LeakCanary 确认泄漏且引用链末端是 Worker 时,才属于 WorkManager 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()
    }
}

✅ 复测:验证泄漏已消除

  1. 关闭所有 Activity,等待 5 秒。
  2. 查看 LeakCanary 通知:应无泄漏报告。
  3. 执行 adb shell dumpsys meminfo your.package,观察 Activities 计数是否归零。
  4. 强制 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()