JetPack WorkManager

1. WorkManager 为什么存在(设计目标)

  • 可靠执行 :在 App 退出、进程被杀、电量/网络受限后,在满足条件时仍会被系统调度(后台稳态由系统保障)。

  • 约束感知:网络、充电、存储、电量、设备 idle 等条件满足时再执行。

  • 持久化:任务及其状态保存在内部数据库(Room),崩溃/重启不丢。

  • 最小侵入:API 友好、与协程/LiveData/Flow 打通。

  • 平台自适配:新系统走 JobScheduler;老系统自动降级(你无需自己适配各版本 API)。

适用场景:延时/后台最终要执行的任务(同步、日志/打点汇聚、清理、压缩上传、预取、定期刷新等)。
不适用:严格准点 (闹钟/精确计时)→ 用 AlarmManager setExact*;前台交互性强的短任务→ 直接在前台或异步执行。


2. 核心模型(必须掌握的 6 个概念)

  • Worker / CoroutineWorker / ListenableWorker:你的任务逻辑,返回 Result.success() / failure() / retry()。
  • WorkRequest :一次任务请求;分 OneTimeWorkRequestPeriodicWorkRequest
  • 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、任务做幂等、状态用进度/输出回传",就是生产可落地的正确打开方式。

相关推荐
uhakadotcom5 小时前
在chrome浏览器插件之中,options.html和options.js常用来做什么事情
前端·javascript·面试
想想就想想5 小时前
线程池执行流程详解
面试
程序员清风6 小时前
Dubbo RPCContext存储一些通用数据,这个用手动清除吗?
java·后端·面试
南北是北北7 小时前
JetPack ViewBinding
面试
南北是北北7 小时前
jetpack ViewModel
面试
渣哥7 小时前
Lazy能否有效解决循环依赖?答案比你想的复杂
javascript·后端·面试
前端架构师-老李8 小时前
面试问题—你接受加班吗?
面试·职场和发展
ANYOLY9 小时前
多线程&并发篇面试题
java·面试
南北是北北9 小时前
RecyclerView 的数据驱动更新
面试