一、WorkManager 是什么?为什么我们需要它?
后台任务调度,听起来好像很简单对吧?
但做过几个项目后你就会发现,Android 的后台任务管理其实是个大坑。不同厂商的定制系统、不同版本的 API 限制、还有用户动不动就杀后台的习惯……嗯,我早期做的一个下载模块就吃过这个亏——明明 Service 跑得好好的,一到小米手机上就被干掉了。
WorkManager 就是 Google 为了解决这些破事推出的官方方案。它不只是一个简单的任务执行器,而是一个兼容性极强、能保证任务最终执行的调度框架。
核心特点:
- 兼容 API 14+,连老设备都能用
- 即使 App 被杀死,任务也会在条件满足时恢复执行
- 支持一次性任务、周期性任务、链式任务
- 可以设置约束条件(如网络状态、电量等)
说白了,你只需要告诉 WorkManager「我要做什么、什么时候做」,剩下的它帮你搞定。
二、快速上手:第一个 WorkManager 任务
先别急着看复杂的东西。我们从一个最简单的例子开始。
2.1 定义 Worker
每个任务的核心逻辑都写在 Worker 类里。你只需要继承 Worker,然后实现 doWork() 方法。
class UploadWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
override fun doWork(): Result {
// 这里写你的后台逻辑
Log.d("WorkManager", "开始上传文件...")
// 模拟耗时操作
Thread.sleep(2000)
return Result.success() // 返回成功
// 或者 Result.failure() 返回失败
// 或者 Result.retry() 要求重试
}
}
我个人习惯把 Worker 写得尽量轻量。不要在 doWork() 里搞太复杂的逻辑,拆成多个小任务更稳妥。
2.2 创建 WorkRequest 并调度
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
就这么简单。两行代码,任务就跑起来了。
小提示:OneTimeWorkRequest 是一次性任务。如果你需要重复执行,后面会讲到 PeriodicWorkRequest。
三、约束条件:让任务在合适的时机执行
你想想看,如果用户正在用流量看视频,你的 App 突然在后台下载大文件,是不是很招人烦?
约束条件就是用来解决这个问题的。你可以告诉 WorkManager:「等连上 Wi-Fi 再执行」、「等电量充足再执行」。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络
.setRequiresCharging(true) // 需要充电
.setRequiresBatteryNotLow(true) // 电量不能太低
.setRequiresStorageNotLow(true) // 存储空间充足
.build()
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(constraints)
.build()
我在项目中遇到过一个问题:用户反馈某些任务一直没执行。查了半天才发现,是约束条件设置得太严格了——既要充电又要 Wi-Fi,用户白天在公司插着电源但用的是有线网络,结果任务一直挂在那里。后来我把约束放宽了一些,加了个超时兜底。
注意:约束条件不是立即生效的。WorkManager 会在条件满足后的某个时间点执行任务,具体时间取决于系统调度。别指望它像闹钟一样准时。
四、链式任务:一个接一个地执行
实际项目中,很少有单个任务就能搞定的事情。比如上传图片前要先压缩,压缩完还要生成缩略图,最后再上传到服务器。
链式任务就是为这种场景设计的。你可以把多个 WorkRequest 串起来,让它们按顺序执行。
val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
.build()
val thumbnailRequest = OneTimeWorkRequestBuilder<ThumbnailWorker>()
.build()
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.build()
WorkManager.getInstance(context)
.beginWith(compressRequest) // 第一步:压缩
.then(thumbnailRequest) // 第二步:生成缩略图
.then(uploadRequest) // 第三步:上传
.enqueue()
链式任务还有一个很实用的特性:数据传递。前一个任务的输出可以作为后一个任务的输入。
// 在 Worker 中输出数据
class CompressWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val output = Data.Builder()
.putString("compressed_path", "/data/compressed.jpg")
.build()
return Result.success(output)
}
}
// 在下一个 Worker 中获取数据
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val compressedPath = inputData.getString("compressed_path")
// 上传 compressedPath 指向的文件
return Result.success()
}
}
嗯,这里要注意:Data 对象的大小有限制,最大 10KB。别往里面塞大文件路径以外的数据。
五、周期性任务:定时重复执行
有些任务需要定期执行,比如每天同步一次数据、每周清理一次缓存。这时候就用 PeriodicWorkRequest。
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
24, TimeUnit.HOURS // 每24小时执行一次
)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueue(syncRequest)
重要提醒:PeriodicWorkRequest 的最小间隔是 15 分钟。你设置 5 分钟也没用,系统会强制拉回到 15 分钟。这是 Google 为了省电做的限制。
我曾经接手过一个项目,里面用 AlarmManager 做定时任务,结果在 Android 8.0 以上的设备上频繁崩溃。换成 WorkManager 的周期性任务后,问题就解决了。说白了,能用 WorkManager 就别自己造轮子。
六、观察任务状态
任务提交了,怎么知道它执行完了没?WorkManager 提供了 LiveData 或 Flow 来观察状态。
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(uploadRequest.id)
.observe(this) { workInfo ->
if (workInfo != null) {
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> Log.d("WorkManager", "任务成功")
WorkInfo.State.FAILED -> Log.d("WorkManager", "任务失败")
WorkInfo.State.RUNNING -> Log.d("WorkManager", "任务执行中")
WorkInfo.State.ENQUEUED -> Log.d("WorkManager", "任务已排队")
else -> {}
}
}
}
状态机其实很简单:ENQUEUED → RUNNING → SUCCEEDED/FAILED。如果设置了重试策略,失败后还会回到 ENQUEUED 状态。
七、避坑指南
最后分享几个我踩过的坑:
- 不要依赖 WorkManager 做即时任务。它设计初衷是「保证最终执行」,不是「立即执行」。需要立即执行的任务请用前台 Service。
- UniqueWorkPolicy 要慎用。如果你用 beginUniqueWork() 创建链式任务,记得指定冲突策略:KEEP(保留旧任务)还是 REPLACE(替换旧任务)。我一开始没注意,结果重复提交了好几个任务。
- 测试时记得加延迟。WorkManager 在测试环境下默认是立即执行的。用 TestListenableWorkerBuilder 可以模拟真实调度。
- 不要忘记处理 Result.retry()。如果任务返回 retry,WorkManager 会根据 BackoffPolicy 重试。默认是指数退避,第一次等 30 秒,第二次等 60 秒,以此类推。
我的习惯:每个 Worker 都加上 try-catch,捕获到异常时返回 Result.retry()。这样即使网络波动或临时错误,任务也能自动恢复。
好了,关于 WorkManager 的核心用法就这些。从一次性任务到链式任务,从约束条件到周期性调度,这套工具基本覆盖了日常开发中 90% 的后台需求。下次遇到后台任务,先想想能不能用 WorkManager 搞定——大概率是可以的。