4. Mockito核心用法
4.1 核心原理:并行入口与串联出口
在WorkManager的链式调度中,beginWith()是构建任务链的唯一入口方法。当传入多个WorkRequest时,这些任务会被系统安排为并行执行,且只有所有并行任务均成功完成后,后续的then()任务才会被触发。这种“并行入口 + 串联出口”的组合机制是构建高效异步工作流的基础。
| 调用方式 | 行为描述 | 典型场景 |
|---|---|---|
beginWith(singleWork) | 单一任务启动,顺序执行then()链 | 线性依赖步骤 |
beginWith(listOfWork) | 多任务并行启动,所有任务完成后继续then() | 并行数据下载、并行预处理 |
beginWith() + then() + then() | 并行入口之后串联任意数量的顺序任务 | 并行下载→统一解析→入库 |
4.2 实现流程:真正的并行合并是怎样工作的
假设需要完成“并行下载图片A和图片B → 合并生成缩略图 → 上传结果”三个步骤。WorkManager通过以下内部机制保证并行与合并的正确执行:
- 任务图构建:
beginWith()会创建一个WorkContinuation对象,内部维护一个DAG(有向无环图)。 - 并行调度:传入的多个WorkRequest被添加为同层节点,没有先后依赖关系,WorkManager线程池允许同时执行。
- 合并等待:
then()在添加后续任务时,会自动将所有前驱节点的父级依赖合并。即后续任务需要等待前驱节点的“所有并行任务”都完成后才进入执行队列。 - 并行+串联组合:可以在任意位置使用
then()插入顺序任务,也可以在then()之后再次使用beginWith()类型的合并(通过WorkContinuation.combine())。
提示:并行任务组中所有任务共享同一个ListenableWorker的生命周期约束。如果其中任何一个任务返回
Result.failure(),整个continuation会被标记为失败,且后续then()任务不会执行。4.3 代码示范:beginWith多任务与then串联
以下是一个完整的Kotlin示例,展示如何创建三个并行Worker,然后串联一个合并Worker:
// 1. 定义三个并行Worker(模拟图片下载、数据预处理、缓存清理)
val downloadWorker = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(workDataOf("url" to "https://example.com/a.jpg"))
.build()
val preprocessWorker = OneTimeWorkRequestBuilder<PreprocessWorker>()
.setInputData(workDataOf("mode" to "thumbnail"))
.build()
val cacheCleanWorker = OneTimeWorkRequestBuilder<CacheCleanWorker>()
.build()
// 2. 定义合并处理Worker(仅在上述三个都成功后执行)
val mergeWorker = OneTimeWorkRequestBuilder<MergeWorker>()
.build()
// 3. 构建并行入口
val workContinuation = WorkManager.getInstance(context)
.beginWith(listOf(downloadWorker, preprocessWorker, cacheCleanWorker))
.then(mergeWorker)
// 4. 观察合并前后的状态
workContinuation.enqueue()
// 5. (可选) 进一步串联后续任务
val uploadWorker = OneTimeWorkRequestBuilder<UploadWorker>().build()
workContinuation.then(uploadWorker).enqueue()
4.4 实际效果:并行与串行的可视化对比
下方SVG流程图直观展示了beginWith(三个并行任务) → then(一个合并任务)的执行顺序:
4.5 调试与日志观察
在调试模式下,可以通过WorkManager的WorkContinuation.debug()方法观察任务图的依赖关系。推荐使用以下ADB命令过滤并行任务状态:
adb logcat -s WorkManager:V
# 或过滤所有Worker状态
adb logcat | grep -E "WorkProgress|WorkerStatus|beginWith|then"
典型日志输出会显示三个并行任务几乎同时被
4.6 常见错误与避坑指南
- 任务过多导致线程池满:WorkManager默认线程池大小为最大1个CPU核心+1。如果并行任务超过16个,可能造成死等待。建议根据设备性能将并行任务数量控制在8-12个以内。
- 依赖不清晰:beginWith传入的任务之间如果有隐式数据依赖,必须使用
then()显式串联,否则并行会导致竞态条件。 - 结果合并失败:使用
combine()进行多个Continuation的合并时,务必确保每个分支都正确调用了.enqueue(),否则合并永远不会触发。 - 错误的使用then后再次beginWith:在一个Continuation上连续调用beginWith不会重置任务图,会抛出
IllegalStateException。应使用WorkContinuation.combine()实现分支合并。
4.7 性能优化建议
| 场景 | 建议 | 原因 |
|---|---|---|
| 并行任务组很大(>10) | 拆分为多个小并行组,使用then串联 | 避免线程池资源竞争导致的延迟抖增 |
| 并行任务存在IO密集型 | 在Worker内部使用自定义线程池 | WorkManager默认线程池偏重CPU任务 |
| 需要合并多个并行结果 | 使用combine()而不是手动等待 | 保持WorkManager生命周期管理的一致性 |
优化验证:使用
WorkManager.getWorkInfoByIdLiveData()观察每个Worker的执行时长。如果并行组中多个Worker的总执行时间超过单个Worker时长+20%,则说明线程池争用严重,应该降低并行度或启用keepResultsForAtLeast持久化策略。