唯一工作序列:beginUniqueWork() 与 ExistingWorkPolicy
各位同学,今天我们来聊一个非常实用的知识点——唯一工作序列。说白了,就是如何保证你的后台任务不会重复执行。
我在做项目时遇到过这么个场景:用户点击同步按钮,结果网络卡了一下,他连点了三下。好家伙,三个同步任务同时跑起来了,数据乱成一锅粥。嗯,从那以后,我就对唯一工作序列格外上心。
什么是唯一工作序列?
唯一工作序列,就是给一组任务贴上一个独一无二的标签。WorkManager 通过这个标签来识别:这个任务是不是已经在排队了?是不是正在执行?我该怎么处理新的请求?
你想想看,这就像你去银行办业务。你拿了一个号,如果再去取号机按一下,系统会怎么处理?是给你一个新号,还是告诉你「你已经在排队了」?这就是 ExistingWorkPolicy 要解决的问题。
核心概念:beginUniqueWork() 方法允许你创建一个「有身份」的工作链。这个身份就是 workName,在整个应用中必须是唯一的。
beginUniqueWork() 的用法
先看代码,我习惯这样写:
val syncWork = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(syncConstraints)
.build()
WorkManager.getInstance(context)
.beginUniqueWork(
"sync_data",
ExistingWorkPolicy.KEEP,
syncWork
)
.enqueue()
这里的关键参数就两个:workName 和 ExistingWorkPolicy。workName 就是你的任务身份证,ExistingWorkPolicy 决定了「撞车」时怎么办。
ExistingWorkPolicy 三种策略详解
| 策略 | 行为 | 适用场景 |
|---|---|---|
| KEEP | 如果已有同名任务在排队或执行中,新任务被丢弃 | 同步任务防重复、数据上报 |
| REPLACE | 取消已有任务,用新任务替代 | 配置更新、用户信息刷新 |
| APPEND | 将新任务追加到已有任务链的末尾 | 链式操作、批量处理 |
KEEP 策略:我最常用的方案
说实话,KEEP 是我个人最常用的策略。为什么?因为大多数场景下,我们就是不想让任务重复。
举个例子,用户上传日志文件。用户可能频繁触发上传操作,但我们只需要一次上传就够了。用 KEEP 策略,后面的请求直接忽略,干净利落。
val uploadWork = OneTimeWorkRequestBuilder<LogUploadWorker>()
.build()
WorkManager.getInstance(context)
.beginUniqueWork(
"upload_logs",
ExistingWorkPolicy.KEEP,
uploadWork
)
.enqueue()
小提示:KEEP 策略不会检查任务是否已经执行完毕。如果任务已经完成,新的请求会正常执行。它只关心「正在排队或执行中」的任务。
REPLACE 策略:当旧数据不再重要
REPLACE 策略就有点「喜新厌旧」的意思了。我记得有一次做用户配置同步功能,用户修改了设置,旧的同步任务还没跑完。这时候如果还用 KEEP,用户会看到旧数据。用 REPLACE 就对了——取消旧的,执行新的。
val configSync = OneTimeWorkRequestBuilder<ConfigSyncWorker>()
.build()
WorkManager.getInstance(context)
.beginUniqueWork(
"sync_config",
ExistingWorkPolicy.REPLACE,
configSync
)
.enqueue()
注意:REPLACE 会调用 Worker.onStopped() 方法。如果你的 Worker 正在执行重要操作,记得在 onStopped() 中做好清理工作,避免数据损坏。
APPEND 策略:链式任务的粘合剂
APPEND 策略比较特殊。它不会取消旧任务,而是把新任务接到旧任务的后面。说白了,就是排队。
我一般在做批量图片上传时用这个策略。用户选了 10 张图,每张图是一个独立的上传任务。用 APPEND 策略,它们会按顺序执行,不会打架。
val firstUpload = OneTimeWorkRequestBuilder<ImageUploadWorker>()
.setInputData(workDataOf("image_id" to 1))
.build()
val secondUpload = OneTimeWorkRequestBuilder<ImageUploadWorker>()
.setInputData(workDataOf("image_id" to 2))
.build()
WorkManager.getInstance(context)
.beginUniqueWork(
"batch_upload",
ExistingWorkPolicy.APPEND,
firstUpload
)
.then(secondUpload)
.enqueue()
实际应用场景:同步任务防重复
好了,理论知识讲完了。咱们来看看实际项目中怎么用。
我曾经做过一个电商 App,用户每次打开应用都要同步购物车数据。但问题是,用户可能频繁切换页面,每次切换都会触发同步。如果不做防重复,服务器会被打爆。
解决方案很简单:
- 在 Application 的 onCreate 中启动一个唯一工作序列
- 使用 KEEP 策略
- 设置合理的约束条件(网络可用、电量充足)
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val syncConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val cartSync = OneTimeWorkRequestBuilder<CartSyncWorker>()
.setConstraints(syncConstraints)
.build()
WorkManager.getInstance(this)
.beginUniqueWork(
"cart_sync",
ExistingWorkPolicy.KEEP,
cartSync
)
.enqueue()
}
}
这样写的好处是:不管用户触发多少次同步请求,最终只会有一个任务在执行。既节省了用户流量,也减轻了服务器压力。
知识体系图
下面这张图帮你理清唯一工作序列的核心逻辑:
避坑指南
最后,分享几个我踩过的坑:
- 不要用 PeriodicWorkRequest 做唯一工作序列——它本身就有重复执行的特性,和唯一工作序列的理念冲突。我曾经这么干过,结果任务调度乱成一团。
- 注意 workName 的命名规范——我建议用「模块名_功能名」的格式,比如 "sync_cart"、"upload_logs"。这样方便排查问题。
- KEEP 策略不是万能的——如果你的任务需要更新数据,用 KEEP 可能导致用户看到旧数据。这时候 REPLACE 更合适。
- APPEND 策略要小心链式任务——如果链式任务中有某个环节失败,整个链都会受影响。建议在 Worker 中做好错误处理。
我的习惯:在调试阶段,我会先用 REPLACE 策略,方便反复测试。上线前再根据实际需求切换到 KEEP 或 APPEND。
好了,唯一工作序列的内容就讲到这里。记住一句话:给任务一个身份,让重复不再发生。这是 WorkManager 中最实用的功能之一,也是保证后台任务稳定运行的关键。