4、单一后台任务与UI状态刷新:使用OneTimeWorkRequest执行简单任务,通过LiveData/Flow监听状态并驱动Compose更新
4.1 原理:WorkManager 任务生命周期与 UI 状态桥接
一次性后台任务(OneTimeWorkRequest)典型生命周期包括:ENQUEUED → RUNNING → SUCCEEDED/FAILED。WorkManager 提供 WorkInfo 的 LiveData/Flow,开发者通过观察该数据驱动 Compose 界面刷新。核心原理:
- WorkManager 内部维护状态机,Worker 执行过程中可更新进度(setProgress)和最终结果。
- LiveData<WorkInfo> 或 Flow<WorkInfo> 由 WorkManager 的
getWorkInfoByIdLiveData()/getWorkInfoByIdFlow()提供,具备自动解注册能力。 - Compose 的 collectAsState() 将 Flow 数据转换为 Compose State,实现声明式刷新。
| 生命周期阶段 | WorkInfo.State | UI 表现 | 数据源 |
|---|---|---|---|
| 任务排队 | ENQUEUED | 显示等待图标 | LiveData/Flow |
| 执行中 | RUNNING | 显示进度条 | setProgress 更新 |
| 成功 | SUCCEEDED | 成功提示 | Result.success() |
| 失败 | FAILED | 错误提示+重试按钮 | Result.failure() |
最佳实践:使用 Flow 配合 Compose 时,建议用
stateIn(WhileSubscribed(5000)) 避免在配置变更时频繁重建订阅。
4.2 实现流程:从 Worker 到 Compose 的完整链路
- 定义 Worker:继承
CoroutineWorker,实现doWork(),使用setProgress上报进度。 - 创建 OneTimeWorkRequest:设置输入数据、约束、重试策略。
- 在 ViewModel 中发起任务:调用
WorkManager.enqueue()并保存 workId。 - ViewModel 暴露状态:使用
getWorkInfoByIdFlow(workId)转换为 UI State。 - Compose UI 收集并渲染:通过
collectAsState()获取最新 WorkInfo,映射为界面元素。
// Step 1: 定义 Worker
class SampleWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setProgress(workDataOf("progress" to 0f))
for (i in 1..10) {
delay(1000) // 模拟耗时
setProgress(workDataOf("progress" to i / 10f))
}
return Result.success()
}
}
// Step 2: 创建 OneTimeWorkRequest
val workRequest = OneTimeWorkRequestBuilder<SampleWorker>()
.setInputData(workDataOf("key" to "value"))
.build()
// Step 3: ViewModel 中发起
class MainViewModel : ViewModel() {
private val workManager = WorkManager.getInstance(application)
private val _workId = MutableStateFlow<UUID?>(null)
val uiState: StateFlow<WorkInfo?> = _workId.flatMapLatest { id ->
id?.let { workManager.getWorkInfoByIdFlow(it) } ?: flowOf(null)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
fun startTask() {
val request = OneTimeWorkRequestBuilder<SampleWorker>().build()
workManager.enqueue(request)
_workId.value = request.id
}
}
// Step 4: Compose 收集
@Composable
fun TaskScreen(viewModel: MainViewModel) {
val workInfo by viewModel.uiState.collectAsState()
when (workInfo?.state) {
WorkInfo.State.ENQUEUED -> Text("等待执行")
WorkInfo.State.RUNNING -> {
val progress = workInfo!!.progress.getFloat("progress", 0f)
LinearProgressIndicator(progress)
}
WorkInfo.State.SUCCEEDED -> Text("任务完成")
WorkInfo.State.FAILED -> Text("失败")
else -> Button(onClick = { viewModel.startTask() }) { Text("开始任务") }
}
}
4.3 调试与日志
WorkManager 提供 WorkManager.enableDebugLogging(true)(仅调试构建)可在 logcat 输出详细状态变迁。常用 ADB 命令查看任务队列:
adb shell dumpsys jobscheduler | grep -A 20 "com.your.package"
# 或使用 WorkManager 的 Diagnostics
WorkManager.getInstance(context).diagnostics()
注意:调试时如果频繁触发任务,务必在 AndroidManifest 中声明
<provider android:name="androidx.work.impl.WorkManagerInitializer" android:enabled="false" />,并自行初始化 WorkManager,避免默认初始化与自定义配置冲突。
4.4 常见错误与避坑
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| 任务永远停留在 ENQUEUED | 未配置约束或违反约束(如无网络) | 检查 setConstraints 或移除不必要的约束 |
| UI 不刷新 | workId 未正确传递,或 Flow 未在 ViewModel 中持久化 | 使用 StateFlow 保存 workId,确保 collect 发生在 lifecycle 范围内 |
| 进度条不更新 | 本地进程内 WorkManager 版本限制 | 确保使用 CoroutineWorker,setProgress 需在 doWork 内调用 |
| 应用被杀后任务丢失 | 未设置 BackoffPolicy 或未返回 Result.retry() | 添加 setBackoffCriteria 并实现重试 |
4.5 性能与稳定性优化
- 避免频繁进度更新:setProgress 不应该在每毫秒调用,建议至少 200ms 间隔。
- WorkInfo 观察者生命周期:使用
WhileSubscribed(5000)防止快速配置变更带来的重复创建。 - 输入数据体积限制:WorkManager 限制 inputData 最大 10KB,过大数据应存入 Room 或 DataStore。
- 使用唯一工作链:对于同一类型的任务,使用
beginUniqueWork避免重复排队。
4.6 状态联动流程示意图
核心结论:OneTimeWorkRequest + Flow + Compose 三者的结合,本质是 异步任务状态 → 响应式流 → 声明式UI 的数据驱动模式。掌握该模式后,可扩展到 PeriodicWorkRequest、监听多任务链、精确进度条等高级场景。