4. 任务约束与链式任务:NetworkType 约束,用 beginWith/then 串联多个任务

还记得上一章我们聊到的 Constraints 吗?说白了,它就是 WorkManager 对任务执行条件的“把关人”。而在所有约束类型里,NetworkType 是我个人用得最多的一种。为什么呢?因为你想想看,大部分后台任务都离不开网络,比如上传日志、同步本地数据。要是用户正在用移动流量,你一个“上传几百兆视频”的任务直接跑起来,那用户体验……嗯,我怕被用户打。

所以今天我们就聚焦两件事:NetworkType 约束怎么配,以及怎么用 beginWith / then 把多个任务串成流水线。这两块结合起来,基本能覆盖你 80% 的后台任务编排需求。

NetworkType 枚举:你该选哪种网络条件?

NetworkType 是 androidx.work 包下的一个枚举,它决定了你的 Worker 在什么网络环境下才能执行。官方提供了 6 种值,但说实话,我项目中常用的就 4 种。我把它们整理了一下:

类型 含义 适用场景(我的经验)
NOT_REQUIRED 不限制网络状态,有网没网都行 本地数据库清理、缓存淘汰
CONNECTED 任何已连接的网络(Wi-Fi 或蜂窝数据) 常规同步、小数据量上传
UNMETERED 不计流量的网络(比如 Wi-Fi、以太网) 大文件上传、数据库备份
NOT_ROAMING 非漫游网络 跨境场景下的同步,避免漫游费
METERED 按流量计费的网络(比如蜂窝数据) 仅在流量下执行,需结合用户授意

我的一个习惯:只要是涉及用户流量的任务,我至少会加上 NetworkType.CONNECTED。如果上传量超过 10MB,我一定强制 UNMETERED。别问我为什么——曾经有个版本没做这个限制,结果用户反馈说“手机欠费了”,背锅背得不冤。

先看代码:给 Worker 加上网络约束

设置约束非常直接。你只需要在 OneTimeWorkRequest 的 Builder 里调用 setConstraints() 即可。看一段我常用的写法:

// 创建一个约束:仅 Wi-Fi 下执行
Constraints wifiConstraints = new Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .build();

OneTimeWorkRequest uploadRequest =
    new OneTimeWorkRequest.Builder(UploadWorker.class)
        .setConstraints(wifiConstraints)
        .build();

就这么简单。但真正有意思的是,当你需要多个任务按顺序执行时,怎么做?

beginWith + then:把任务串成链

WorkManager 提供了 beginWith 和 then 这两个方法,用来创建链式任务。你可以把它想象成“流水线”:第一个任务干完了,结果传给第二个,第二个干完再传第三个……直到整条链完成。

我记得刚开始接触时,总以为 beginWith 只能接受单个 WorkRequest,其实它支持传入一个列表。为什么强调这点?因为有时候你需要“并行执行几个前置任务,再统一走后续链路”。这里我画个图帮你理解:

链式任务与 NetworkType 约束的协作流程 Work A NetworkType: CONNECTED Work B NetworkType: UNMETERED Work C NetworkType: NOT_REQUIRED 约束如何生效? • 每个 Worker 独立设置自己的 NetworkType 约束。 • 执行时,WorkManager 会逐任务判断网络条件。 • 即使前一个任务满足了 UNMETERED,后一个任务如果设置 了 CONNECTED,就必须重新检查网络状态。 • 我不建议链式任务中混合不同的约束级别,容易导致延迟。 beginWith(WorkA).then(WorkB).then(WorkC) 形成有序链

实战代码:完整的链式任务示例

光说不练假把式。下面我写一个真实的场景:用户上传照片到服务器前,先做压缩,再做备份。要求压缩可以在有网络下执行,但上传必须走 Wi-Fi。来看看怎么写:

// 创建约束
Constraints compressConstraints = new Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build();

Constraints uploadConstraints = new Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .build();

// 任务定义
OneTimeWorkRequest compressRequest =
    new OneTimeWorkRequest.Builder(CompressWorker.class)
        .setConstraints(compressConstraints)
        .setInputData(new Data.Builder().putString("filePath", "/sdcard/photo.jpg").build())
        .addTag("compress")
        .build();

OneTimeWorkRequest uploadRequest =
    new OneTimeWorkRequest.Builder(UploadWorker.class)
        .setConstraints(uploadConstraints)
        .addTag("upload")
        .build();

OneTimeWorkRequest backupRequest =
    new OneTimeWorkRequest.Builder(BackupWorker.class)
        .setConstraints(new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build())
        .addTag("backup")
        .build();

// 链式编排
WorkManager.getInstance(context)
    .beginWith(compressRequest)          // 第一步:压缩
    .then(uploadRequest)                 // 第二步:上传(必须 Wi-Fi)
    .then(backupRequest)                 // 第三步:备份
    .enqueue();

关键点:链中的每个任务可以有自己的约束,互相独立。WorkManager 会等前一个任务成功完成后,再检查下一个任务的条件。如果网络不符合,任务会停在 ENQUEUED 状态,直到条件满足。

避坑指南:我曾经踩过的两个坑

第一个坑:链式任务中,如果某个 Worker 的约束一直不满足(比如用户三天没连 Wi-Fi),那么整条链都会被阻塞,后面的任务永远无法执行。我曾经以为“反正后一个任务约束更宽松”,实际却不是这样——每个任务只检查自己的约束,前一个没跑完,后一个根本不会启动。所以我现在会在任务里加一个超时机制,或者用 WorkManager.cancelUniqueWork() 兜底。

第二个坑:beginWith 可以传多个 WorkRequest,实现并行执行。但要注意:then 里的任务会等待所有前置任务都完成后才启动。比如 beginWith(A1, A2).then(B),B 会等 A1 和 A2 都结束才执行。这跟“串行链”是两回事,别搞混了。

注意:beginWith 和 then 返回的是 WorkContinuation 对象,你可以继续调用 then 多次。但是一旦调用了 enqueue(),就不允许再修改链了。如果你需要动态添加任务,建议先构建好完整的链,再一次性入队。

小结(其实是经验分享)

好,回到开头的问题。NetworkType 约束 + 链式任务,能帮我们把“该在什么时候做什么事”这件事理得清清楚楚。我个人会先把所有任务按依赖关系画成图(就像上面那个 SVG 一样),然后再动手写代码。这么做能减少不少调试时间。

嗯,最后送大家一句话:约束是写在代码里的,但用户的网络环境是动态的。尽量在链式任务中给每个 Worker 匹配“最低合理要求”的约束,既不让用户多花流量,也不让任务无限期等待。这就是实战中我认为最优雅的平衡。