第2章 后台任务模拟实现:WorkManager、Coroutine、Handler三种方式对比与代码实现

本章深入剖析 Android 中三种主流后台任务触发方式——WorkManager、Coroutine 和 Handler。在 Compose 自动化测试场景中,后台任务的结果需要准确传递到 UI 层并触发重组,理解三种方式的底层机制和代码实现是编写可靠 Espresso 测试的前提。

2.1 三种方式的核心原理

方式 核心原理 生命周期感知 典型适用场景
WorkManager 基于 JobScheduler / AlarmManager 的队列式任务调度,支持持久化、延迟、重试、链式任务 完全感知(强制关联 LifecycleOwner) 一次性的或周期性的数据同步、日志上传、数据库清理
Coroutine 轻量级协程调度器,在 ViewModel 或 Activity 中通过 viewModelScope 或 lifecycleScope 启动 通过 LifecycleScope 自动取消 网络请求、数据库操作、耗时计算,需要直接更新 Compose State
Handler 将 Runnable 投递到主线程 Looper 的消息队列,实现线程切换 需手动管理移除回调 简单延迟任务、主线程更新 UI、与旧版 View 系统交互

2.2 实现流程与代码示例

2.2.1 WorkManager 实现

  1. 定义 Worker 类,重写 doWork()
  2. 创建 WorkRequest 并 enqueue
  3. 通过 LiveData / Flow 监听结果

示例:模拟后台任务并输出结果

// Worker 定义
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 模拟耗时任务
        Thread.sleep(2000)
        val result = "WorkManager 完成: ${System.currentTimeMillis()}"
        // 通过 outputData 传递结果
        val output = workDataOf("result" to result)
        return Result.success(output)
    }
}

// 在 ViewModel 中启动
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .build()
WorkManager.getInstance(context).enqueue(workRequest)

// 监听结果(在 Activity/Fragment 或 Compose 中)
WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(workRequest.id)
    .observe(lifecycleOwner) { info ->
        if (info.state == WorkInfo.State.SUCCEEDED) {
            val result = info.outputData.getString("result")
            // 更新 Compose State
        }
    }

2.2.2 Coroutine 实现

  1. 在 ViewModel 中使用 viewModelScope.launch
  2. 切换到 Dispatchers.IO 执行后台逻辑
  3. 完成后切换回主线程更新 MutableState
// 在 Compose ViewModel 中
class MyViewModel : ViewModel() {
    private val _result = mutableStateOf("")
    val result: State<String> = _result

    fun simulateTask() {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                delay(2000) // 模拟后台任务
                "Coroutine 完成: ${System.currentTimeMillis()}"
            }.let { output ->
                _result.value = output // 直接触发 Compose 重组
            }
        }
    }
}

2.2.3 Handler 实现

  1. 获取主线程 Handler
  2. 在子线程执行任务后 post Runnable
  3. 在 Runnable 中更新 Compose 可观察变量
// 在 Activity 或 Fragment 中使用
private val mainHandler = Handler(Looper.getMainLooper())

fun simulateTaskWithHandler() {
    Thread {
        // 模拟后台工作
        Thread.sleep(2000)
        val result = "Handler 完成: ${System.currentTimeMillis()}"
        // 回主线程更新 UI
        mainHandler.post {
            // 更新 composeState(假设持有引用)
            composeState.value = result
        }
    }.start()
}

2.3 三种方式对比总结

对比维度 WorkManager Coroutine Handler
生命周期管理 自动(WorkManager 绑定 lifecycle) 自动(LifecycleScope) 手动(需在 onDestroy 移除 Callback)
延迟/重试 内置延迟、退避策略 需自定义 delay 与 try-catch 仅延迟(postDelayed)
持久化 数据库持久化,进程重启可恢复 不持久化 不持久化
触发 Compose 刷新 通过 LiveData/Flow 转化为 State 直接更新 MutableState 需手动更新可观察对象
测试复杂度(Espresso) 需要 TestListenableWorker 或等待 WorkInfo 使用 runBlockingTest 或 advanceUntilIdle 使用 IdlingResource 或等待 Handler 执行

选择建议:对于 Espresso 测试中需要精确控制后台任务完成时刻的场景,优先使用 Coroutine + 协程测试规则;若任务需要保证系统重启后继续执行(如日志上报),选 WorkManager;仅作简单延迟或旧版代码兼容时保留 Handler。

2.4 集成流程示意图

三种后台任务触发 Compose 刷新流程 启动后台任务 WorkManager doWork() → Result Coroutine viewModelScope.launch Handler post(Runnable) 触发 Compose 刷新 State 变化 → 重组 Espresso 验证通过 三种方式最终都通过更新 Compose State 触发重组,Espresso 测试需要等待重组完成后再断言 UI 结果。

2.5 常见错误与调试

  • WorkManager 任务未被触发:检查是否调用了 enqueue,并且 Android 12+ 需要 SCHEDULE_EXACT_ALARM 权限(若使用精确延迟)。
  • Coroutine 未更新 UI:确认使用的是 MutableState 而非 LiveData,或者使用 collectAsState 正确转换。
  • Handler 回调泄漏:在 Activity/Fragment 销毁时调用 handler.removeCallbacksAndMessages(null),否则可能导致内存泄漏或空指针。
  • Espresso 测试中超时:三种方式均需等待异步任务完成。WorkManager 使用 idlingResource,Coroutine 使用 runBlockingTest,Handler 使用 AdvanceTimeRule 或自定义 IdlingResource。

性能提示:在单元测试中避免使用真实的 Handler 延迟(postDelayed),否则测试会挂起。使用 HandlerThreadIdlingResource 或直接替换为 CoroutineDispatcher 替代方案。对于 WorkManager,在测试中应使用 TestWorker 模拟同步执行。

2.6 部署与集成建议

在实际项目中,推荐将后台任务逻辑封装在 Repository 层,ViewModel 通过统一接口调用。WorkManager 用于需要保证完成的作业;Coroutine 用于 UI 相关的异步操作;Handler 仅用于遗留系统或极简场景。测试时,通过依赖注入替换为测试 Double(如 FakeWorker 或 TestDispatcher),确保 Espresso 测试稳定且快速。