延迟执行与重试策略:让后台任务更智能
后台任务调度,说白了就是让 App 在合适的时间干合适的活。但现实往往没那么简单——网络可能不稳定,服务器可能暂时抽风,用户可能正在用手机打游戏。这时候,延迟执行和重试策略就成了我们的救命稻草。
我个人习惯把 WorkManager 的调度策略比作「闹钟+保险丝」的组合。闹钟决定什么时候响,保险丝决定出问题了怎么补救。今天我们就来拆解这两个核心机制。
初始延迟设置:让任务「等一下」
先说说 setInitialDelay。这个 API 的作用很简单——让任务在指定的时间之后才开始执行。我在项目中遇到过这样一个场景:用户上传照片后,我们希望在 5 分钟后才开始压缩处理,避免影响用户当前的操作体验。
val workRequest = OneTimeWorkRequestBuilder<CompressWorker>()
.setInitialDelay(5, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
嗯,这里要注意:setInitialDelay 只对 OneTimeWorkRequest 有效。对于 PeriodicWorkRequest,你需要用 setInitialDelay 来设置第一次执行的延迟时间,但后续的周期执行不受影响。
setInitialDelay 硬算时间差。我建议用 setInitialDelay 配合 Constraints 来实现更精准的调度。
重试与退避策略:失败后的优雅处理
任务执行失败了怎么办?直接放弃?还是立刻重试?WorkManager 给了我们一套非常灵活的重试机制。
首先,要让任务支持重试,你需要在 Worker 的 doWork() 方法中返回 Result.retry():
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return try {
// 执行上传操作
uploadFile()
Result.success()
} catch (e: IOException) {
// 网络异常,需要重试
Result.retry()
}
}
}
但问题来了:如果网络一直不好,任务会无限重试下去吗?当然不会。WorkManager 默认有重试次数限制,而且每次重试之间会有间隔——这就是退避策略。
线性退避 vs 指数退避
WorkManager 提供了两种退避策略:
| 策略 | 说明 | 重试间隔 | 适用场景 |
|---|---|---|---|
BackoffPolicy.LINEAR |
线性退避 | 每次增加固定时间 | 临时性故障(如服务器短暂过载) |
BackoffPolicy.EXPONENTIAL |
指数退避 | 每次翻倍增长 | 持续性故障(如网络断开) |
举个例子你就明白了。假设初始延迟是 10 秒:
- 线性退避:第 1 次重试等 10 秒,第 2 次等 20 秒,第 3 次等 30 秒……
- 指数退避:第 1 次重试等 10 秒,第 2 次等 20 秒,第 3 次等 40 秒,第 4 次等 80 秒……
你想想看,如果服务器正在重启,指数退避能避免客户端疯狂请求造成雪崩效应。我曾经在一个项目中遇到过这种情况:服务器凌晨做维护,客户端每 5 秒重试一次,结果服务器刚恢复就被请求淹没了。后来改成指数退避,问题就解决了。
最大重试次数限制
WorkManager 默认的最大重试次数是 3 次(包括第一次执行)。超过这个次数后,任务会被标记为失败,不再重试。
但你可以通过 setBackoffCriteria 来调整:
val workRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
WorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
这里有个坑:WorkRequest.MIN_BACKOFF_MILLIS 是最小退避时间,默认是 10 秒。你不能设置比这个更小的值,否则 WorkManager 会强制使用最小值。我曾经想设成 1 秒,结果发现根本不生效——嗯,Google 工程师早就想到了这种滥用场景。
知识体系总览
下面这张图帮你理清延迟执行与重试策略的核心逻辑:
实战建议
说了这么多理论,最后分享几个我在实战中总结的经验:
- 网络请求场景:用指数退避 + 最小延迟 10 秒。网络波动通常不会持续太久,指数增长能有效降低服务器压力。
- 文件上传场景:用线性退避 + 最小延迟 30 秒。文件上传失败往往是因为文件过大或网络不稳定,线性增长更可控。
- 数据同步场景:结合
Constraints使用。比如只在 Wi-Fi 下重试,避免消耗用户流量。
好了,延迟执行和重试策略就讲到这里。记住一句话:好的调度策略,不是让任务「跑得快」,而是让任务「跑得稳」。下次遇到后台任务失败的情况,你应该知道怎么处理了吧?