翻译自android官网,可直接去官网观看
Jetpack ---- WorkManager入门(二)
三、方法指南
一、定义工作请求
入门指南介绍了如何创建简单的 WorkRequest 并将其加入队列。
在本指南中,您将了解如何定义和自定义 WorkRequest 对象来处理常见用例,例如:
- 调度一次性工作和重复性工作
- 设置工作约束条件,例如要求连接到 Wi-Fi 网络或正在充电
- 确保至少延迟一定时间再执行工作
- 设置重试和退避策略
- 将输入数据传递给工作
- 使用标记将相关工作分组在一起
概览
工作通过 WorkRequest 在 WorkManager 中进行定义。为了使用 WorkManager 调度任何工作,您必须先创建一个 WorkRequest 对象,然后将其加入队列。
WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);
WorkRequest 对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。
WorkRequest 本身是抽象基类。该类有两个派生实现,可用于创建 OneTimeWorkRequest 和 PeriodicWorkRequest 请求。顾名思义,OneTimeWorkRequest 适用于调度非重复性工作,而 PeriodicWorkRequest 则更适合调度以一定间隔重复执行的工作。
调度一次性工作
对于无需额外配置的简单工作,请使用静态方法 from:
WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);
对于更复杂的工作,可以使用构建器。
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
// Additional configuration
.build();
调度定期工作
您的应用有时可能需要定期运行某些工作。例如,您可能要定期备份数据、定期下载应用中的新鲜内容或者定期上传日志到服务器。
使用 PeriodicWorkRequest 创建定期执行的 WorkRequest 对象的方法如下:
PeriodicWorkRequest saveRequest =
new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
// Constraints
.build();
在此示例中,工作的运行时间间隔定为一小时。
时间间隔定义为两次重复执行之间的最短时间。工作器的确切执行时间取决于您在 WorkRequest 对象中设置的约束以及系统执行的优化。
注意:可以定义的最短重复间隔是 15 分钟(与 JobScheduler API 相同)。
灵活的运行间隔
如果您的工作的性质致使其对运行时间敏感,您可以将 PeriodicWorkRequest 配置为在每个时间间隔的灵活时间段内运行,如图 1 所示。
图 1. 此图显示了可在灵活时间段内运行工作的的重复间隔。
您可以为定期作业设置一个灵活间隔。您要定义一个重复间隔,然后再定义一个灵活间隔(指定一个在重复间隔末尾开始的具体时间段)。WorkManager 会尝试在每个周期的灵活间隔内运行作业。
如需定义具有灵活时间段的定期工作,请在创建 PeriodicWorkRequest 时传递 flexInterval 以及 repeatInterval。灵活时间段从 repeatInterval - flexInterval 开始,一直到间隔结束。
以下是可在每小时的最后 15 分钟内运行的定期工作的示例。
WorkRequest saveRequest =
new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
1, TimeUnit.HOURS,
15, TimeUnit.MINUTES)
.build();
重复间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,而灵活间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS。
约束对定期工作的影响
您可以对定期工作设置约束。例如,您可以为工作请求添加约束,以便工作仅在用户设备充电时运行。在这种情况下,除非满足约束条件,否则即使过了定义的重复间隔,PeriodicWorkRequest 也不会运行。这可能会导致工作在某次运行时出现延迟,甚至会因在相应间隔内未满足条件而被跳过。
工作约束
约束可确保将工作延迟到满足最佳条件时运行。以下约束适用于 WorkManager。
- NetworkType
约束运行工作所需的网络类型
。例如 Wi-Fi (UNMETERED)。 - BatteryNotLow
如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行
。 - RequiresCharging
如果设置为 true,那么工作只能在设备充电时运行
。 - DeviceIdle
如果设置为 true,则要求用户的设备必须处于空闲状态,才能运行工作
。这对于运行批处理操作非常有用,否则可能会使用户设备上正在运行的其他应用程序产生较大的性能影响。 - StorageNotLow
如果设置为 true,那么当用户设备上的存储空间不足时,工作不会运行
。
如需创建一组约束并将其与某项工作相关联,请使用一个 Contraints.Builder() 创建 Constraints 实例,并将该实例分配给 WorkRequest.Builder()。
例如,以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build();
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setConstraints(constraints)
.build();
如果指定了多个约束,工作将仅在满足所有约束时才会运行。
如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。
延迟工作
如果工作没有约束,或者当工作加入队列时所有约束都得到了满足,那么系统可能会选择立即运行该工作。如果您不希望工作立即运行,可以将工作指定为在经过一段最短初始延迟时间后再启动。
下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟后再运行。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setInitialDelay(10, TimeUnit.MINUTES)
.build();
该示例说明了如何为 OneTimeWorkRequest 设置初始延迟时间,您也可以为 PeriodicWorkRequest 设置初始延迟时间。在这种情况下,定期工作只有首次运行时会延迟。
注意:执行工作器的确切时间还取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供可能的最佳行为。
重试和退避政策
如果您需要让 WorkManager 重试工作,可以从工作器返回 Result.retry()。然后,系统将根据退避延迟时间和退避政策重新调度工作。
- 退避延迟时间指定了首次尝试后重试工作前的最短等待时间。
此值不能超过 10 秒(或 MIN_BACKOFF_MILLIS)
。 - 退避政策定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。
WorkManager 支持 2 个退避政策,即 LINEAR 和 EXPONENTIAL
。
每个工作请求都有退避政策和退避延迟时间。默认政策是 EXPONENTIAL,延迟时间为 10 秒,但您可以在工作请求配置中替换此设置。
以下是自定义退避延迟时间和政策的示例。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();
在本示例中,最短退避延迟时间设置为允许的最小值,即 10 秒。由于政策为 LINEAR,每次尝试重试时,重试间隔都会增加约 10 秒。例如,第一次运行以 Result.retry() 结束并在 10 秒后重试;然后,如果工作在后续尝试后继续返回 Result.retry(),那么接下来会在 20 秒、30 秒、40 秒后重试,以此类推。如果退避政策设置为 EXPONENTIAL,那么重试时长序列将接近 20、40、80 秒,以此类推。
注意:退避延迟时间不精确,在两次重试之间可能会有几秒钟的差异,但绝不会低于配置中指定的初始退避延迟时间。
标记工作
每个工作请求都有一个唯一标识符,该标识符可用于在以后标识该工作,以便取消工作或观察其进度。
如果有一组在逻辑上相关的工作,对这些工作项进行标记可能也会很有帮助。通过标记,您一起处理一组工作请求。
例如,WorkManager.cancelAllWorkByTag(String) 会取消带有特定标记的所有工作请求,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。
以下代码展示了如何向工作添加“cleanup”标记:
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.addTag("cleanup")
.build();
最后,可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。对于工作请求,您可以通过 WorkRequest.getTags() 检索其标记集。
分配输入数据
您的工作可能需要输入数据才能正常运行。例如,处理图片上传的工作可能需要使用待上传图片的 URI 作为输入数据。
输入值以键值对的形式存储在 Data 对象中,并且可以在工作请求中设置。WorkManager 会在执行工作时将输入 Data 传递给工作。Worker 类可通过调用 Worker.getInputData() 访问输入参数。以下代码展示了如何创建需要输入数据的 Worker 实例,以及如何在工作请求中发送该实例。
// Define the Worker requiring input
public class UploadWork extends Worker {
public UploadWork(Context appContext, WorkerParameters workerParams) {
super(appContext, workerParams);
}
@NonNull
@Override
public Result doWork() {
String imageUriInput = getInputData().getString("IMAGE_URI");
if(imageUriInput == null) {
return Result.failure();
}
uploadFile(imageUriInput);
return Result.success();
}
...
}
// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
new OneTimeWorkRequest.Builder(UploadWork.class)
.setInputData(
new Data.Builder()
.putString("IMAGE_URI", "http://...")
.build()
)
.build();
同样,可使用 Data 类输出返回值。如需详细了解输入和输出数据,请参阅输入参数和返回值部分。
后续步骤
在状态和观察页面中,您将详细了解工作状态以及如何监控工作的进度。
二、工作状态
工作在其整个生命周期内经历了一系列 State 更改。
一次性工作的状态
对于 one-time 工作请求,工作的初始状态为 ENQUEUED。
在 ENQUEUED 状态下,您的工作会在满足其 Constraints 和初始延迟计时要求后立即运行。接下来,该工作会转为 RUNNING 状态,然后可能会根据工作的结果转为 SUCCEEDED、FAILED 状态;或者,如果结果是 retry,它可能会回到 ENQUEUED 状态。在此过程中,随时都可以取消工作,取消后工作将进入 CANCELLED 状态。
图 1 展示了一次性工作的生命周期,事件可能会进入另一个状态。
图 1. 一次性工作的状态图。
SUCCEEDED、FAILED 和 CANCELLED 均表示此工作的终止状态。如果您的工作处于上述任何状态,WorkInfo.State.isFinished() 都将返回 true
。
定期工作的状态
成功和失败状态仅适用于一次性工作和链式工作。定期工作只有一个终止状态 CANCELLED。这是因为定期工作永远不会结束。每次运行后,无论结果如何,系统都会重新对其进行调度。图 2 描述了定期工作的精简状态图。
图 2. 定期工作的状态图。
BLOCKED 状态
还有一种我们尚未提到的最终状态,那就是 BLOCKED。此状态适用于一系列已编排的工作,或者说工作链。链接工作中介绍了工作链及其状态图。
后续步骤
在管理工作中,您将详细了解如何管理和监控工作的进度。
三、管理工作
定义 Worker 和 WorkRequest 后,最后一步是将工作加入队列。将工作加入队列的最简单方法是调用 WorkManager enqueue() 方法,然后传递要运行的 WorkRequest。
WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);
在将工作加入队列时请小心谨慎,以避免重复。例如,应用可能会每 24 小时尝试将其日志上传到后端服务。如果不谨慎,即使作业只需运行一次,您最终也可能会多次将同一作业加入队列。为了实现此目标,您可以将工作调度为唯一工作。
唯一工作
唯一工作是一个很实用的概念,可确保同一时刻只有一个具有特定名称的工作实例。与 ID 不同的是,唯一名称是人类可读的,由开发者指定,而不是由 WorkManager 自动生成。与 tags不同,唯一名称仅与一个work实例相关联。
唯一工作既可用于一次性工作,也可用于定期工作。您可以通过调用以下方法之一创建唯一工作序列,具体取决于您是调度重复工作还是一次性工作。
- WorkManager.enqueueUniqueWork()(用于一次性工作)
- WorkManager.enqueueUniquePeriodicWork()(用于定期工作)
这两种方法都接受 3 个参数:
- uniqueWorkName - 用于唯一标识工作请求的 String。
- existingWorkPolicy - 此 enum 可告知 WorkManager 如果已有使用该名称且尚未完成的唯一工作链,应执行什么操作。如需了解详情,请参阅冲突解决政策。
- work - 要调度的 WorkRequest。
借助唯一工作,我们可以解决前面提到的重复调度问题。
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 中进行查询,以检查其状态。
// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>
该查询会返回 WorkInfo 对象的 ListenableFuture,该值包含工作的 id、其标记、其当前的 State 以及通过 Result.success(outputData) 设置的任何输出数据。
利用每个方法的 LiveData 变种,您可以通过注册监听器来观察 WorkInfo 的变化。例如,如果您想要在某项工作成功完成后向用户显示消息,您可以进行如下设置:
workManager.getWorkInfoByIdLiveData(syncWorker.id)
.observe(getViewLifecycleOwner(), workInfo -> {
if (workInfo.getState() != null &&
workInfo.getState() == W