WorkManager:可靠的延迟与后台任务调度
认识 WorkManager:让后台任务可靠又简单
在 Android 开发中,处理后台任务一直是一个需要小心翼翼应对的领域。从早期的 Service、JobScheduler,到后来的 AlarmManager、Firebase JobDispatcher……开发者面对碎片化的 API、复杂的电池优化与后台限制,经常感到头疼。Google 推出了 Jetpack WorkManager 库,致力于统一解决需要保证执行、即便应用退出或设备重启也不丢失的延迟后台任务。
WorkManager 能解决什么问题?
许多任务不一定要立刻执行,但必须保证最终完成:
- 上传日志或错误报告
- 定期同步服务器数据
- 压缩并保存用户拍摄的图片
- 在满足特定条件(如网络可用)时下载资源
- 应用更新后的数据库整理
对于这类任务,如果依赖简单的 Thread 或 AsyncTask,应用被杀死任务就丢掉了;如果依赖 Service,又容易受系统后台限制影响,消耗电量。WorkManager 在底层根据设备 API 版本自动选择合适的调度引擎(JobScheduler、AlarmManager 等),并提供统一 API、约束条件、链式执行、状态观察等特性,让开发者只需关注做什么,不用管怎么保证执行。
核心优势一览
- 保证任务执行:即使应用进程被杀死,任务也会在合适的时候重新调度。
- 兼容性强:自动适配 API 14 以上的所有设备,最低支持到 Android 4.0。
- 约束支持:可以指定只在「设备空闲」「网络已连接」「电量充足」等条件满足时才运行。
- 链式任务:可以定义任务执行顺序,或组合出复杂的依赖关系(A → B → C)。
- 状态观察:通过
LiveData或ListenableFuture方便地观察任务进度和结果。 - 定期任务:灵活设置最小间隔的定期工作,系统会优化定时执行的时机。
- 加急任务:从 WorkManager 2.7 开始支持加急工作,可绕过省电模式的限制执行重要任务。
快速上手:集成与第一行代码
添加依赖
在模块级 build.gradle.kts 中添加最新稳定版:
dependencies {
val work_version = "2.9.0"
implementation("androidx.work:work-runtime-ktx:$work_version")
}
如果你的项目使用 Java,把
-ktx去掉即可。推荐配合 Kotlin 获得更简洁的协程支持。
定义一个 Worker
创建一个类继承 Worker,重写 doWork() 方法,在这里编码实际要执行的逻辑,并返回执行结果:
class UploadLogWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
try {
// 模拟上传日志
uploadLogs()
// 任务成功完成
return Result.success()
} catch (e: Exception) {
// 任务失败,如果需要重试可以返回 Result.retry()
return Result.failure()
}
}
}
返回结果有三种:
Result.success():成功。Result.failure():失败,且不需要重试。Result.retry():暂时失败,WorkManager 会根据退避策略稍后重试。
构建并提交任务
使用 WorkRequest 来描述任务,并提交给 WorkManager 实例:
val uploadRequest = OneTimeWorkRequestBuilder<UploadLogWorker>().build()
WorkManager.getInstance(context).enqueue(uploadRequest)
这样,任务就进入系统调度队列了。即使此刻网络断掉,只要约束条件满足,它就会执行。
给任务加上约束:只在合适时机运行
很多后台操作需要特定的环境才执行,比如上传必须连网,下载最好在 Wi‑Fi 下进行。通过 Constraints 可以轻松设置:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 任意网络连接
// .setRequiredNetworkType(NetworkType.UNMETERED) // 仅非计费网络(Wi-Fi)
.setRequiresBatteryNotLow(true) // 电池不低
.setRequiresCharging(true) // 充电中
.setRequiresDeviceIdle(true) // 设备空闲(API 23+)
.setRequiresStorageNotLow(true) // 存储空间不低
.build()
val constrainedRequest = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setConstraints(constraints)
.build()
当约束不再满足时,任务会自动停止,待条件恢复后继续执行。
一次性任务与定期任务
WorkManager 支持两种基础调度类型。
一次性任务 (OneTimeWorkRequest)
适合执行一次且保证完成的工作。可以通过 setInitialDelay() 延迟执行:
val delayedRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
定期任务 (PeriodicWorkRequest)
适合每隔一段时间重复执行的操作,如定期同步。最小间隔为 15 分钟(这是系统省电所要求的):
val periodicRequest = PeriodicWorkRequestBuilder<CleanupWorker>(
repeatInterval = 15, TimeUnit.MINUTES // 循环间隔
).build()
注意定期任务不能设置初始延迟,但同样可以应用约束。
链式任务与执行策略
串行链式调用
如果想按顺序执行一系列任务,一个任务成功后自动执行下一个,可以使用 beginWith 和 then:
WorkManager.getInstance(context)
.beginWith(compressImageWork)
.then(uploadImageWork)
.then(cleanCacheWork)
.enqueue()
任何一个失败,后续的任务都不会执行。这种依赖关系在频繁组合数据流时非常有用。
并行任务与复杂组合
可以使用 beginWith 传入多个 WorkRequest,它们会并行执行。然后用 then 接后续任务:
val parallelTaskA = OneTimeWorkRequestBuilder<TaskA>().build()
val parallelTaskB = OneTimeWorkRequestBuilder<TaskB>().build()
val mergeTask = OneTimeWorkRequestBuilder<MergeTask>().build()
WorkManager.getInstance(context)
.beginWith(listOf(parallelTaskA, parallelTaskB))
.then(mergeTask)
.enqueue()
并行任务全部成功后,mergeTask 才会启动。
唯一工作序列
有时需要保证同一时间只有一个同名链在执行,例如同步操作只应有一个实例,可以使用 beginUniqueWork:
WorkManager.getInstance(context)
.beginUniqueWork(
"unique_sync",
ExistingWorkPolicy.KEEP, // 如果已有同名任务在运行,保留原有的,不重复创建
syncRequest
)
.enqueue()
ExistingWorkPolicy 选项:
REPLACE:取消已存在的链,用新的替换。KEEP:保留已存在的链,忽略新请求。APPEND:将新请求附加到已有链的末尾。
观察任务状态与进度
很多时候我们需要在界面展示进度或根据结果做出响应。WorkManager 为每个请求都提供了状态信息,通过 LiveData 或 ListenableFuture 可以轻松监听。
获取 WorkInfo
每个 WorkRequest 都有一个唯一 ID,通过它获取 LiveData<WorkInfo>:
val workInfoLiveData = WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(uploadRequest.id)
workInfoLiveData.observe(lifecycleOwner) { workInfo ->
if (workInfo != null) {
when (workInfo.state) {
WorkInfo.State.ENQUEUED -> showStatus("已加入队列")
WorkInfo.State.RUNNING -> {
// 还可以获取进度数据
val progress = workInfo.progress.getInt("PROGRESS", 0)
updateProgressBar(progress)
}
WorkInfo.State.SUCCEEDED -> showStatus("上传成功")
WorkInfo.State.FAILED -> showStatus("上传失败")
WorkInfo.State.BLOCKED -> showStatus("被阻塞,等待依赖任务")
WorkInfo.State.CANCELLED -> showStatus("已取消")
}
}
}
在 Worker 中报告进度
在 doWork() 内使用 setProgress() 发送进度数据:
class ProgressWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
for (i in 0..100 step 10) {
setProgress(workDataOf("PROGRESS" to i))
// 模拟耗时操作
Thread.sleep(500)
}
return Result.success()
}
}
然后观察端就能实时收到进度数值。
输入输出数据传递
任务经常需要接收参数,并返回处理结果。WorkManager 使用 Data 对象来传递轻量数据(类似于 Bundle,支持基本类型及小尺寸数据)。
传入输入数据
创建 WorkRequest 时设置输入:
val inputData = workDataOf(
"IMAGE_URI" to "file:///sdcard/photo.jpg",
"COMPRESS_QUALITY" to 80
)
val request = OneTimeWorkRequestBuilder<CompressWorker>()
.setInputData(inputData)
.build()
在 Worker 中取出:
val imageUri = inputData.getString("IMAGE_URI")
val quality = inputData.getInt("COMPRESS_QUALITY", 80)
返回输出数据
在 doWork() 中通过 Result.success(outputData) 返回:
return Result.success(workDataOf("OUTPUT_URI" to compressedUri))
后续任务可以通过 getInputData() 获取前一个任务的输出,实现任务间数据流串联。
加急任务:突破省电限制
从 Android 12 起,后台工作受到了更严格限制。但某些任务对时效性要求高(如消息发送、支付同步),可以使用加急工作 (Expedited Work)。WorkManager 2.7+ 提供了 setExpedited():
val importantRequest = OneTimeWorkRequestBuilder<PaymentSyncWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
加急任务会尽可能快地执行,但系统仍可能因资源问题将其稍作延迟。如果应用超出加急配额,可以降级为普通任务(参数指定的策略)。
取消、停止与自定义重试
取消任务
通过 ID 或标签取消任务:
WorkManager.getInstance(context).cancelWorkById(request.id)
// 或者给任务加标签,批量取消
WorkManager.getInstance(context).cancelAllWorkByTag("sync_tag")
重试与退避策略
当 doWork() 返回 Result.retry() 时,任务会根据退避策略重新调度。可以自定义退避时间和方式:
val retryRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10L, TimeUnit.SECONDS // 首次重试间隔10秒
)
.build()
两种策略:
EXPONENTIAL:指数增长(10秒 → 20秒 → 40秒……)。LINEAR:固定间隔。
调试与诊断
在开发阶段查看任务调度情况非常有用,可以通过 ADB 命令操作:
# 查看当前所有待执行和正在执行的任务信息
adb shell dumpsys jobscheduler | grep -A30 work
# 直接触发一次 WorkManager 的任务(需要知道包名)
adb shell am broadcast -a "androidx.work.impl.diagnostics.REQUEST_DIAGNOSTICS" -p <你的包名>
启用日志也能看到 WorkManager 的内部调度信息:
adb shell setprop log.tag.WM-EXECUTOR VERBOSE
adb shell setprop log.tag.WM-SCHEDULER VERBOSE
最佳实践与常见问题
-
Worker 的小心陷阱
doWork()默认在后台线程运行(Worker使用一个共享线程池),不要在内部直接更新 UI。如需更新 UI,请使用Post到主线程或结合LiveData。 -
避免内存泄漏
观察WorkInfo时,尽量使用LiveData+LifecycleOwner自动管理生命周期。如果不需要后台结果响应,可以不观察。 -
定期任务间隔选择
系统可能会为了省电将多个定期任务挤在一起执行。如果你的操作不需要严格的准时,接受系统弹性调度会更省电。 -
长时间运行任务
doWork()方法被期望在 10 分钟内完成。如果确实需要长时间运行,可以考虑ForegroundService配合 WorkManager 的加急任务,或使用WorkManager的setForeground()信息。 -
处理停止信号
你的Worker在系统要求停止时会收到onStopped()回调。重写它以释放资源或标记取消,使任务提前结束,避免继续浪费资源。
小结
WorkManager 是 Android 当下处理可靠后台任务的推荐方案。它统一了碎片化的 API,提供了强大的约束、链式组合和状态观察功能,并且能保证任务在应用退出、设备重启后依然得到执行。从简单的一次性上传,到复杂的数据同步链,都可以依赖 WorkManager 让您的应用更加健壮和节能。
开始尝试将那些需要“保证完成”的逻辑迁移到 WorkManager 中吧,您会惊喜于它的稳定和简洁!