第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 实现流程

  1. 定义 Worker 子类,重写 doWork(),返回 Result.success() 或 Result.retry()。
  2. 使用 PeriodicWorkRequestBuilder<MyWorker>(repeatInterval, TimeUnit, flexInterval, TimeUnit) 构建请求。
  3. 可选设置约束(如网络连接、电量)、标签、初始延迟。
  4. 通过 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 流程图:周期性任务调度与生命周期

PeriodicWorkRequest 调度与生命周期 应用提交 PeriodicWorkRequest 约束满足? ENQUEUED(等待窗口) RUNNING(执行 Worker) 成功? (Result.success / retry / failure) ENQUEUED(下一周期) CANCELLED / FAILED 满足 灵活窗口到达 success / retry failure 取消任务 不满足→等待 关键说明 - 灵活窗口在间隔末尾 - 最小间隔 15 分钟

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(但要注意后台限制)。