WorkManager 核心三件套:Worker、WorkRequest 与 WorkManager 实例
讲依赖注入和 Mock 测试之前,得先把地基打牢。WorkManager 最核心的三样东西,说白了就是 Worker、WorkRequest 和 WorkManager 实例。我在项目中见过不少新手一上来就写代码,结果任务跑不起来,回头一看,这三者的关系根本没理清。嗯,今天我们就把它彻底掰开揉碎。
一句话总结三者的关系:
- Worker:干活的人,定义“做什么”
- WorkRequest:任务单,定义“什么时候干、有什么条件”
- WorkManager:调度中心,负责把任务单分发给干活的人,并管理整个生命周期
1. Worker——真正干活的家伙
Worker 是一个抽象类,你需要继承它并实现 doWork() 方法。我最早接触时以为它就是个普通的 Runnable,后来才发现它在后台运行在 WorkManager 管理的线程池里,生命周期有严格保障。
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// 这里是你的后台逻辑
val imageUri = inputData.getString("image_uri") ?: return Result.failure()
return try {
uploadImage(imageUri)
Result.success()
} catch (e: Exception) {
// 重试策略会自动处理
Result.retry()
}
}
}
个人经验:Worker 的构造器有两个参数——Context 和 WorkerParameters。我在项目中踩过一个坑:直接在构造器里做耗时操作!记住,doWork() 才是真正的工作入口,Worker 实例本身是初始化用的,别在里面搞网络请求。
Worker 有三种返回结果,记住它们就好:
| 返回类型 | 含义 | 行为 |
|---|---|---|
Result.success() |
任务执行成功 | WorkManager 标记完成,不再重试 |
Result.failure() |
任务永久失败 | WorkManager 放弃该任务,不会再重试 |
Result.retry() |
需要重试 | 根据 BackoffPolicy 配置的延迟时间重新调度 |
嗯,这里要注意一个常见的坑:Result.retry() 不是无限重试的,它有一个默认的最大次数限制。我曾经有个任务一直 retry,结果 Debug 模式下看起来正常,上线后用户反馈日志里全是失败——因为默认重试次数用完了,任务直接被标记为失败。
2. WorkRequest——任务配置单
Worker 定义了做什么,而 WorkRequest 定义了 什么时候做、有什么条件。它有点像 Android 的 Intent——你告诉系统你想干啥,但具体执行时机可能由系统决定。
WorkRequest 有两种风味:
// 一次性任务:执行一次,适合单次同步操作
val oneTimeRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf("image_uri" to "file:///..."))
.addTag("upload")
.build()
// 周期性任务:循环执行,适合定时检查
val periodicRequest = PeriodicWorkRequestBuilder<CheckWorker>(
15, TimeUnit.MINUTES // 最小间隔是 15 分钟
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
我曾经踩过的坑:刚接触 PeriodicWorkRequest 时,我天真地以为可以设置 5 秒的间隔。结果 Google 为了省电,强制最短周期为 15 分钟。你想想看,如果每隔几秒就跑一次后台任务,用户手机不得分分钟没电?所以官方设计是有道理的。
WorkRequest 最常用的配置项我整理了一下:
| 配置项 | 对应方法 | 说明 |
|---|---|---|
| 输入数据 | setInputData() |
通过键值对传递参数给 Worker |
| 执行约束 | setConstraints() |
如网络状态、充电状态、设备空闲等 |
| 重试策略 | setBackoffCriteria() |
线性或指数退避,默认 30 秒后重试 |
| 标签 | addTag() |
用于按标签取消或查询任务组 |
| 唯一标识 | setId() |
不设置会自动生成 UUID |
3. WorkManager 实例——调度中心
前面两个准备好了,总得有人来调度。WorkManager 是全局单例,通过 WorkManager.getInstance() 获取。但我得强调一点:千万别在 Application.onCreate() 里手动初始化,除非你需要自定义配置。
// 最常用的方式——自动初始化(默认配置)
WorkManager.getInstance(context)
// 如果你需要自定义配置
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// 自定义配置
val config = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.setWorkerFactory(MyWorkerFactory()) // 依赖注入的关键!
.build()
WorkManager.initialize(this, config)
}
}
为什么说 WorkManager.getInstance() 不是简单的事?
默认情况下,WorkManager 通过 ContentProvider 自动初始化。但一旦你牵扯到依赖注入(比如 Dagger/Hilt 或 Koin),你就需要手动设置 WorkerFactory。原因很简单:Worker 是通过反射创建的,系统默认的 WorkerFactory 不知道你的依赖怎么注入。嗯,这正是我们后面几章要重点解决的。
提交任务也很简单:
val workManager = WorkManager.getInstance(context)
workManager.enqueue(oneTimeRequest)
// 如果你有多个任务顺序执行
WorkManager.getInstance(context)
.beginWith(oneTimeRequest)
.then(nextRequest)
.enqueue()
这里有个细节:enqueue() 是异步的,它返回一个 Operation 对象,你可以监听完成状态。但说实话,大部分场景下你不需要关心它什么时候完成,交给 WorkManager 去管理就好——它会处理进程重启、设备重启等极端情况。
核心流程速览
把这三个东西串起来,你得到的就是整个 WorkManager 的工作流:
- 定义 Worker:继承 Worker,在
doWork()里写你的业务逻辑 - 构建 WorkRequest:用 Builder 创建任务单,配置约束和输入数据
- 获取 WorkManager 实例:调用
WorkManager.getInstance() - 入队执行:
workManager.enqueue(workRequest)
一个小技巧:如果你的项目从 RxJava 或 LiveData 迁移到 WorkManager,你会发现它天然支持协程——用 CoroutineWorker 替代普通的 Worker,doWork() 可以直接写成挂起函数,代码更简洁。我现在的项目基本都用 CoroutineWorker 了。