4. 后台任务基础概念:WorkManager 适用场景、任务类型与约束条件
Android 提供了多种后台执行方案,但在保证省电、兼容不同系统版本、避免被系统强制杀死的需求下,WorkManager 成为官方推荐的首选。本章将剖析 WorkManager 的定位、任务分类以及约束机制,为后续结合 Room 数据库的实操打下坚实基础。
4.1 适用场景:何时选择 WorkManager
WorkManager 适用于可延迟、需保证完成的后台任务。它与应用生命周期解耦,即使应用被用户关闭或设备重启,任务仍能被调度并执行。
典型场景
- 数据同步:定期将本地数据库(Room)的未上传记录同步至服务器。
- 日志上传:在设备连接 Wi-Fi 且电量充足时批量上传崩溃日志。
- 缓存清理:每天凌晨清理过期的图片缓存。
- 预取内容:在空闲时预先下载用户可能浏览的新闻列表。
| 对比项 | 前台服务 | JobScheduler | WorkManager |
|---|---|---|---|
| 系统兼容性 | API 1+ | API 21+ | API 14+(通过兼容包) |
| 持久化保证 | 不保证 | 设备重启后任务可能丢失 | 数据持久化至 Room,重启后恢复 |
| 约束条件 | 需手动检查 | 内置网络/充电约束 | 网络、充电、空闲、存储等丰富约束 |
| 延迟执行 | 不支持 | 支持最小延迟 | 支持最小延迟 + 灵活重试策略 |
4.2 任务类型:OneTimeWorkRequest 与 PeriodicWorkRequest
WorkManager 将任务分为两种基本类型,分别对应一次性和周期性的需求。
4.2.1 OneTimeWorkRequest(一次性任务)
只执行一次,适用于“上传单条日志”、“执行一次数据清洗”等场景。
// 创建一次性上传任务
OneTimeWorkRequest uploadWork =
new OneTimeWorkRequest.Builder(UploadWorker.class)
.setInitialDelay(10, TimeUnit.MINUTES) // 延迟 10 分钟后执行
.setConstraints(constraints) // 设置约束条件
.addTag("upload")
.build();
WorkManager.getInstance(context).enqueue(uploadWork);
4.2.2 PeriodicWorkRequest(周期性任务)
按固定间隔重复执行,适用于“每天同步一次数据库”、“每 15 分钟检查一次新消息”等场景。
注意:系统为了省电会将多个周期任务合并,实际执行时间可能偏离设置值。
// 创建每天执行一次的同步任务
PeriodicWorkRequest syncWork =
new PeriodicWorkRequest.Builder(SyncWorker.class, 24, TimeUnit.HOURS)
.setConstraints(constraints)
.addTag("sync")
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily_sync",
ExistingPeriodicWorkPolicy.KEEP,
syncWork
);
| 参数 | OneTimeWorkRequest | PeriodicWorkRequest |
|---|---|---|
| 执行次数 | 1 次 | 无限循环(需手动取消) |
| 最小间隔 | 无限制 | 15 分钟(实际可能更长) |
| 延迟设置 | setInitialDelay | 不支持(固定间隔) |
| 重试策略 | 背压、指数退避 | 下一次窗口重新尝试 |
4.3 约束条件设置:让任务在合适时机执行
约束条件(Constraints)是 WorkManager 的核心特性,它允许开发者声明任务运行的环境要求,避免在弱网、低电量等恶劣条件下抢占资源。
4.3.1 可用约束类型
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络连接
.setRequiresBatteryNotLow(true) // 电池不能过低
.setRequiresCharging(true) // 正在充电
.setRequiresDeviceIdle(true) // 设备处于空闲状态(API 23+)
.setRequiresStorageNotLow(true) // 存储空间充足
.build();
| 约束条件 | 方法签名 | 说明 |
|---|---|---|
| 网络类型 | setRequiredNetworkType(NetworkType) |
NOT_REQUIRED / CONNECTED / UNMETERED / NOT_ROAMING / METERED |
| 电量不低于阈值 | setRequiresBatteryNotLow(true) |
避免在电量低于 15% 时执行 |
| 充电状态 | setRequiresCharging(true) |
仅在连接充电器时执行 |
| 设备空闲 | setRequiresDeviceIdle(true) |
API 23+;设备待机且屏幕关闭时执行 |
| 存储空间 | setRequiresStorageNotLow(true) |
内置存储剩余空间充足 |
约束冲突处理:若任务同时设置网络连接和充电约束,则两个条件必须同时满足才会执行。WM 会等待所有条件满足,不会因条件不满足而取消任务。
4.3.2 调试约束状态
在开发阶段可以通过 WorkManager.getWorkInfoByIdLiveData() 观察任务状态,确认约束是否触发。
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(uploadWork.getId())
.observe(this, workInfo -> {
if (workInfo != null) {
Log.d("WM_Debug", "状态: " + workInfo.getState());
Log.d("WM_Debug", "约束是否满足: " + workInfo.getState().isFinished());
}
});
4.4 任务流程与状态机
下面的 SVG 展示了一个 WorkRequest 从创建到完成的完整生命周期,以及 OneTimeWork 和 PeriodicWork 的分支路径。
4.5 常见错误与调试
错误 1:约束设置过于严苛导致任务永不执行
例如同时设置
例如同时设置
CONNECTED、充电、空闲 三项。在模拟器中测试时,需手动满足所有条件,否则任务将一直处于 ENQUEUED 状态。建议开发阶段先用 Constraints.NONE 验证业务逻辑,再逐步添加约束。
// 调试:查看排队中的任务
adb shell dumpsys jobscheduler | grep "your.package.name"
4.6 性能与稳定性优化建议
- 避免短周期任务:PeriodicWorkRequest 最小间隔为 15 分钟,若需更短间隔,应结合前台服务或 AlarmManager。(WorkManager 官方不推荐小于 15 分钟)
- 使用唯一任务策略:对于同类型任务,使用
enqueueUniqueWork或enqueueUniquePeriodicWork防止重复排队。 - 合理设置重试策略:对于网络相关的任务,使用
BackoffPolicy设置指数退避,避免频繁重试消耗电量。
// 设置重试策略:线性退避,最多重试 3 次
OneTimeWorkRequest retryWork = new OneTimeWorkRequest.Builder(UploadWorker.class)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();