一旦您已定义您的 Worker
和您的 WorkRequest
,最后一步是将您的工作入队。将工作入队最简单的方法是调用 WorkManager 的 enqueue()
方法,并传入您想要运行的 WorkRequest
。
Kotlin
val myWork: WorkRequest = // ... OneTime or PeriodicWork WorkManager.getInstance(requireContext()).enqueue(myWork)
Java
WorkRequest myWork = // ... OneTime or PeriodicWork WorkManager.getInstance(requireContext()).enqueue(myWork);
在将工作入队时要谨慎,避免重复。例如,某个应用可能尝试每 24 小时将其日志上传到后端服务。如果您不小心,您可能会多次将同一任务入队,即使该任务只需要运行一次。要实现此目标,您可以将工作计划为唯一工作。
唯一工作
唯一工作是一个强大的概念,它保证您一次只有一个具有特定名称的工作实例。与 ID 不同,唯一名称对人类来说可读,由开发者指定,而不是由 WorkManager 自动生成。与标签不同,唯一名称仅与一个工作实例相关联。
唯一工作可以应用于一次性工作和周期性工作。您可以通过调用以下方法之一来创建唯一的作业序列,具体取决于您是计划重复作业还是一次性作业。
这两种方法都接受 3 个参数
- uniqueWorkName - 用于唯一标识工作请求的
String
。 - existingWorkPolicy - 一个
enum
,它告诉 WorkManager 如果已经存在具有该唯一名称的未完成工作链该怎么办。有关更多信息,请参阅冲突解决策略。 - work - 要计划的
WorkRequest
。
使用唯一工作,我们可以修复前面提到的重复计划问题。
Kotlin
val sendLogsWorkRequest = PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS) .setConstraints(Constraints.Builder() .setRequiresCharging(true) .build() ) .build() WorkManager.getInstance(this).enqueueUniquePeriodicWork( "sendLogs", ExistingPeriodicWorkPolicy.KEEP, sendLogsWorkRequest )
Java
PeriodicWorkRequest sendLogsWorkRequest = new PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS) .setConstraints(new Constraints.Builder() .setRequiresCharging(true) .build() ) .build(); WorkManager.getInstance(this).enqueueUniquePeriodicWork( "sendLogs", ExistingPeriodicWorkPolicy.KEEP, sendLogsWorkRequest);
现在,如果代码在 sendLogs 作业已在队列中的情况下运行,则保留现有作业,并且不会添加新作业。
如果您需要逐步构建一个很长的任务链,唯一工作序列也很有用。例如,照片编辑应用可能会允许用户撤消一系列操作。每个撤消操作可能需要一段时间,但必须按正确的顺序执行。在这种情况下,应用可以创建一个“撤消”链,并根据需要将每个撤消操作附加到该链中。有关更多详细信息,请参阅链接工作。
冲突解决策略
在计划唯一工作时,您必须告诉 WorkManager 在发生冲突时采取什么操作。您可以在将工作入队时通过一个枚举来执行此操作。
对于一次性工作,您提供一个ExistingWorkPolicy
,它支持 4 个处理冲突的选项。
REPLACE
用新工作替换现有工作。此选项会取消现有工作。KEEP
保留现有工作并忽略新工作。APPEND
将新工作附加到现有工作的末尾。此策略会导致您的新工作链接到现有工作,在现有工作完成后运行。
现有工作成为新工作的先决条件。如果现有工作变为CANCELLED
或FAILED
,则新工作也会变为CANCELLED
或FAILED
。如果希望新工作无论现有工作的状态如何都运行,请改用APPEND_OR_REPLACE
。
APPEND_OR_REPLACE
的功能类似于APPEND
,但它不依赖于先决条件工作状态。如果现有工作为CANCELLED
或FAILED
,新工作仍会运行。
对于周期性工作,您需要提供一个ExistingPeriodicWorkPolicy
,它支持两种选项:REPLACE
和 KEEP
。这些选项的功能与它们的 ExistingWorkPolicy
对应项相同。
观察您的工作
在入队工作后的任何时间点,您都可以通过其 name
、id
或与其关联的 tag
查询 WorkManager 来检查其状态。
Kotlin
// by id workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo> // by name workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>> // by tag workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
Java
// by id workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo> // by name workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>> // by tag workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>
查询返回一个ListenableFuture
,其中包含一个WorkInfo
对象,该对象包含工作的id
、其标签、其当前State
以及通过 Result.success(outputData)
设置的任何输出数据。
每个方法的LiveData
变体允许您通过注册监听器来观察对 WorkInfo
的更改。例如,如果您希望在某些工作成功完成时向用户显示一条消息,您可以按如下方式设置它
Kotlin
workManager.getWorkInfoByIdLiveData(syncWorker.id) .observe(viewLifecycleOwner) { workInfo -> if(workInfo?.state == WorkInfo.State.SUCCEEDED) { Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT) .show() } }
Java
workManager.getWorkInfoByIdLiveData(syncWorker.id) .observe(getViewLifecycleOwner(), workInfo -> { if (workInfo.getState() != null && workInfo.getState() == WorkInfo.State.SUCCEEDED) { Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT) .show(); } });
复杂的工作查询
WorkManager 2.4.0 及更高版本支持使用WorkQuery
对象对已入队作业进行复杂查询。WorkQuery
支持通过其标签、状态和唯一工作名称的组合来查询工作。
以下示例显示了如何查找所有带有标签“syncTag”、状态为 FAILED
或 CANCELLED
且唯一工作名称为“preProcess”或“sync”的工作。
Kotlin
val workQuery = WorkQuery.Builder .fromTags(listOf("syncTag")) .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) .addUniqueWorkNames(listOf("preProcess", "sync") ) .build() val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)
Java
WorkQuery workQuery = WorkQuery.Builder .fromTags(Arrays.asList("syncTag")) .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) .addUniqueWorkNames(Arrays.asList("preProcess", "sync") ) .build(); ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);
WorkQuery
中的每个组件(标签、状态或名称)都与其他组件进行AND
运算。每个组件中的每个值都进行OR
运算。例如:(name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)
。
WorkQuery
也适用于 LiveData
等效项,即getWorkInfosLiveData()
。
取消和停止工作
如果您不再需要以前入队的工作运行,您可以请求取消它。可以通过其 name
、id
或与其关联的 tag
取消工作。
Kotlin
// by id workManager.cancelWorkById(syncWorker.id) // by name workManager.cancelUniqueWork("sync") // by tag workManager.cancelAllWorkByTag("syncTag")
Java
// by id workManager.cancelWorkById(syncWorker.id); // by name workManager.cancelUniqueWork("sync"); // by tag workManager.cancelAllWorkByTag("syncTag");
在后台,WorkManager 检查工作的State
。如果工作已经完成,则不会发生任何事情。否则,工作状态将更改为CANCELLED
,并且工作将来不会运行。任何依赖于此工作的WorkRequest
作业也将被CANCELLED
。
当前正在RUNNING
的工作会收到对 ListenableWorker.onStopped()
的调用。覆盖此方法以处理任何潜在的清理工作。有关更多信息,请参阅停止正在运行的工作器。
停止正在运行的工作器
您的正在运行的 Worker
可能因多种原因被 WorkManager 停止
- 您明确要求取消它(例如,通过调用
WorkManager.cancelWorkById(UUID)
)。 - 在唯一工作 的情况下,您明确入队了一个新的
WorkRequest
,其ExistingWorkPolicy
为REPLACE
。旧的WorkRequest
会立即被视为已取消。 - 您的工作约束不再满足。
- 系统指示您的应用出于某种原因停止您的工作。如果您的执行时间超过 10 分钟,则可能会发生这种情况。工作将在稍后时间重新安排。
在这些情况下,您的工作器将停止。
您应该协作中止您正在进行的任何工作,并释放您的工作器正在持有的任何资源。例如,您应该在此处关闭数据库和文件的打开句柄。您有两个机制可以用来了解您的工作器何时停止。
onStopped() 回调
一旦您的工作器停止,WorkManager 就会调用ListenableWorker.onStopped()
。覆盖此方法以关闭您可能正在持有的任何资源。
isStopped() 属性
您可以调用ListenableWorker.isStopped()
方法来检查您的工作器是否已被停止。如果您在工作器中执行长时间运行或重复的操作,则应经常检查此属性,并将其用作尽快停止工作的信号。
注意:WorkManager 会忽略已收到 onStop 信号的工作器设置的Result
,因为工作器已被视为已停止。