一、WorkManager 到底是什么?
说实话,刚接触 Android 后台任务时,我踩过不少坑。Service 被系统杀、JobScheduler 兼容性差、AlarmManager 不够灵活……直到 WorkManager 出现,我才觉得「嗯,这才是该有的样子」。
WorkManager 是 Jetpack 家族里专门处理后台任务的组件。它的核心思想很简单:保证任务一定会被执行,哪怕手机重启、应用退出。你想想看,这比你自己写一套任务队列靠谱多了。
核心特性一览:
- 兼容 API 14+,底层自动适配不同版本
- 支持一次性任务和周期性任务
- 可以设置约束条件(比如只在充电时执行)
- 支持链式任务,一个接一个跑
- 即使进程被杀,任务也会被持久化
我个人习惯把 WorkManager 理解成一个「智能任务管家」。你只管告诉它做什么、什么时候做,剩下的交给它。
二、Worker 定义——你的任务逻辑
Worker 是 WorkManager 里最基础的单元。说白了,它就是你要执行的代码块。
2.1 如何定义一个 Worker?
继承 Worker 类,重写 doWork() 方法就行。我一般这样写:
class UploadWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
return try {
// 你的后台逻辑
uploadFile()
Result.success()
} catch (e: Exception) {
// 失败后可以重试
Result.retry()
}
}
}
这里有个细节:doWork() 是在后台线程执行的,所以你不用操心线程切换。但要注意,它默认是同步的,如果你在里面开协程,得自己管理生命周期。
我的经验:曾经有个同事在 doWork() 里直接调用了挂起函数,结果任务莫名其妙就结束了。后来才发现,Worker 不会等协程完成。所以要么用 CoroutineWorker,要么自己控制同步。
2.2 Result 的三种状态
| 状态 | 含义 | 适用场景 |
|---|---|---|
| Result.success() | 任务成功完成 | 正常执行完毕 |
| Result.failure() | 任务失败,不再重试 | 不可恢复的错误 |
| Result.retry() | 任务失败,需要重试 | 网络超时、临时错误 |
你可能会问:重试策略怎么控制?别急,后面会讲到。
三、WorkRequest——任务的「说明书」
Worker 定义了「做什么」,WorkRequest 定义了「怎么做」。它俩是搭档关系。
3.1 创建 WorkRequest
最常用的两种:
// 一次性任务
val oneTimeRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInitialDelay(10, TimeUnit.SECONDS)
.build()
// 周期性任务
val periodicRequest = PeriodicWorkRequestBuilder<UploadWorker>(
15, TimeUnit.MINUTES
)
.build()
注意:周期性任务的最小间隔是 15 分钟。这是系统强制的,你设 5 分钟也没用。我刚开始不知道,调试了半天……
3.2 输入输出数据
WorkManager 支持通过 Data 对象传递参数:
val inputData = workDataOf("file_path" to "/sdcard/photo.jpg")
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(inputData)
.build()
在 Worker 里取数据:
val filePath = inputData.getString("file_path")
注意:Data 对象有大小限制,不能超过 10KB。如果你需要传大对象,建议存到本地再传路径。
四、约束条件——让任务更智能
约束条件是 WorkManager 最实用的功能之一。你可以指定任务在什么环境下执行。
4.1 常用约束
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络
.setRequiresCharging(true) // 需要充电
.setRequiresDeviceIdle(true) // 需要设备空闲
.setRequiresStorageNotLow(true) // 存储空间充足
.build()
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(constraints)
.build()
我曾经做过一个日志上传功能,就用了这个组合:有 Wi-Fi + 充电 + 设备空闲。这样既不影响用户体验,又能保证数据上传。
4.2 约束的触发机制
当约束条件不满足时,任务会处于 BLOCKED 状态。一旦条件满足,系统会自动恢复执行。你不需要写任何轮询代码。
五、链式任务——任务编排的艺术
实际项目中,很少有单一任务。比如:先下载图片 → 压缩 → 上传。这时候链式任务就派上用场了。
5.1 基本链式
WorkManager.getInstance(context)
.beginWith(downloadRequest)
.then(compressRequest)
.then(uploadRequest)
.enqueue()
每个任务会按顺序执行,前一个成功后才启动下一个。如果中间某个任务返回 failure(),整个链会终止。
5.2 并行与合并
有时候需要先并行做两件事,再合并结果:
val parallel1 = OneTimeWorkRequestBuilder<DownloadWorker>().build()
val parallel2 = OneTimeWorkRequestBuilder<DownloadWorker>().build()
WorkManager.getInstance(context)
.beginWith(listOf(parallel1, parallel2))
.then(mergeRequest)
.enqueue()
我的习惯:链式任务里,我会给每个 Worker 加上唯一标签,方便后续观察状态。比如 .addTag("download")。
六、知识体系总览
下面这张图是我梳理的 WorkManager 核心逻辑,你看完应该能有个整体印象:
七、实战:后台任务调度
光说不练假把式。我们做一个完整的例子:用户上传头像,先压缩再上传。
7.1 定义 Worker
class CompressWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val filePath = inputData.getString("file_path") ?: return Result.failure()
// 压缩逻辑
val compressedPath = compressImage(filePath)
val output = workDataOf("compressed_path" to compressedPath)
return Result.success(output)
}
}
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val path = inputData.getString("compressed_path") ?: return Result.failure()
// 上传逻辑
return if (uploadToServer(path)) Result.success() else Result.retry()
}
}
7.2 编排任务
val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
.setInputData(workDataOf("file_path" to "/sdcard/avatar.jpg"))
.addTag("compress")
.build()
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.addTag("upload")
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context)
.beginWith(compressRequest)
.then(uploadRequest)
.enqueue()
7.3 观察状态
WorkManager.getInstance(context)
.getWorkInfosByTagLiveData("upload")
.observe(this) { workInfos ->
workInfos.forEach { info ->
when (info.state) {
WorkInfo.State.SUCCEEDED -> showToast("上传成功")
WorkInfo.State.FAILED -> showToast("上传失败")
WorkInfo.State.RUNNING -> showProgress(true)
else -> {}
}
}
}
避坑指南:我曾经在链式任务里忘记传递数据,导致第二个 Worker 拿不到压缩后的路径。记得用 Result.success(output) 把数据传下去。
八、总结
WorkManager 的设计思路其实很清晰:把任务定义、执行策略、约束条件三者解耦。你只需要关心业务逻辑,调度的事情交给框架。
我个人觉得,它最大的价值在于「可靠性」。以前用 Service 做后台任务,总担心被系统杀掉。现在有了 WorkManager,我可以放心地说:「这个任务一定会被执行」。
嗯,如果你还在用老方式做后台任务,不妨试试 WorkManager。你会发现,原来后台调度可以这么省心。