第3章 调度周期性任务:PeriodicWorkRequest
周期性任务在 Android 开发中非常常见:定期同步数据、上传日志、清理缓存、定时检查更新等。WorkManager 提供的 PeriodicWorkRequest 让你无需自行管理 AlarmManager、JobScheduler 或 Handler,就能以统一、健壮的方式调度重复执行的后台任务。
3.1 认识 PeriodicWorkRequest
PeriodicWorkRequest 封装了周期性执行的 Worker。它需要指定两个关键参数:重复间隔 和 灵活间隔(Flex Interval)。系统会根据设备状态和电量优化,在间隔窗口内选择一个合适的时间点执行 Worker。
| 参数 | 说明 | 最小值 |
|---|---|---|
repeatInterval |
两次执行的固定间隔时间 | 15 分钟(Android 12+);更低版本为 15 分钟硬限制 |
flexTimeInterval |
在重复间隔末尾的灵活窗口长度,系统可在此窗口内任意时间执行 | 5 分钟 |
核心关系: 如果
flexTimeInterval 等于 repeatInterval,则表示“尽可能准时”;如果小于间隔,则表示允许系统在最后一段窗口内灵活执行,从而节省电量。
最小间隔限制: 即使你设置
repeatInterval = 1 分钟,系统也会将间隔强制提升到 15 分钟。这是 Android 系统对 Battery Doze 和 JobScheduler 的限制。测试时务必使用至少 15 分钟的间隔。
3.2 与一次性任务的区别
| 维度 | 一次性任务(OneTimeWorkRequest) | 周期性任务(PeriodicWorkRequest) |
|---|---|---|
| 调度方式 | 执行一次后自动结束 | 按间隔持续重复,除非被取消或条件不满足 |
| 链式串联 | 支持 beginWith + then |
不支持串联;周期任务必须单独 enqueue |
| 初始延迟 | 可通过 setInitialDelay 设置 |
不直接支持;可通过 setInitialDelay 对第一次执行生效(需 API 21+) |
| 约束条件 | 支持所有约束(网络、电量、存储等) | 支持所有约束,但约束失败会导致下一次执行被推迟 |
| 生命周期 | 执行一次后进入 SUCCEEDED/FAILED |
每次执行结束可能进入 ENQUEUED(等待下一个周期)或 CANCELLED |
| 灵活间隔 | 无 | 可配置灵活窗口,让系统优化调度 |
3.3 实现流程
- 定义 Worker 子类,重写
doWork(),返回Result.success()或Result.retry()。 - 使用
PeriodicWorkRequestBuilder<MyWorker>(repeatInterval, TimeUnit, flexInterval, TimeUnit)构建请求。 - 可选设置约束(如网络连接、电量)、标签、初始延迟。
- 通过
WorkManager.getInstance(context).enqueueUniquePeriodicWork(name, ExistingPeriodicWorkPolicy, request)调度。
代码示例:构建一个每 1 小时(实际最小 15 分钟)同步数据的周期性任务
// SyncWorker.kt
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return try {
// 执行同步操作
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
// 调度周期任务
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
1, TimeUnit.HOURS, // repeatInterval
15, TimeUnit.MINUTES // flexInterval(最后 15 分钟窗口)
).apply {
setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
addTag("periodic_sync")
}.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"sync_work",
ExistingPeriodicWorkPolicy.KEEP, // 若已存在则保留
syncRequest
)
策略选择:
ExistingPeriodicWorkPolicy.KEEP 保持旧任务;REPLACE 用新任务替换(旧任务会被取消)。使用 REPLACE 时注意旧任务正在执行的问题。
3.4 生命周期管理
周期性任务的 WorkInfo 状态会反复在 ENQUEUED → RUNNING → ENQUEUED 之间循环,直到被取消或 Worker 返回 Result.failure()。每次执行结束后,Worker 实例会被销毁,下一个周期会创建新的 Worker 实例。
SVG 流程图:周期性任务调度与生命周期
3.5 常见错误与调试
- 任务不执行:检查 WorkInfo 状态。若为
ENQUEUED但未运行,通常是约束条件未满足(如无网络)。通过WorkManager.getWorkInfoByIdLiveData(uuid).observe(...)观察。 - 最小间隔踩坑:测试时设置 15 分钟间隔,等 15 分钟才能看到效果。不要用
1 分钟然后抱怨不执行。 - 重复 ID 冲突:使用
enqueueUniquePeriodicWork时,name 不一致会导致多个重复任务同时存在,消耗资源。 - Worker 被多次实例化:PeriodicWorkRequest 每个周期都会创建一个新的 Worker 对象,不要在 Worker 中持有 Activity Context 导致内存泄漏。
3.6 性能与稳定性优化
推荐策略: 尽可能使用较长的重复间隔(如 8 小时或每天),配合灵活窗口让系统批处理任务,减少唤醒次数。对时效性要求不高的同步任务,使用
flexInterval = repeatInterval 让系统自由调度。
- 使用
setBackoffCriteria(虽然 PeriodicWorkRequest 不支持直接设置,但 Worker 内返回Result.retry()时会遵循 OneTimeWorkRequest 的重试策略,注意此行为在周期任务中默认不生效;推荐改为返回Result.success()并记录失败次数,下次周期再重试)。 - 避免在
doWork()中执行耗时超过 10 分钟的操作,否则系统可能认为 Worker 超时并终止。 - 使用
WorkManager.cancelUniqueWork(name)释放不需要的周期任务。
重要: 不要依赖 PeriodicWorkRequest 的精确执行时间(如每秒或每分钟)。系统可能因省电、Doze 模式、待机状态延迟执行数小时。若需要准实时重复,请考虑前台服务 + Handler 或 AlarmManager(但要注意后台限制)。