任务状态与观察:WorkInfo 与 LiveData/Foreach
说实话,刚开始用 WorkManager 的时候,我踩过一个坑——任务提交了,但不知道它到底跑没跑完。那时候我还在用轮询的方式去查状态,现在想想真是又笨又累。后来我才发现,WorkManager 本身就提供了非常优雅的观察机制。
这一节,我们就来聊聊怎么实时“盯住”你的后台任务。
WorkInfo:任务的“体检报告”
每个 WorkManager 任务在执行过程中,都会生成一个 WorkInfo 对象。你可以把它理解成任务的“体检报告”——里面记录了任务的 ID、当前状态、输出数据、标签等信息。
我个人习惯,在调试阶段一定会打印 WorkInfo 的完整内容。它长这样:
WorkInfo {
id: 3a2f1b4c-...
state: SUCCEEDED
outputData: { "result": "success" }
tags: [ "sync_work", "user_123" ]
}
你看,状态、输出、标签一目了然。有了它,你就不需要自己维护任务状态了。
观察单个任务状态:LiveData 的妙用
WorkManager 提供了 getWorkInfoByIdLiveData() 方法。它返回一个 LiveData<WorkInfo>,你只要观察它,状态变化就会自动通知你。
举个例子:
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.build()
WorkManager.getInstance(context).enqueue(workRequest)
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(workRequest.id)
.observe(this, Observer { workInfo ->
if (workInfo != null) {
when (workInfo.state) {
WorkInfo.State.ENQUEUED -> Log.d("TAG", "任务已排队")
WorkInfo.State.RUNNING -> Log.d("TAG", "任务正在执行")
WorkInfo.State.SUCCEEDED -> Log.d("TAG", "任务成功")
WorkInfo.State.FAILED -> Log.d("TAG", "任务失败")
WorkInfo.State.CANCELLED -> Log.d("TAG", "任务已取消")
}
}
})
嗯,这里要注意:observe() 的第一个参数是 LifecycleOwner。如果你在 Activity 或 Fragment 里用,直接传 this 就行。它会自动在界面销毁时取消观察,不会造成内存泄漏。
Transformations.map() 或者直接过滤状态。我经常这么写:workInfo.state == WorkInfo.State.SUCCEEDED 时才触发 UI 更新。
观察多个任务状态:Foreach 与链式任务
有时候你需要同时观察多个任务。比如批量上传图片,每张图片都是一个独立任务。这时候 getWorkInfosByTagLiveData() 就派上用场了。
我习惯给同一类任务打上相同的标签:
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
.addTag("upload")
.build()
WorkManager.getInstance(context).enqueue(uploadWork)
WorkManager.getInstance(context)
.getWorkInfosByTagLiveData("upload")
.observe(this, Observer { workInfos ->
workInfos.forEach { workInfo ->
Log.d("TAG", "任务 ${workInfo.id} 状态: ${workInfo.state}")
}
})
你看,workInfos 是一个列表,用 forEach 遍历每个任务的状态。这在批量场景下特别实用。
还有一种情况是链式任务。比如先下载、再解压、最后通知用户。你可以观察整个链的最后一个任务:
val chain = WorkManager.getInstance(context)
.beginWith(downloadWork)
.then(unzipWork)
.then(notifyWork)
chain.enqueue()
chain.workInfosLiveData.observe(this, Observer { workInfos ->
// workInfos 包含链中所有任务的状态
})
workInfosLiveData 返回的是 LiveData<List<WorkInfo>>,每次任何一个任务状态变化,整个列表都会重新发射。如果你只关心最后一个任务,建议单独观察它的 ID。
状态转换图:ENQUEUED → RUNNING → SUCCEEDED/FAILED/CANCELLED
理解了怎么观察,我们还得知道状态是怎么流转的。说白了,一个任务的生命周期就是下面这张图:
这张图我建议你收藏。每次写 Worker 的时候,对照着看,思路会特别清晰。
简单总结一下:
- ENQUEUED:任务已提交,等待执行。此时可能受约束条件(如网络、电量)影响。
- RUNNING:任务正在执行。你的
doWork()方法正在跑。 - SUCCEEDED:任务成功完成。对应
Result.success()。 - FAILED:任务执行失败。对应
Result.failure()。 - CANCELLED:任务被取消。可以手动调用
cancelWorkById()触发。
避坑指南:我曾经踩过的坑
我曾经在观察任务状态时,忘记检查 workInfo 是否为 null。结果在任务还没开始的时候,直接访问 workInfo.state 导致空指针。后来我养成了习惯:每次观察都先判空。
还有一次,我在 Fragment 里观察任务状态,但 Fragment 已经销毁了,观察者还在。虽然 LiveData 会自动清理,但如果你用了 observeForever(),一定要在 onDestroy() 里手动移除。不然会出现内存泄漏。
嗯,这些细节看起来小,但真出了问题,排查起来很头疼。
小结
这一节我们聊了 WorkInfo 的结构、怎么用 LiveData 观察单个和多个任务状态,以及状态转换的完整流程。说白了,掌握了这些,你就能实时掌握后台任务的“脉搏”。
下一节,我们会深入 Worker 的内部,聊聊 doWork() 的返回值到底怎么影响任务链。到时候见。