14、WorkManager与LiveData结合:观察任务状态的LiveData、在ViewModel中使用WorkManager、UI层与后台任务的状态同步
说实话,WorkManager 本身已经很强大了,但真正让它变得好用的,是它和 LiveData 的配合。你想想看,后台任务跑完了,UI 层怎么知道?总不能一直轮询吧?LiveData 就是那个桥梁,让后台任务的状态变化能自动通知到界面。
我个人习惯把 WorkManager 和 LiveData 的配合称为「被动观察模式」——你不需要主动去查任务状态,状态自己会来找你。这跟 Android 的响应式编程理念是一脉相承的。
WorkManager 自带的 LiveData 观察能力
WorkManager 的 API 设计者显然考虑到了这个问题。他们直接提供了返回 LiveData 的方法,你不需要自己封装。
// 获取某个任务的 LiveData 状态
val workInfoLiveData: LiveData<WorkInfo> =
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(workRequest.id)
// 或者根据唯一名称获取
val workInfosLiveData: LiveData<List<WorkInfo>> =
WorkManager.getInstance(context)
.getWorkInfosForUniqueWorkLiveData("my_unique_work")
我在项目中遇到过一个问题:多个任务同时运行时,状态回调会非常频繁。如果你在 Activity 里直接观察,很容易造成界面闪烁。后来我学乖了,把这些观察逻辑都放到了 ViewModel 里。
在 ViewModel 中使用 WorkManager
ViewModel 的生命周期比 Activity 长,它不会因为屏幕旋转而销毁。这就意味着,你的任务状态观察不会断掉。
class TaskViewModel(
private val workManager: WorkManager = WorkManager.getInstance(getApplication())
) : AndroidViewModel(application) {
// 暴露给 UI 层的状态
private val _taskState = MutableLiveData<TaskUiState>()
val taskState: LiveData<TaskUiState> = _taskState
fun startUploadTask(fileUri: Uri) {
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf("file_uri" to fileUri.toString()))
.build()
// 观察这个任务的状态变化
workManager.getWorkInfoByIdLiveData(uploadWork.id)
.observeForever { workInfo ->
// 这里做状态转换
_taskState.value = when (workInfo.state) {
WorkInfo.State.RUNNING -> TaskUiState.Uploading(
progress = workInfo.progress.getInt("progress", 0)
)
WorkInfo.State.SUCCEEDED -> TaskUiState.Success(
result = workInfo.outputData.getString("result")
)
WorkInfo.State.FAILED -> TaskUiState.Error(
message = workInfo.outputData.getString("error", "未知错误")
)
else -> TaskUiState.Idle
}
}
workManager.enqueue(uploadWork)
}
override fun onCleared() {
super.onCleared()
// 注意:这里需要移除观察者,避免内存泄漏
// 但 observeForever 需要手动管理
}
}
observeForever 时,一定要在 ViewModel 的 onCleared() 中移除观察者。否则 ViewModel 都销毁了,观察者还在,就会造成内存泄漏。我刚开始用的时候踩过这个坑,排查了好久。
UI 层与后台任务的状态同步
UI 层要做的事情其实很简单:观察 ViewModel 暴露出来的 LiveData,然后更新界面。
class TaskActivity : AppCompatActivity() {
private val viewModel: TaskViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_task)
// UI 层只需要观察,不需要关心 WorkManager 的细节
viewModel.taskState.observe(this) { state ->
when (state) {
is TaskUiState.Idle -> {
progressBar.visibility = View.GONE
statusText.text = "等待中"
}
is TaskUiState.Uploading -> {
progressBar.visibility = View.VISIBLE
progressBar.progress = state.progress
statusText.text = "上传中 ${state.progress}%"
}
is TaskUiState.Success -> {
progressBar.visibility = View.GONE
statusText.text = "上传成功: ${state.result}"
}
is TaskUiState.Error -> {
progressBar.visibility = View.GONE
statusText.text = "失败: ${state.message}"
}
}
}
}
}
你看,UI 层变得非常干净。它不需要知道 WorkManager 的存在,也不需要处理 Worker 的生命周期。这就是分层的好处——各司其职。
状态同步的核心流程图
下面这张图展示了整个数据流动的过程,我建议你仔细看一下,理解了这个流程,后面写代码就会很顺畅。
状态映射的最佳实践
在实际项目中,WorkManager 的状态和 UI 状态往往不是一一对应的。我建议你创建一个 UI 状态类来做映射,而不是直接把 WorkInfo 暴露给 UI 层。
// 定义 UI 层需要的状态
sealed class TaskUiState {
object Idle : TaskUiState()
data class Uploading(val progress: Int) : TaskUiState()
data class Success(val result: String) : TaskUiState()
data class Error(val message: String) : TaskUiState()
}
// 在 ViewModel 中做转换
private fun WorkInfo.toUiState(): TaskUiState {
return when (this.state) {
WorkInfo.State.ENQUEUED -> TaskUiState.Idle
WorkInfo.State.RUNNING -> TaskUiState.Uploading(
progress = this.progress.getInt("progress", 0)
)
WorkInfo.State.SUCCEEDED -> TaskUiState.Success(
result = this.outputData.getString("result", "")
)
WorkInfo.State.FAILED -> TaskUiState.Error(
message = this.outputData.getString("error", "未知错误")
)
else -> TaskUiState.Idle
}
}
避坑指南:生命周期与内存泄漏
我曾经在一个项目里犯过一个低级错误:在 Activity 里直接调用 observeForever,结果 Activity 销毁了观察者还在,导致内存泄漏。后来排查了好久才发现问题。
正确的做法是:
- 在 ViewModel 中使用
observeForever,并在onCleared()中移除观察者 - 在 Activity/Fragment 中使用
observe(),它会自动跟随生命周期 - 如果必须使用
observeForever,记得保存观察者引用,以便后续移除
// 正确的做法
class MyViewModel : ViewModel() {
private val observer = Observer<WorkInfo> { workInfo ->
// 处理状态变化
}
fun observeWork(workManager: WorkManager, id: UUID) {
workManager.getWorkInfoByIdLiveData(id)
.observeForever(observer)
}
override fun onCleared() {
super.onCleared()
// 一定要移除!不然会泄漏
// 但这里需要持有 LiveData 的引用才能移除
// 所以建议把 LiveData 也存起来
}
}
嗯,这里要注意:observeForever 的移除需要传入同一个 observer 对象。如果你用匿名内部类,那就没法移除了。所以一定要用变量保存起来。
总结一下
WorkManager 和 LiveData 的结合,说白了就是让后台任务的状态变化能自动通知到 UI 层。你不需要写一堆回调接口,也不需要担心生命周期问题。ViewModel 作为中间层,负责转换状态和协调生命周期,UI 层只管观察和更新。
我个人觉得,这种模式是 Android 官方架构组件里设计得最优雅的部分之一。它把复杂的东西藏起来了,留给开发者的就是一个简单的 LiveData 观察。你只要按照这个模式来写,代码会非常干净,也容易维护。
公众号:蓝海资料掘金营,微信deep3321