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,其实它支持传入一个列表。为什么强调这点?因为有时候你需要“并行执行几个前置任务,再统一走后续链路”。这里我画个图帮你理解:
实战代码:完整的链式任务示例
光说不练假把式。下面我写一个真实的场景:用户上传照片到服务器前,先做压缩,再做备份。要求压缩可以在有网络下执行,但上传必须走 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 匹配“最低合理要求”的约束,既不让用户多花流量,也不让任务无限期等待。这就是实战中我认为最优雅的平衡。