2. 约束基础
2.1 什么是约束
约束,说白了就是 WorkManager 给任务定的一组“执行条件”。你告诉它:这个任务必须等到满足某几个条件才能跑。比如网络要可用、电量不能太低、最好在充电…… 把这些条件打包成一个 Constraints 对象,传给 WorkRequest.Builder 就完事。
我个人习惯把约束看成任务的“安检门”。任务不会一到队列就立刻执行,得先过一遍安检——所有条件都绿灯了,WorkManager 才放它出去干活。这跟 Android 传统后台模型里的 BroadcastReceiver 加 IntentService 那种“手动监听”完全不同,WorkManager 替你管了这套逻辑,省心得多。
2.2 为什么需要约束
你可能会想:任务直接跑不就完了?加这些条条框框不是自找麻烦吗?
我之前接手过一个耗电严重的项目,里面有个同步任务每 15 分钟唤醒一次 CPU,用户反馈手机半天就没电了。后来加上 BatteryNotLow 和 Charging 约束,电量低于 20% 或者不在充电时任务根本不启动。改完之后用户投诉直线下降。你看,约束看似多此一举,其实是为了保护用户体验和设备资源。
再举一个例子:上传日志如果没限制网络类型,用户用移动网络时可能瞬间耗尽流量。加了 NetworkType.UNMETERED 后,只走 Wi-Fi,用户再也不会骂你“偷流量”了。
所以核心就三点——
- 省电:避免在低电量或非充电时做高耗能操作
- 省流量:把大流量任务限制在 Wi-Fi 下
- 保证成功率:比如上传文件时网络不可用就会失败,约束能推迟到网络恢复再重试
记住:约束是 WorkManager 帮你“延迟执行”的理由,但不是“不执行”的理由。只要条件一直不满足,任务会永远挂在队列里,直到满足为止(或超时)。
2.3 约束的常见类型
WorkManager 默认提供了 6 种约束,我把它们整理成一张表,方便你快速查阅:
| 约束类型 | Builder 方法 | 可选值 | 说明 |
|---|---|---|---|
| 网络类型 | setRequiredNetworkType() |
CONNECTED, UNMETERED, NOT_ROAMING, METERED |
限制任务只能在特定网络下运行。比如只走 Wi-Fi 用 UNMETERED |
| 电池电量不低 | setRequiresBatteryNotLow() |
true / false |
电量低于 20% 时不启动任务 |
| 充电状态 | setRequiresCharging() |
true / false |
只在设备连接充电器时执行 |
| 设备空闲 | setRequiresDeviceIdle() |
true / false |
设备处于空闲(Doze 模式)时执行,适合低优先级后台同步 |
| 存储空间不低 | setRequiresStorageNotLow() |
true / false |
可用存储低于 10% 时不启动 |
| 内容 URI 触发 | addContentUriTrigger() |
一组 Uri + 监听 Flag |
当指定的 ContentProvider 数据发生变化时触发任务(API 24+) |
嗯,这里要注意一点:setRequiresDeviceIdle() 听起来很美好,但它只在设备进入 Doze 模式并且屏幕关闭、未充电时才触发。我在某个项目中用它做夜间数据预取,结果用户白天打开应用数据始终是旧的——因为白天设备很少进入空闲状态。所以不要过度依赖这个约束,除非你确定任务可以忍受长时间延迟。
💡 约束可以组合使用。比如同步任务既要 Wi-Fi 又要充电,就直接设置两个条件,WorkManager 会帮你做“与”逻辑——所有条件同时满足才执行。
2.4 如何添加约束
代码非常简单。先构建 Constraints 对象,然后注入到 WorkRequest 里:
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(MyWorker.class)
.setConstraints(constraints)
.build();
WorkManager.getInstance(context).enqueue(work);
这串代码跑起来之后,任务会在设备连接 Wi-Fi、正在充电、并且电量不低时才开始。缺一个都不行。
⚠️ 注意:setRequiresDeviceIdle() 和 addContentUriTrigger() 需要 API 23+ 或 API 24+,低版本设备上这两个约束会被忽略。建议配合 @RequiresApi 注解使用,或者在运行时检查版本。
2.5 约束的执行流程
为了帮你直观理解,我画了一张图,把约束和任务的调度关系梳理出来:
从图里你也能看出来,约束之间是平级的,没有优先级差别。组合使用时,WorkManager 会持续监听每个条件的状态变化。比如网络从 Wi-Fi 切到移动数据,任务会被暂停,等网络重新满足条件后再继续。整个过程对开发者完全透明。
2.6 避坑经验
最后分享几个我在项目中踩过的坑:
- 别滥用 Idle 约束。我之前想做一个“夜间整理”任务,给所有工作都加了
setRequiresDeviceIdle(true),结果用户白天打开 App 时数据缓存一直没更新。因为设备几乎不会在白天进入空闲状态。后来我把这个约束只留给非紧急的本地数据清理,其他任务用更宽松的条件。 - ContentUriTrigger 要小心权限。如果你监听的是其他应用的内容 URI,需要确保有对应的读取权限。我遇到过监听系统媒体库变化时忘记声明
READ_EXTERNAL_STORAGE,结果触发了一堆失败日志。 - 约束不是强保证。系统可能会因为低内存等原因杀掉进程,即使约束满足。所以任务本身要做幂等处理,Worker 里的代码要能安全重试。