1. WorkManager 为什么存在(设计目标)
-
可靠执行 :在 App 退出、进程被杀、电量/网络受限后,在满足条件时仍会被系统调度(后台稳态由系统保障)。
-
约束感知:网络、充电、存储、电量、设备 idle 等条件满足时再执行。
-
持久化:任务及其状态保存在内部数据库(Room),崩溃/重启不丢。
-
最小侵入:API 友好、与协程/LiveData/Flow 打通。
-
平台自适配:新系统走 JobScheduler;老系统自动降级(你无需自己适配各版本 API)。
适用场景:延时/后台 且最终要执行的任务(同步、日志/打点汇聚、清理、压缩上传、预取、定期刷新等)。
不适用:严格准点 (闹钟/精确计时)→ 用 AlarmManager setExact*;前台交互性强的短任务→ 直接在前台或异步执行。
2. 核心模型(必须掌握的 6 个概念)
- Worker / CoroutineWorker / ListenableWorker:你的任务逻辑,返回 Result.success() / failure() / retry()。
- WorkRequest :一次任务请求;分 OneTimeWorkRequest 与 PeriodicWorkRequest。
- Constraints:网络/充电/电量/存储/idle 等执行条件。
- Input/Output Data :任务入参/出参(体积小,~KB 级,大数据放文件/DB)。
- Chaining:任务依赖链(A → B → C)。
- Unique Work :同名任务的去重/替换/排队策略(防重复)。
3. 快速上手(一次性与周期性)
scss
// 1) 定义任务
class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result = try {
val userId = inputData.getString("userId") ?: return Result.failure()
sync(userId) // 你的逻辑(挂起即可)
Result.success(workDataOf("rows" to 42))
} catch (e: IOException) {
Result.retry()
} catch (e: Exception) {
Result.failure()
}
}
// 2) 约束
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // 仅 Wi-Fi
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.build()
// 3) 一次性任务
val oneTime = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
.setInputData(workDataOf("userId" to "u123"))
.build()
WorkManager.getInstance(context).enqueue(oneTime)
// 4) 周期性任务(最小周期受平台限制,通常≥15分钟)
val periodic = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"periodic_sync",
ExistingPeriodicWorkPolicy.KEEP, // 已有则保留(避免重复)
periodic
)
4. 约束(Constraints)与重试(Backoff)
可用约束:
-
setRequiredNetworkType(...):NOT_REQUIRED / CONNECTED / UNMETERED / NOT_ROAMING / METERED
-
setRequiresCharging(true)
-
setRequiresBatteryNotLow(true)
-
setRequiresStorageNotLow(true)
-
setRequiresDeviceIdle(true)(仅新系统有效)
重试退避:
-
setBackoffCriteria(BackoffPolicy.LINEAR | EXPONENTIAL, minBackoff, unit)
-
Result.retry() 触发退避;平台会裁剪到允许范围(最小/最大值受系统限制)。
小建议:网络/后端易波动 → EXPONENTIAL;本地可快速修复(如文件锁)→ LINEAR。
5. 任务链(Chaining)与输入输出传递
scss
val stepA = OneTimeWorkRequestBuilder<A>().build()
val stepB = OneTimeWorkRequestBuilder<B>().build()
val stepC = OneTimeWorkRequestBuilder<C>().build()
WorkManager.getInstance(ctx)
.beginWith(stepA)
.then(stepB)
.then(stepC)
.enqueue()
- 上游 所有成功 才会触发下游;任何一个 failure() → 整条链失败。
- 上游多分支可用 beginWith(listOf(...)) 做 AND 依赖。
- 数据传递 :A 的 Result.success(data) 会合并到下游 inputData(键冲突后者覆盖)。注意 Data 尺寸限制(小数据) 。
6. 唯一任务(Unique Work)与去重策略
防止重复排队,是生产落地的关键。
一次性:
arduino
WorkManager.getInstance(ctx).enqueueUniqueWork(
"sync_user_u123",
ExistingWorkPolicy.KEEP, // KEEP / REPLACE / APPEND / APPEND_OR_REPLACE
oneTimeRequest
)
周期性:
scss
enqueueUniquePeriodicWork("daily_cleanup", ExistingPeriodicWorkPolicy.UPDATE, periodicReq)
策略说明(一次性):
- KEEP:已有在队列/运行中 → 丢弃新的(常用)
- REPLACE:取消旧的,使用新的
- APPEND(_OR_REPLACE):把新请求接到现有链尾部
7. "尽快执行"与长时任务
7.1 加急(Expedited)工作(Android 12+ 背景限制下的"立即"语义)
ini
val req = OneTimeWorkRequestBuilder<SyncWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
-
适合用户触发、需要尽快完成的小任务(上传单张、快速落盘...)。
-
有配额:超限时按 OutOfQuotaPolicy 回退成普通 Work。
-
不能加约束(或效果受限),也不能是周期性任务。
7.2 前台长任务(需要通知栏可见)
当任务会长时间运行/受前台限制时,转为 Foreground:
kotlin
class LongTaskWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
setForeground(createForegroundInfo()) // 尽早调用
// 长时间处理...
return Result.success()
}
private fun createForegroundInfo(): ForegroundInfo { /* 构建通知 */ }
}
- 与前台服务类似,但由 WorkManager 管理生命周期与恢复。
8. 进度上报、取消与状态观察
8.1 进度(Progress)
scss
setProgress(workDataOf("percent" to 40))
// UI 侧
workManager.getWorkInfoByIdLiveData(id).observe(owner) {
val p = it.progress.getInt("percent", 0)
}
8.2 取消
scss
WorkManager.getInstance(ctx).cancelWorkById(id)
WorkManager.getInstance(ctx).cancelAllWorkByTag("tag_sync")
WorkManager.getInstance(ctx).cancelUniqueWork("sync_user_u123")
8.3 观察状态
-
LiveData:getWorkInfoByIdLiveData(id) / getWorkInfosByTagLiveData(tag)
-
Kotlin Flow:workManager.getWorkInfosFlow(...)(或自写轮询/查询)
状态机: ENQUEUED → RUNNING → (SUCCEEDED | FAILED | CANCELLED)
最终态带 outputData(成功)或供你做补偿(失败/取消)。
9. 配置与依赖注入
- 依赖:
arduino
implementation "androidx.work:work-runtime-ktx:<latest>"
- 自定义全局配置(自定义线程池/Factory/日志级别):
kotlin
class App : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setMinimumLoggingLevel(Log.INFO)
.setWorkerFactory(myHiltWorkerFactory) // Hilt/DI 注入
.build()
}
使用 App Startup 自动初始化时,若要自定义配置,请关闭默认 initializer 或实现 Configuration.Provider。
10. 测试(单元/仪器)
less
@get:Rule val instant = InstantTaskExecutorRule()
@get:Rule val tmp = TemporaryFolder()
@Test
fun runWork() = runTest {
val context = ApplicationProvider.getApplicationContext<Context>()
WorkManagerTestInitHelper.initializeTestWorkManager(context)
val req = OneTimeWorkRequestBuilder<SyncWorker>().build()
val wm = WorkManager.getInstance(context)
wm.enqueue(req).result.get()
val info = wm.getWorkInfoById(req.id).get()
assertThat(info.state).isEqualTo(WorkInfo.State.SUCCEEDED)
}
- 用 TestDriver 控制定时/约束触发,验证延时/重试逻辑。
- 业务隔离:把网络/存储通过 DI 注入 Worker,替换为 Fake/Mock。
11. 常见坑与最佳实践
坑:
-
不使用 唯一任务 → 同一逻辑重复排队,导致"多开/重复上传"。
-
在 doWork() 里阻塞线程或忽略协程取消 → 无法优雅中止。
-
用 Data 传大对象/大字节数组 → 超限或内存抖动(把大数据放文件/DB,仅传路径/ID)。
-
周期性任务想"准点" → WorkManager只保证尽量 按周期执行,不保证精确。
-
即时任务用普通 Work → Android 12+ 可能延迟;考虑 Expedited 或前台。
最佳实践:
- 需要去重/幂等 → Unique Work + 业务幂等(服务端/本地 ID 去重)。
- 组合条件 → 统一在 Constraints 设置,不要在 Worker 内自己轮询。
- 长任务 → 前台 ForegroundInfo;短且"尽快" → setExpedited(...)。
- 链式依赖 → beginWith().then();并行汇聚 → beginWith(listOf(...))。
- 观测与回传 → Progress + outputData,UI 订阅 WorkInfo。
- 迁移/升级 → 给 Worker 起稳定的唯一名称,方便切策略/替换。
- Compose 场景 → 用 getWorkInfoByIdLiveData(id).observeAsState() 或封装成 Flow。
12. 与其它方案的选型对比
诉求 | 首选 |
---|---|
后台"最终要完成",可被系统延后 | WorkManager |
严格准点/闹钟/日程 | AlarmManager (setExactAndAllowWhileIdle) |
前台交互、立即且短、对生命周期敏感 | 直接协程/线程 + 前台服务(或 Expedited Work) |
大量结构化数据、需要查询 | Room/数据库(WorkManager 调用它) |
13. 进阶范式(唯一 + 链 + 约束 + 进度)
scss
fun enqueueUserFullSync(ctx: Context, userId: String) {
val prepare = OneTimeWorkRequestBuilder<PrepareWorker>()
.addTag("user_full_sync")
.setInputData(workDataOf("userId" to userId))
.build()
val upload = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build()
WorkManager.getInstance(ctx)
.beginUniqueWork(
"user_full_sync_$userId",
ExistingWorkPolicy.KEEP,
prepare
)
.then(upload)
.enqueue()
}
一句话总结
WorkManager 用"持久化 + 约束感知 + 系统调度"的方式,在各种设备/版本上为你的后台可靠性 兜底;结合 Unique Work、Constraints、Backoff、Expedited、Foreground 与 链式依赖,可以覆盖大多数"需要最终执行"的移动任务。把"大数据放文件/DB、任务做幂等、状态用进度/输出回传",就是生产可落地的正确打开方式。