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.StateUI 表现数据源
任务排队ENQUEUED显示等待图标LiveData/Flow
执行中RUNNING显示进度条setProgress 更新
成功SUCCEEDED成功提示Result.success()
失败FAILED错误提示+重试按钮Result.failure()
最佳实践:使用 Flow 配合 Compose 时,建议用 stateIn(WhileSubscribed(5000)) 避免在配置变更时频繁重建订阅。

4.2 实现流程:从 Worker 到 Compose 的完整链路

  1. 定义 Worker:继承 CoroutineWorker,实现 doWork(),使用 setProgress 上报进度。
  2. 创建 OneTimeWorkRequest:设置输入数据、约束、重试策略。
  3. 在 ViewModel 中发起任务:调用 WorkManager.enqueue() 并保存 workId。
  4. ViewModel 暴露状态:使用 getWorkInfoByIdFlow(workId) 转换为 UI State。
  5. 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 状态流转与 Compose 联动 ENQUEUED RUNNING SUCCEEDED FAILED 重试 Compose 层收集 collectAsState() 映射 WorkInfo -> UI 组件 Worker 内部 setProgress 更新 Result.success/failure
核心结论:OneTimeWorkRequest + Flow + Compose 三者的结合,本质是 异步任务状态 → 响应式流 → 声明式UI 的数据驱动模式。掌握该模式后,可扩展到 PeriodicWorkRequest、监听多任务链、精确进度条等高级场景。