WorkManager
本教程共 100 篇 · 第 75 篇 · 更新于 2026-07-28 · 约 10 分钟阅读
75. WorkManager
本节目标:学会用 WorkManager 调度可靠执行的后台任务,掌握 Worker 定义、约束条件、周期任务、链式任务、加急工作,理解它和协程的分工。
协程解决「前台时」的并发,但用户切到后台、应用被杀,协程就没了。要保证任务一定执行,得用 WorkManager。
WorkManager 是什么
WorkManager 是 Jetpack 提供的后台任务调度库。它的核心承诺:
- 可靠执行:应用退出、设备重启,任务照样跑。
- 约束触发:只在充电、联网、空闲等条件下运行。
- 自动重试:失败自动退避重试。
- 任务链:多个任务串联、并联,传递数据。
打个比方,协程像你亲自盯着的快递,你走了快递就停;WorkManager 像物流公司,你下了单就管走人,包裹会按约定送到。
适用场景
适合 WorkManager 的任务:
- 定期同步服务器数据。
- 上传日志、埋点。
- 处理用户产生的内容(拍照后上传)。
- 夜间清理缓存。
不适合的场景:
- 用户在前台时立即响应的操作(用协程)。
- 必须立即执行不可中断的操作(用前台服务)。
- 精确时间点触发(用 AlarmManager)。
第一步:加依赖
dependencies {
val workVersion = "2.10.0"
implementation("androidx.work:work-runtime-ktx:$workVersion")
}
KTX 版本自带协程支持,比纯 work-runtime 好用。
第二步:定义 Worker
Worker 是任务的具体实现。继承 Worker 或 CoroutineWorker,推荐后者,能用协程:
class UploadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val fileUri = inputData.getString("file_uri") ?: return Result.failure()
val file = Uri.parse(fileUri).toFile()
uploadApi.upload(file)
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) {
Result.retry()
} else {
Result.failure()
}
}
}
}
返回值三档:
Result.success():成功,结束。Result.failure():失败,不重试,结束。Result.retry():失败,按退避策略重试。
Note
CoroutineWorker的doWork默认在Dispatchers.IO跑,不用自己切线程。
第三步:构造 WorkRequest
Worker 是「做什么」,WorkRequest 是「怎么做、什么时候做」。
一次性任务
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf("file_uri" to fileUri.toString()))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10, TimeUnit.SECONDS
)
.build()
WorkManager.getInstance(context).enqueue(request)
周期任务
val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.DAYS)
.setConstraints(
Constraints.Builder()
.setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily_sync",
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest
)
要点:
- 周期最短 15 分钟,系统强制。
enqueueUniquePeriodicWork用唯一名字,避免重复调度。KEEP表示已有同名任务就保留旧的,REPLACE表示替换。- 周期任务的执行时间不精确,受 Doze 影响,可能延迟。
WarningWorkManager 不保证精确执行时间。需要 7 点整触发的闹钟应用,用 AlarmManager。
约束条件
Constraints 决定任务在什么条件下才跑:
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 联网
.setRequiresCharging(true) // 充电中
.setRequiresBatteryNotLow(true) // 电量不低
.setRequiresStorageNotLow(true) // 存储不低
.setRequiresDeviceIdle(true) // 设备空闲
.build()
NetworkType 几档:
NOT_REQUIRED:不限制。CONNECTED:任何网络。UNMETERED:Wi-Fi 等不计流量网络。METERED:计流量网络。NOT_ROAMING:非漫游。
约束满足才会触发,没满足一直等。
加急工作
普通 WorkManager 任务可能被延迟很久。某些任务用户感知强、需要立即执行但能几分钟内完成,用「加急工作」(Expedited Work):
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
特点:
- 优先级高,系统尽快调度。
- 有配额限制,超过配额退化成普通任务(或者直接失败,看策略)。
- Android 12+ 用「加急作业」机制实现。
- 适合「用户点了上传,希望立刻跑」的场景。
Tip加急工作配额按应用前台时间分配。前台用得多的应用配额多。别滥用,留给真正重要的任务。
链式任务
多个任务有依赖关系,用 beginWith + then:
WorkManager.getInstance(context)
.beginWith(OneTimeWorkRequest.from(FilterWorker::class.java))
.then(OneTimeWorkRequest.from(CompressWorker::class.java))
.then(OneTimeWorkRequest.from(UploadWorker::class.java))
.enqueue()
每个 Worker 的输出数据会自动传给下一个:
// FilterWorker
val output = workDataOf("filtered_uri" to filteredUri.toString())
Result.success(output)
// CompressWorker
val input = inputData.getString("filtered_uri")
并联任务:
WorkManager.getInstance(context)
.beginWith(workA, workB) // A 和 B 并行
.then(workC) // 都完成后跑 C
.enqueue()
或者用 WorkContinuation.combine:
val chain1 = WorkManager.getInstance(context).beginWith(workA).then(workB)
val chain2 = WorkManager.getInstance(context).beginWith(workC).then(workD)
WorkContinuation.combine(listOf(chain1, chain2))
.then(workE)
.enqueue()
唯一任务
避免重复调度,用唯一名字:
WorkManager.getInstance(context).enqueueUniqueWork(
"upload_avatar",
ExistingWorkPolicy.REPLACE,
request
)
策略:
REPLACE:取消旧任务,启动新的。KEEP:保留旧任务,新的丢弃。APPEND:追加到旧任务后面,形成链。
观察任务状态
任务入队后能观察状态:
WorkManager.getInstance(context)
.getWorkInfoByIdFlow(request.id)
.collect { info ->
when (info?.state) {
WorkInfo.State.ENQUEUED -> showPending()
WorkInfo.State.RUNNING -> showProgress()
WorkInfo.State.SUCCEEDED -> showDone()
WorkInfo.State.FAILED -> showError()
WorkInfo.State.CANCELLED -> showCancelled()
else -> {}
}
}
也可以按 tag 或者 name 查:
WorkManager.getInstance(context)
.getWorkInfosByTagFlow("upload")
WorkManager.getInstance(context)
.getWorkInfosForUniqueWorkFlow("upload_avatar")
取消任务
按 id、tag、name 取消:
WorkManager.getInstance(context).cancelWorkById(request.id)
WorkManager.getInstance(context).cancelAllWorkByTag("upload")
WorkManager.getInstance(context).cancelUniqueWork("upload_avatar")
取消是协作式的,Worker 里的 doWork 会收到 CancellationException,要在合适的地方退出。
前台服务 Worker
长时间运行的任务(超过 10 分钟)需要变成前台服务:
class LongRunningWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setForeground(
ForegroundInfo(
NOTIFICATION_ID,
buildNotification("正在处理...")
)
)
// 长时间任务
return Result.success()
}
}
WarningAndroid 14+ 前台服务 Worker 要声明服务类型。比如数据同步类型要申请
FOREGROUND_SERVICE_DATA_SYNC权限,并在ForegroundInfo里指定类型。
输入输出数据
Data 对象在任务间传值,只能存基本类型和字符串:
val input = workDataOf(
"user_id" to 123L,
"file_path" to "/sdcard/x.jpg",
"is_urgent" to true
)
val output = workDataOf("result_url" to "https://...")
Note
Data有大小限制(约 1KB),别用来传大数据。要传文件用 URI,Worker 里读文件。
测试 Worker
Worker 单元测试用 TestWorkerBuilder:
@Test
fun `upload success`() = runTest {
val worker = TestWorkerBuilder.from(context, UploadWorker::class.java).build()
val result = worker.doWork()
assertThat(result).isEqualTo(Result.success())
}
TipWorker 逻辑要写得可测—把网络、文件操作抽象成依赖注入到 Worker 构造函数,测试时换 Mock。
常见坑
- 周期任务设短了:最短 15 分钟,写 1 分钟不会按你设的跑。
- 约束太严:
setRequiresDeviceIdle(true)在日常使用中很难满足,任务可能几天不跑。 - 加急工作滥用:配额用光,真正紧急任务跑不了。
- 大对象塞 Data:超 1KB 报错,用 URI 替代。
- Worker 里调主线程操作:Worker 在子线程,操作 UI 要 post 回主线程。
- 忘了唯一名字:每次启动都 enqueue,任务越堆越多。周期任务必须用
enqueueUniquePeriodicWork。
小结
WorkManager 适合「需要可靠执行」的后台任务。CoroutineWorker + Constraints + WorkRequest 三件套搞定大部分场景。一次性任务用 OneTimeWorkRequest,周期任务用 PeriodicWorkRequest,紧急任务加 setExpedited,长任务用 setForeground。任务间用 Data 传值,链式任务用 beginWith().then()。
下一章讲其他异步方案,把 Handler、线程池这些历史方案补齐。