任务链与并行任务:让WorkManager真正“串”起来

说实话,刚开始用WorkManager的时候,我总觉得它就是个“高级版的后台任务管理器”。直到有一天,我在做一个电商App的订单同步功能——需要先下载订单数据,再解析,再上传到本地数据库,最后通知UI更新。这一串操作如果拆成一个个独立任务,代码会变得又臭又长。嗯,这时候任务链就派上用场了。

今天我们就来聊聊WorkManager里最核心的编排能力:任务链(WorkChain)并行任务组合。说白了,就是让你能像搭积木一样,把多个任务串起来或者并起来跑。

1. beginWith()与then():最简单的任务链

先看最基础的情况。假设你有三个任务:A、B、C,必须按顺序执行。A做完才能做B,B做完才能做C。这在WorkManager里怎么表达?

val workA = OneTimeWorkRequestBuilder<DownloadWorker>()
    .build()
val workB = OneTimeWorkRequestBuilder<ParseWorker>()
    .build()
val workC = OneTimeWorkRequestBuilder<SaveWorker>()
    .build()

WorkManager.getInstance(context)
    .beginWith(workA)
    .then(workB)
    .then(workC)
    .enqueue()

这段代码看着是不是很直观?beginWith()启动链条,then()追加后续任务。我个人的习惯是,把beginWith()想象成“起跑线”,后面的then()就是接力棒。每个任务完成后,WorkManager会自动把结果传递给下一个。

关键点:beginWith()可以传入多个WorkRequest,表示并行开始。但then()只能串行追加。

2. 并行任务组合:WorkContinuation

但现实世界哪有这么简单?很多时候,我们需要同时做几件事。比如用户上传图片时,同时压缩图片、同时生成缩略图、同时上传到CDN。这三件事互不依赖,完全可以并行。

WorkManager提供了WorkContinuation来组合并行任务。看代码:

val compress = OneTimeWorkRequestBuilder<CompressWorker>()
    .build()
val thumbnail = OneTimeWorkRequestBuilder<ThumbnailWorker>()
    .build()
val upload = OneTimeWorkRequestBuilder<UploadWorker>()
    .build()

// 三个任务并行执行
val parallelWorks = WorkContinuation.combine(
    listOf(compress, thumbnail, upload)
)
parallelWorks.enqueue()

这里WorkContinuation.combine()接收一个WorkRequest列表,所有任务会同时启动。我在项目中遇到过一个问题:如果列表里有某个任务特别慢,整个combine会等它完成才继续。所以建议把耗时相近的任务放在一起,避免“木桶效应”。

小技巧:combine()返回的也是一个WorkContinuation,所以后面可以继续接.then()。比如并行做完压缩、缩略图、上传后,再通知UI更新。

3. 合并多个任务链

有时候,你会有多个独立的链条,最后需要合并成一个。举个例子:

  • 链条1:下载用户信息 → 解析用户信息
  • 链条2:下载用户头像 → 裁剪头像
  • 最后:合并用户信息和头像 → 保存到本地

这种场景下,WorkContinuation.combine()可以接收多个WorkContinuation对象:

val chain1 = WorkManager.getInstance(context)
    .beginWith(downloadUserInfo)
    .then(parseUserInfo)

val chain2 = WorkManager.getInstance(context)
    .beginWith(downloadAvatar)
    .then(cropAvatar)

// 合并两个链条
val merged = WorkContinuation.combine(
    listOf(chain1, chain2)
).then(saveToLocal)

merged.enqueue()

你看,combine()的参数可以是WorkContinuation列表,而不仅仅是WorkRequest列表。这样就能把任意复杂的链条组合起来。我曾经在做一个社交App的“发布动态”功能时,就用这个模式把图片处理、文字审核、位置获取三个链条合并,最后统一提交。

注意:合并多个链条时,每个链条的最后一个任务必须都成功完成,合并后的任务才会执行。任何一个链条失败,整个合并任务都会被标记为失败。

4. 任务链中的输入输出传递

任务链最强大的地方,就是数据可以在任务之间传递。每个Worker可以通过inputData接收数据,通过outputData输出数据。下一个任务会自动拿到上一个任务的输出作为输入的一部分。

看一个完整的例子:

// 任务A:下载订单数据
class DownloadOrderWorker(context: Context, params: WorkerParameters) 
    : Worker(context, params) {
    override fun doWork(): Result {
        val orderId = inputData.getString("order_id") ?: return Result.failure()
        // 模拟下载
        val rawData = "order_$orderId:item1,item2"
        val output = workDataOf("raw_data" to rawData)
        return Result.success(output)
    }
}

// 任务B:解析订单数据
class ParseOrderWorker(context: Context, params: WorkerParameters) 
    : Worker(context, params) {
    override fun doWork(): Result {
        val rawData = inputData.getString("raw_data") ?: return Result.failure()
        val items = rawData.split(":")[1].split(",")
        val output = workDataOf("items" to items.toTypedArray())
        return Result.success(output)
    }
}

// 构建链条
val download = OneTimeWorkRequestBuilder<DownloadOrderWorker>()
    .setInputData(workDataOf("order_id" to "12345"))
    .build()
val parse = OneTimeWorkRequestBuilder<ParseOrderWorker>()
    .build()

WorkManager.getInstance(context)
    .beginWith(download)
    .then(parse)
    .enqueue()

这里的关键是:DownloadOrderWorker输出的raw_data,会自动成为ParseOrderWorker的输入。你不需要手动传递,WorkManager帮你做了。

避坑指南:我曾经犯过一个错误——在Worker里用全局变量或单例来传递数据。结果任务被延迟执行或重启时,数据全丢了。记住:永远通过inputData/outputData传递数据,这是WorkManager保证数据持久化的唯一方式。

5. 知识体系总览

为了让你更直观地理解任务链和并行任务的关系,我画了一张图:

WorkManager 任务链与并行任务结构图 串行链条 任务A 任务B 任务C 并行任务 任务X 任务Y 任务Z 合并任务 链条合并 链1-A 链1-B 链2-A 链2-B

这张图展示了三种核心模式:串行链条(A→B→C)、并行任务(X、Y、Z同时执行后合并)、以及链条合并(两个独立链条最后汇合)。你可以把它当作一个速查表,写代码时对照着看。

6. 总结一下

任务链和并行任务,说白了就是WorkManager给你的“编排工具箱”。我个人觉得,掌握这三个要点就够了:

  • 串行用then():一个接一个,数据自动传递
  • 并行用combine():多个任务同时跑,等所有完成再继续
  • 数据用input/output:别偷懒用全局变量,Worker的生命周期你控制不了

嗯,这些基础概念搞懂了,后面我们才能聊更高级的话题——比如任务链的监听、唯一工作链、以及如何与LiveData/Flow结合。一步一步来,别急。

我的建议:刚开始接触任务链时,先从小链条练手。比如先写一个“下载→保存”的两步链条,跑通了再往上加并行。一口吃不成胖子,WorkManager的调试有时候挺磨人的。

公众号:蓝海资料掘金营,微信deep3321