第一章:WorkManager 简介与核心概念
1.1 什么是 WorkManager
WorkManager 是 Android Jetpack 提供的后台任务调度库,用于执行可延迟、需保证完成的后台工作。它根据系统版本、设备状态和应用生命周期自动选择底层实现(JobScheduler / AlarmManager + BroadcastReceiver),开发者无需关心兼容性问题。
| 特性 | 说明 |
|---|---|
| 最低 API | API 14+(带 Google Play 服务),API 23+(原生) |
| 任务保证 | 即使应用被关闭或设备重启,任务仍会执行(使用 Room 持久化状态) |
| 生命周期感知 | 自动感知 Fragment/Activity/Service 生命周期 |
| 约束条件 | 支持网络、电量、存储、空闲等运行时约束 |
| 协程集成 | 通过 CoroutineWorker 天然支持 Kotlin 协程 |
1.2 调度原理:底层分发策略
WorkManager 的核心调度逻辑由 WorkManagerImpl 驱动,决策流程如下:
- 开发者创建
OneTimeWorkRequest或PeriodicWorkRequest,并通过WorkManager.enqueue()提交。 - 请求进入 WorkManager 内部数据库(Room),状态设为
ENQUEUED。 - 调度器 Scheduler 根据当前设备 API 选择合适实现:
- API 23+:使用
JobSchedulerScheduler,将任务委托给 JobScheduler。 - API 14~22:使用
AlarmManagerScheduler+BroadcastReceiver组合。 - 同时支持 GreedyScheduler(贪婪调度器),在应用进程存活时立即尝试执行任务,延迟低于系统级调度。
- 任务执行完成后,状态更新为
SUCCEEDED、FAILED或CANCELLED。
关键点:WorkManager 并不保证实时性,它保证的是“最终一定会执行”(Best Effort + 保证完成)。协程结合时,CoroutineWorker 会在后台协程作用域内挂起,直到任务完成或取消。
1.3 工作约束(Constraints)
WorkManager 允许在任务上绑定 约束条件,只有满足约束时任务才会执行。常用约束列表:
| 约束类型 | API 方法 | 说明 |
|---|---|---|
| 网络类型 | setRequiredNetworkType(NetworkType) | UNMETERED / CONNECTED / NOT_ROAMING / METERED 等 |
| 电量充足 | setRequiresCharging(true) | 设备正在充电时才执行 |
| 设备空闲 | setRequiresDeviceIdle(true) | 设备处于空闲(Doze mode)状态 |
| 存储空间 | setRequiresStorageNotLow(true) | 存储空间不低 |
| 电池电量 | setRequiresBatteryNotLow(true) | 电池电量不低(API 26+) |
| 内容触发器 | setContentUriTriggers(...) | 特定 ContentUri 发生变化时触发(API 24+) |
最佳实践:约束条件越多,任务延迟执行的概率越大。在协程结合场景中,建议仅在必要条件下添加约束,避免任务永久挂起。同时配合 WorkRequest.addTag() 便于批量观察和管理。
1.4 工作状态(Work States)
每个 WorkRequest 在生命周期内经历以下状态(存储在 Room 数据库中):
| 状态 | 枚举值 | 含义 |
|---|---|---|
| ENQUEUED | State.ENQUEUED | 任务已入队,等待满足约束并开始执行 |
| RUNNING | State.RUNNING | 任务正在执行(Worker 的 doWork 方法正在运行) |
| SUCCEEDED | State.SUCCEEDED | 任务成功完成(返回 Result.success()) |
| FAILED | State.FAILED | 任务失败(返回 Result.failure());仅一次性任务最终状态 |
| CANCELLED | State.CANCELLED | 任务被取消(调用 cancelWorkById 或 cancelAllWorkByTag) |
| BLOCKED | State.BLOCKED | 任务被前置依赖任务阻塞(已弃用,API 30+ 统一用 ENQUEUED 表示) |
1.5 与协程结合的基础
使用 CoroutineWorker 代替 Worker,可以在 doWork() 中直接调用挂起函数:
class UploadWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// 在这里可以调用挂起函数(如网络请求、数据库操作)
val imageData = getImageDataFromDatabase()
val response = apiService.upload(imageData)
return if (response.isSuccessful) {
Result.success()
} else {
Result.retry()
}
}
}
注意:CoroutineWorker 的 doWork() 运行在默认的 Dispatchers.Default 协程上下文。如果需要主线程操作,应使用 withContext(Dispatchers.Main) 切换。不要直接使用 GlobalScope 或手动创建协程,否则可能导致生命周期泄漏或任务提前结束。
1.6 常见错误与调试
- 任务未执行:检查是否缺少必要的权限(如
FOREGROUND_SERVICE、RECEIVE_BOOT_COMPLETED)。 - 状态一直 ENQUEUED:可能是约束条件未满足(如要求充电但设备未插电),可通过
WorkManager.getWorkInfoById()查看约束详情。 - Result.retry() 导致无限循环:搭配
setBackoffCriteria()设置退避策略(线性/指数),并限制重试次数。 - 协程内异常未被捕获:
CoroutineWorker中的未捕获异常会导致任务状态变为FAILED,建议使用 try-catch 包裹挂起调用。
// 调试示例:获取任务信息
val workInfo = WorkManager.getInstance(context).getWorkInfoById(workRequestId).await()
Log.d("WorkManager", "状态: ${workInfo.state}, 约束: ${workInfo.constraints}")
1.7 性能与稳定性要点
| 方面 | 建议 |
|---|---|
| 任务粒度 | 单个任务执行时间不宜超过 10 分钟(系统会触发 ANR 或强制结束) |
| 内存占用 | 避免在 CoroutineWorker 内持有大型 Bitmap 或集合,尽量使用文件流 |
| 生命周期 | 使用 WorkManager.enqueueUniqueWork() 防止重复任务堆积 |
| 协程取消 | 任务被取消时协程作用域自动取消,应在挂起点检查 isActive 或 ensureActive() 及时响应 |
| 数据持久化 | 使用 Data.Builder 传递小数据(< 10KB);大文件通过 Uri 或文件路径传入 |
本章小结:WorkManager 的核心是“基于持久化状态的可延迟保证调度”,通过约束控制执行时机,通过状态机管理生命周期。协程结合后,后台任务可以像普通同步代码一样编写,同时享受挂起与调度的优势。下一章将深入构建第一个 CoroutineWorker 示例。