WorkManager

WorkManager:保留第四版正文机制,以所有者—状态—结果合同、事件轨迹和章专属故障完成可重放验收。

学习目标

  • 能沿“WorkManager”的用户事件解释Android组件、状态所有者、线程与销毁边界。
  • 能围绕“使用 WorkManager 实现可靠的延迟后台任务:约束条件、任务链、输入输出、唯一任务,以及何时选择 WorkManager 而非 Thread 或 Service。”改出一个可运行结果,并用前后状态而非组件数量验收。
  • 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
  • 能用WorkRequest状态、约束切换、attempt与唯一工作队列独立重放结论,并标明第四版机制与现代targetSdk政策的边界。

为什么需要"可靠的后台任务"?

你的 App 需要在后台做一件事:把用户编辑的笔记同步到服务器。如果用 Thread { ... } 来做,用户刚点完保存就关了 App、切了网络——线程直接被杀,同步中断,笔记丢失。你怎么办?自己写一套"重试机制"、"任务持久化存储"、"网络状态监听"?

Android 生态历史上出现过一堆解决方案——JobScheduler、AlarmManager、Firebase JobDispatcher、甚至老掉牙的 AsyncTask——它们各有各的坑:有的 API 版本限制、有的不保证执行、有的只对 Google Play Services 用户有效。

就是为了终结这场混乱而生的——它把"可靠地安排后台任务该什么时候做"这件事统一封装。你的任务不丢、不重复、在条件满足时按时执行——即使 App 被杀死、设备重启后也能恢复。

WorkManager 的工作机制

核心三角:Worker + WorkRequest + WorkManager

// 1. Worker:定义"做什么"
class SyncWorker(context: Context, params: WorkerParameters)
    : Worker(context, params) {
    override fun doWork(): Result {
        return try {
            syncNotesToServer()          // 执行同步逻辑
            Result.success()             // 成功 → 任务结束
        } catch (e: Exception) {
            Result.retry()               // 失败 → WorkManager 按退避策略自动重试
        }
    }
}
 
// 2. WorkRequest:定义"什么时候做"
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)   // 需要网络
    .setRequiresCharging(true)                        // 需要充电
    .build()
 
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(constraints)
    .setInitialDelay(10, TimeUnit.MINUTES)           // 10 分钟后开始
    .build()
 
// 3. WorkManager:执行调度
WorkManager.getInstance(context).enqueue(syncRequest)

三者关系:Worker 定义任务逻辑(doWork),WorkRequest 定义任务的条件和时机(约束 + 延迟 + 重试),WorkManager 做统一调度(入队 + 选择执行时机 + 通知结果)。

WorkManager 生命周期:定义 → 约束 → 入队 → 保证执行 → 链式把「保证最终执行」交给系统:进程被杀 / 重启都不丢任务约束 Constraints(都满足才执行):需联网充电中设备空闲电量充足① 定义 Worker继承 Worker / CoroutineWorker重写 doWork():写后台逻辑返回 Result.success / retry / failure② 构建 WorkRequest + 约束OneTimeWorkRequest / PeriodicWorkRequestsetConstraints 设约束(见下方四件套)可设 setInitialDelay / 退避策略③ WorkManager.enqueue 入队WorkManager.getInstance(ctx).enqueue(req)把请求交给 WorkManager 统一调度④ 约束满足时调度执行持久化到内部 DB,保证最终执行约束都满足才跑 doWork()进程被杀 / 设备重启后仍自动恢复重跑⑤ 可链式:WorkContinuationbeginWith(A).then(B).then(C).enqueue()多个 Worker 顺序 / 并行编排前一段输出可作后一段输入WorkManager 的脾气可延迟但保证执行:不抢即时性,换可靠性尊重系统省电(Doze):低电 / 待机时顺延底层据 API 级别自动选 JobScheduler / AlarmManager
WorkManager 把「可靠后台任务」拆成五步:定义 Worker(重写 doWork()Result)→ 构建 WorkRequest 并设约束(联网 / 充电 / 空闲 / 电量)→ enqueue 入队 → 约束满足时调度执行(持久化到 DB,进程被杀 / 重启都保证最终执行)→ 可用 beginWith().then() 链式编排。它换的是 可靠性而非即时性,并尊重系统省电(Doze)。

OneTimeWorkRequest(执行一次)和 PeriodicWorkRequest(周期性重复,最小间隔 15 分钟)。

观察任务执行状态

WorkManager 提供 LiveData 接口让你观察任务进展:

WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(syncRequest.id)
    .observe(lifecycleOwner) { workInfo ->
        when (workInfo?.state) {
            WorkInfo.State.ENQUEUED -> showStatus("排队等待中...")
            WorkInfo.State.RUNNING -> showStatus("正在同步...")
            WorkInfo.State.SUCCEEDED -> showStatus("同步成功!")
            WorkInfo.State.FAILED -> showStatus("同步失败")
            WorkInfo.State.BLOCKED -> showStatus("等待前置任务完成...")
            else -> {}
        }
    }

动手看:一条 WorkRequest 的完整生命周期

猜一猜:如果用户在 SyncWorker 的 doWork() 执行期间杀掉了 App,同步会中断吗?

分步1 / 5

① 入队(ENQUEUED)

WorkManager.getInstance(context).enqueue(syncRequest) 把任务注册到内部数据库。这一步后任务已经持久化——即使 App 进程被杀、设备重启,WorkManager 在下次启动时会从数据库中恢复任务。

代码逐段拆解:任务链与数据传递

场景:先压缩图片,再上传到服务器

// 第 1 个 Worker:压缩图片
class CompressWorker(context: Context, params: WorkerParameters)
    : Worker(context, params) {
    override fun doWork(): Result {
        val imageUri = inputData.getString("IMAGE_URI") ?: return Result.failure()
        val compressedUri = compressImage(imageUri)
 
        // 输出压缩后的图片路径供下一个 Worker 使用
        val output = workDataOf("COMPRESSED_URI" to compressedUri)
        return Result.success(output)
    }
}
 
// 第 2 个 Worker:上传
class UploadWorker(context: Context, params: WorkerParameters)
    : Worker(context, params) {
    override fun doWork(): Result {
        val uri = inputData.getString("COMPRESSED_URI") ?: return Result.failure()
        return try {
            uploadToServer(uri)
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}
 
// 串联执行:先压缩,成功后再上传
val inputData = workDataOf("IMAGE_URI" to photoUri.toString())
 
val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
    .setInputData(inputData)
    .build()
 
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()
 
WorkManager.getInstance(context)
    .beginWith(compressRequest)     // 第一步:压缩
    .then(uploadRequest)            // 第二步:等压缩成功后上传
    .enqueue()

唯一任务:防止重复入队

WorkManager.getInstance(context)
    .enqueueUniqueWork(
        "daily-sync",                          // 唯一任务名
        ExistingWorkPolicy.REPLACE,            // 如果已存在同名任务,替换它
        syncRequest
    )

ExistingWorkPolicy 有三种策略:

  • REPLACE:取消已有任务,用新的替代
  • KEEP:如果已有任务在排队或执行,新的被丢弃
  • APPEND:把新任务加在已有任务后面(形成隐式链)

容易踩的坑

小结

  • WorkManager 解决"可靠调度":任务持久化在数据库中,进程被杀或设备重启后自动恢复
  • 核心三角:Worker(定义做什么)→ WorkRequest(定义时机和条件)→ WorkManager(统一调度)
  • OneTimeWorkRequest 一次执行,PeriodicWorkRequest 周期执行(最小间隔 15 分钟)
  • beginWith().then() 构建任务链,setInputData() + workDataOf() 传递数据
  • enqueueUniqueWork() 防止重复任务,LiveData + getWorkInfoByIdLiveData() 观察状态

练习

问题 1:“第27章 WorkManager”覆盖哪些正式节点和项目主线?

问题 2:怎样建立本页最小可执行实验?

问题 3:为什么只在正常点击路径运行不能证明完成?

问题 4:怎样设计能推翻当前实现的反例?

问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?

问题 6:本页达到独立交接标准需要什么?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

WorkManager

Android Jetpack 中专门做"可靠后台任务调度"的库。任务存储在内部数据库中,进程被杀或重启后自动恢复。支持约束条件(网络、充电、空闲状态)、任务链、唯一任务、状态观察。是 JobScheduler / AlarmManager / AsyncTask 等老方案的系统性替代。详见本章"WorkManager 的工作机制"一节。

Worker

定义后台任务逻辑的抽象类。你需要继承 Worker 并实现 doWork() 方法,返回 Result.success()Result.failure()Result.retry()doWork() 默认在后台线程执行,最长 10 分钟。详见本章"核心三角"一节。

OneTimeWorkRequest

只执行一次的后台任务请求。可以设置延迟(setInitialDelay)、约束条件(setConstraints)、输入数据(setInputData)。队列入队(enqueue)后由 WorkManager 统一调度执行时机。详见本章"核心三角"一节。

PeriodicWorkRequest

按固定时间间隔周期性重复执行的后台任务请求。最小间隔 15 分钟——这是 WorkManager 为防止过度耗电设置的系统下限。不保证精确执行时间,只在间隔窗口内选择一个合适时机执行。详见本章"容易踩的坑"一节。

“WorkManager”不使用未获授权的纸书正文;InformIT出版信息授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。

为什么“WorkManager”必须回到可观察状态

“WorkManager”的学习结果不是记住类名,而是能预测“使用 WorkManager 实现可靠的延迟后台任务:约束条件、任务链、输入输出、唯一任务,以及何时选择 WorkManager 而非 Thread 或 Service。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。

第四版机制逐项深读

27. WorkManager

在“WorkManager”中,分析“27. WorkManager”要记录WorkRequest ID、约束、attempt、输入输出与终态,Worker重试不应重复通知或写入。

Creating a Worker

在“WorkManager”中,“Creating a Worker”描述可延迟且必须最终执行的持久工作;约束决定何时可运行,唯一工作策略决定重复入队如何合并。

Scheduling Work

在“WorkManager”中,分析“Scheduling Work”要记录WorkRequest ID、约束、attempt、输入输出与终态,Worker重试不应重复通知或写入。

Checking for New Photos

在“WorkManager”中,验证“Checking for New Photos”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。

Notifying the User

在“WorkManager”中,“Notifying the User”不是精确定时器,也不适合立即UI工作;用户关闭轮询后必须取消唯一工作并验证队列为空。

Providing User Control over Polling

在“WorkManager”中,“Providing User Control over Polling”不是精确定时器,也不适合立即UI工作;用户关闭轮询后必须取消唯一工作并验证队列为空。

“WorkManager”验收回顾

“WorkManager”只有在WorkRequest状态、约束切换、attempt与唯一工作队列能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。

← 上一页:搜索 · 下一页:broadcast intent →

章专属可重放状态实验

先预测“启用轮询、网络变化、进程重启、重试、通知与关闭轮询”发生后,WorkManager 数据库、PollWorker 与用户设置应怎样改变WorkSpec、约束、attempt、唯一名称、结果和取消状态;再操作三个实验。第四版示例与当前 Android 政策分别记录,实验不把新 API 名称倒填为原书内容。

实验一:所有者—状态—结果合同

选择任一正式目录节点和正常/边界场景,检查它是否真的进入本章状态合同。目录标题只有同时出现在解释、可视状态和交付证据中才算覆盖。

Owner · state · observable result

WorkManager:状态合同

用 Worker、约束、唯一工作、通知与用户开关表达可靠可延期轮询

验证场景

第四版正式目录节点

workmanager · 正常任务

27. WorkManager固定 SDK、设备配置和初始状态,触发“启用轮询、网络变化、进程重启、重试、通知与关闭轮询”

状态所有者WorkManager 数据库、PollWorker 与用户设置
受控状态WorkSpec、约束、attempt、唯一名称、结果和取消状态
触发事件启用轮询、网络变化、进程重启、重试、通知与关闭轮询

冻结入口:27. WorkManager

记录WorkManager 数据库、PollWorker 与用户设置的初始WorkSpec、约束、attempt、唯一名称、结果和取消状态

观察:WorkInfo、约束切换、attempt、唯一队列、通知和取消测试中的“27. WorkManager”轨迹

预期:由WorkManager 数据库、PollWorker 与用户设置提交WorkSpec、约束、attempt、唯一名称、结果和取消状态,并持续满足“关闭后唯一工作不存在,重复启用不产生并行轮询”

实验二:事件与生命周期轨迹

沿五次转换逐步执行“启用轮询、网络变化、进程重启、重试、通知与关闭轮询”。每一步只允许WorkManager 数据库、PollWorker 与用户设置按职责提交状态,并持续核对“关闭后唯一工作不存在,重复启用不产生并行轮询”。

Deterministic event replay

WorkManager:事件轨迹

选择一次状态转换1 / 5

不变量:关闭后唯一工作不存在,重复启用不产生并行轮询

交付证据:WorkInfo、约束切换、attempt、唯一队列、通知和取消测试

实验三:章专属反例与同输入恢复

注入“把 PeriodicWorkRequest 当精确定时器,并重复入队多个同名任务”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有WorkInfo、约束切换、attempt、唯一队列、通知和取消测试一起恢复才算修复。

Fault · cancel · restore

WorkManager:反例与恢复

故障:把 PeriodicWorkRequest 当精确定时器,并重复入队多个同名任务

1. 冻结输入一致

第 1 次使用相同 SDK、设备配置、初始状态与用户事件

2. 注入边界一致

保持正常输入不变,仅注入“把 PeriodicWorkRequest 当精确定时器,并重复入队多个同名任务”

3. 检查所有者一致

关闭后唯一工作不存在,重复启用不产生并行轮询

4. 核对结果一致

WorkInfo、约束切换、attempt、唯一队列、通知和取消测试

讨论

评论区加载中…