第35章:WorkManager:后台任务调度、约束条件、链式任务、观察任务状态
后台任务,说白了就是让App在用户看不见的时候偷偷干活。比如上传日志、同步数据、定期清理缓存。Android上做后台任务的方式很多,但WorkManager是我目前最推荐的选择。
为什么?因为它能保证任务一定会执行,哪怕App被杀死、手机重启。我早期做项目时用过Service + AlarmManager的组合,结果各种机型兼容性问题搞得我焦头烂额。后来换成WorkManager,世界清净了。
35.1 WorkManager 的核心概念
WorkManager 是 Android Jetpack 的一部分。它专门处理那些「需要保证完成」的后台任务。你想想看,如果用户上传一张照片到云端,中途App被系统杀掉了,这张照片就丢了?那肯定不行。
WorkManager 会帮你把任务信息存到数据库里。等设备恢复后,它自动重新调度。嗯,这里要注意:它不适用于「即时」任务,比如用户点击按钮后立刻要看到结果。那种场景你应该用协程或者线程池。
- 数据同步(上传日志、下载资源)
- 定期清理(删除过期缓存)
- 条件触发(仅在充电时备份照片)
- 链式任务(先下载配置,再初始化SDK)
35.2 基本用法:三步搞定一个任务
先看一个最简单的例子。我习惯把每个后台任务定义成一个 Worker 类:
class LogUploadWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
return try {
// 模拟上传日志
Log.d("WorkManager", "开始上传日志...")
Thread.sleep(2000)
Log.d("WorkManager", "上传成功")
Result.success()
} catch (e: Exception) {
Log.e("WorkManager", "上传失败", e)
Result.retry()
}
}
}
然后创建请求并提交:
val uploadRequest = OneTimeWorkRequestBuilder<LogUploadWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
就这么简单。任务会在网络可用时自动执行。如果失败,它会根据重试策略重新尝试。
35.3 约束条件:让任务更聪明
约束条件就是「任务执行的前提」。我曾经遇到一个坑:用户在地铁里刷App,后台任务突然开始上传大文件,流量哗哗地跑。用户投诉后,我加上了网络类型约束。
WorkManager 支持的约束条件:
| 约束类型 | API | 说明 |
|---|---|---|
| 网络状态 | setRequiredNetworkType() | CONNECTED / UNMETERED / NOT_ROAMING |
| 充电状态 | setRequiresCharging() | true 表示只在充电时执行 |
| 空闲状态 | setRequiresDeviceIdle() | 设备空闲时执行(API 23+) |
| 存储空间 | setRequiresStorageNotLow() | 存储空间充足时执行 |
| 电池电量 | setRequiresBatteryNotLow() | 电量不低时执行 |
组合使用也很简单:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // 仅WiFi
.setRequiresCharging(true) // 充电中
.setRequiresBatteryNotLow(true) // 电量充足
.build()
嗯,这里要注意:约束条件越多,任务执行的窗口就越窄。如果用户一直不满足条件,任务会一直等待。我建议只加必要的约束,别太苛刻。
35.4 链式任务:按顺序执行
很多业务场景需要任务之间有依赖关系。比如:先下载配置文件,再初始化SDK,最后上报状态。用链式任务就很优雅:
val downloadWork = OneTimeWorkRequestBuilder<ConfigDownloadWorker>()
.build()
val initWork = OneTimeWorkRequestBuilder<SdkInitWorker>()
.build()
val reportWork = OneTimeWorkRequestBuilder<StatusReportWorker>()
.build()
WorkManager.getInstance(context)
.beginWith(downloadWork)
.then(initWork)
.then(reportWork)
.enqueue()
WorkManager 会保证顺序执行。如果中间某个任务失败了,后面的任务不会执行。你可以通过 Result.success() 和 Result.failure() 来控制流程。
链式任务还支持「并行 + 串行」的混合模式:
// 先并行下载两个文件
val downloadA = OneTimeWorkRequestBuilder<DownloadWorkerA>().build()
val downloadB = OneTimeWorkRequestBuilder<DownloadWorkerB>().build()
// 等两个都完成后,再合并处理
val mergeWork = OneTimeWorkRequestBuilder<MergeWorker>().build()
WorkManager.getInstance(context)
.beginWith(listOf(downloadA, downloadB))
.then(mergeWork)
.enqueue()
你想想看,这种写法比手动管理线程池和回调要清晰多少?
35.5 观察任务状态:LiveData 与 Flow
任务提交后,我们总得知道它执行得怎么样了。WorkManager 提供了两种观察方式。
方式一:LiveData
val workManager = WorkManager.getInstance(context)
val workId = uploadRequest.id
workManager.getWorkInfoByIdLiveData(workId)
.observe(this) { workInfo ->
if (workInfo != null) {
when (workInfo.state) {
WorkInfo.State.ENQUEUED -> Log.d("状态", "等待执行")
WorkInfo.State.RUNNING -> Log.d("状态", "正在执行")
WorkInfo.State.SUCCEEDED -> Log.d("状态", "执行成功")
WorkInfo.State.FAILED -> Log.d("状态", "执行失败")
WorkInfo.State.CANCELLED -> Log.d("状态", "已取消")
WorkInfo.State.BLOCKED -> Log.d("状态", "被阻塞(依赖的任务未完成)")
}
}
}
方式二:Flow(推荐)
我个人更喜欢用 Flow,因为它和协程配合更自然:
lifecycleScope.launch {
workManager.getWorkInfoByIdFlow(workId)
.collect { workInfo ->
// 处理状态变化
updateUI(workInfo.state)
}
}
为什么推荐 Flow?因为你可以用 flow 的各种操作符。比如只关心成功或失败的状态:
workManager.getWorkInfoByIdFlow(workId)
.filter { it.state.isFinished }
.collect { workInfo ->
// 任务结束了,不管成功还是失败
}
35.6 知识体系总览
我把本章的核心逻辑画了一张图,方便你理解 WorkManager 的整体架构:
35.7 避坑指南与最佳实践
最后分享几个我实际项目中积累的经验:
- 不要用 WorkManager 做即时任务。它内部有数据库写入和调度开销,至少几百毫秒。用户点击按钮后要立刻反馈的场景,请用协程。
- 定期任务用 PeriodicWorkRequest。但要注意最小间隔是15分钟。如果你需要更精确的定时,考虑 AlarmManager。
- 任务结果传递。Worker 之间传递数据用 outputData。我习惯把关键结果放到 outputData 里,方便下游任务读取。
- 测试很重要。WorkManager 提供了 TestListenableWorkerBuilder,可以方便地写单元测试。别偷懒,我吃过亏。
好了,WorkManager 的核心用法就这些。它不像 RxJava 那样复杂,也不像 AlarmManager 那样原始。说白了,它就是 Android 官方给开发者准备的一个「靠谱的后台任务管家」。你只要把任务定义好、约束配好、链式搭好,剩下的交给它就行。
公众号:蓝海资料掘金营,微信deep3321