4. WorkManager 默认 Context 类型:ApplicationContext 与 ActivityContext 的区别及泄漏风险
现象识别
在 WorkManager 任务执行过程中,应用不响应、内存占用持续增长、或出现 android.view.WindowLeaked 异常,往往是 Context 泄漏的典型信号。特别是当任务中持有了 Activity 或 Fragment 的引用,而 WorkManager 的默认生命周期与 Activity 不一致时,泄漏风险成倍增加。
泄漏判断规则:如果任务结束后,通过
adb shell dumpsys meminfo <包名> 发现 Activity 对象数量未减少,或 LeakCanary 上报 Activity 实例泄漏,则强烈怀疑使用了错误的 Context 类型。数据采集
收集以下信息用于定位泄漏源头:
- WorkManager 初始化配置:检查
Configuration中是否自定义了AndroidJobFactory或WorkerFactory,以及是否将 Activity 传入。 - Worker 实现代码:查看
doWork()方法内是否持有Context引用并传递给其他组件(如Handler、BroadcastReceiver、Dialog)。 - 内存快照:通过
Android Profiler或MAT分析堆转储,查找android.app.Activity类型的泄漏链。
| 采集项 | 获取方式 | 关键判断 |
|---|---|---|
| Context 来源 | getApplicationContext() vs getBaseContext() | 是否使用 Activity 实例作为 Context |
| Worker 生命周期 | doWork() 执行前后 | 是否在 onStopped() 后仍有未释放的回调 |
| WorkManager 默认 Context | WorkManager.getInstance(context) 传入的 context | 是否为 ApplicationContext |
原因定位
WorkManager 在设计上默认使用 ApplicationContext 来创建 Worker 实例。但开发者有时会错误地传入 Activity 或 Service 的 Context,导致与 UI 生命周期绑定。下表列出两种 Context 的核心差异:
| 对比维度 | ApplicationContext | ActivityContext |
|---|---|---|
| 生命周期 | 与进程一致,直到进程被杀 | 与 Activity 一致,onDestroy 后不可用 |
| 泄漏风险 | 低(系统单例) | 高(持有 Activity 引用会阻止 GC) |
| 适用场景 | 后台任务、全局单例、数据库、SharedPreferences | UI 操作、Dialog、Toast、资源主题 |
| WorkManager 默认使用 | 是(推荐) | 否(需显式传入,不推荐) |
泄漏风险根源:当 Worker 的
doWork() 中使用 getApplicationContext() 误写成 getActivity() 或通过构造函数传入 Activity 时,即使 Worker 执行完毕,只要任务队列或回调仍持有 Context 引用,Activity 就无法被回收。下图展示了两种 Context 在 WorkManager 中的传递路径及泄漏点:
修复动作
针对不同场景,采用以下修复策略:
- 确保使用 ApplicationContext:在
Worker构造函数或doWork()中始终调用getApplicationContext()。 - 如必须使用 ActivityContext:需在
onStop()或onDestroy()中手动取消 WorkManager 任务,并释放所有引用。 - 修改 WorkManager 初始化:不要在
Application的onCreate()之外的地方初始化 WorkManager,也不要在 Activity 中调用WorkManager.initialize()。
// ✅ 正确:Worker 内使用 ApplicationContext
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val appContext = applicationContext
// 使用 appContext 进行数据库、网络等操作
return Result.success()
}
}
// ❌ 错误:将 Activity 传入 Worker
class UploadWorker(activity: Activity) : Worker(activity, WorkerParameters()) {
override fun doWork(): Result {
// 持有 activity 引用,导致泄漏
return Result.success()
}
}
修复清单:
- 检查所有 Worker 类,确保没有将 Activity、Fragment、View 作为成员变量保存。
- 使用
applicationContext替代context变量。 - 如果使用
WorkManager.getInstance(context)传入的 context 不是 ApplicationContext,立即替换为applicationContext。
复测
复测步骤应包含:
- 内存泄漏检测:运行 WorkManager 任务后,使用 LeakCanary 或
adb shell dumpsys meminfo检查 Activity 数量。 - 回归测试:执行所有涉及 WorkManager 的用例,确保功能正常且无 ANR。
- 压力测试:模拟频繁的任务入队和取消操作,观察内存曲线是否平稳。
# 检查 Activity 泄漏(包名替换为你的应用包名)
adb shell dumpsys meminfo com.example.app | grep "Activity"
预防
- 编码规范:在团队 Code Review 中明确要求 Worker 内禁止存储 Activity 引用。
- 静态检查:集成 Lint 或 Detekt 规则,检测 Context 使用不当。
- 基类封装:创建基础
BaseWorker,强制子类使用applicationContext。
// 基类封装示例
abstract class SafeWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
protected val safeContext: Context = applicationContext
}
class MyWorker(context: Context, params: WorkerParameters) : SafeWorker(context, params) {
override fun doWork(): Result {
// 直接使用 safeContext
return Result.success()
}
}