定义工作请求

入门指南中,介绍了如何创建简单的WorkRequest 并将其排队。

在本指南中,您将学习如何定义和自定义WorkRequest对象以处理常见用例,例如如何

  • 安排一次性和重复性工作
  • 设置工作约束,例如要求 Wi-Fi 或充电
  • 保证工作执行的最小延迟
  • 设置重试和回退策略
  • 将输入数据传递给工作
  • 使用标签将相关工作组合在一起

概述

工作在 WorkManager 中通过WorkRequest定义。为了使用 WorkManager 安排任何工作,您必须首先创建一个WorkRequest对象,然后将其排队。

Kotlin

val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java

WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

WorkRequest 对象包含 WorkManager 安排和运行您的工作所需的所有信息。它包括必须满足才能运行您的工作的约束条件、调度信息(例如延迟或重复间隔)、重试配置,并且如果您的工作依赖于它,则可能包含输入数据。

WorkRequest 本身是一个抽象基类。您可以使用此类的两个派生实现来创建请求,即OneTimeWorkRequestPeriodicWorkRequest。顾名思义,OneTimeWorkRequest用于安排非重复性工作,而PeriodicWorkRequest更适合安排以一定间隔重复的工作。

安排一次性工作

对于不需要额外配置的简单工作,请使用静态方法from

Kotlin

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

对于更复杂的工作,您可以使用构建器

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

安排加急工作

WorkManager 2.7.0 引入了加急工作的概念。这允许 WorkManager 执行重要工作,同时让系统更好地控制对资源的访问。

加急工作以以下特征为显著特点

  • **重要性**:加急工作适合对用户很重要的任务或用户发起的任务。
  • **速度**:加急工作最适合立即开始并在几分钟内完成的短期任务。
  • **配额**:确定加急作业是否可以启动的系统级配额,限制前台执行时间。
  • **电源管理**:电源管理限制(例如电池节电器和休眠)不太可能影响加急工作。
  • **延迟**:系统会立即执行加急工作,前提是系统当前的工作负载允许这样做。这意味着它们对延迟敏感,并且无法安排在以后执行。

加急工作的潜在用例可能是在聊天应用中,当用户想要发送消息或附加图像时。类似地,处理付款或订阅流程的应用也可能想要使用加急工作。这是因为这些任务对用户很重要,在后台快速执行,需要立即开始,并且即使用户关闭应用也应继续执行

配额

系统必须为加急作业分配执行时间才能运行。执行时间不是无限的。相反,每个应用都会收到一个执行时间配额。当您的应用使用其执行时间并达到分配的配额时,您将无法再执行加急工作,直到配额刷新。这使 Android 能够更有效地在应用程序之间平衡资源。

应用可用的执行时间量基于待机状态和进程重要性。

您可以确定执行配额不允许加急作业立即运行时会发生什么。请参阅下面的代码段以了解详细信息。

执行加急工作

从 WorkManager 2.7 开始,您的应用可以调用 setExpedited() 来声明一个 WorkRequest 应该尽快使用加急作业运行。以下代码片段提供了一个如何使用 setExpedited() 的示例。

Kotlin

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
    .setInputData(inputData)
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build();

在这个示例中,我们初始化了一个 OneTimeWorkRequest 的实例并调用了其上的 setExpedited()。此请求随后成为加急工作。如果配额允许,它将立即在后台开始运行。如果配额已用完,则 OutOfQuotaPolicy 参数指示请求应作为普通非加急工作运行。

向后兼容性和前台服务

为了保持加急作业的向后兼容性,WorkManager 可能会在低于 Android 12 的平台版本上运行前台服务。前台服务可以向用户显示通知。

在您的 Worker 中的 getForegroundInfoAsync()getForegroundInfo() 方法使 WorkManager 能够在您在 Android 12 之前调用 setExpedited() 时显示通知。

如果您希望请求任务作为加急作业运行,则任何 ListenableWorker 都必须实现 getForegroundInfo 方法。

在面向 Android 12 或更高版本时,前台服务仍可通过相应的 setForeground 方法使用。

工作器

工作器不知道它们正在执行的工作是否是加急的。但当 WorkRequest 被加速时,工作器可以在某些版本的 Android 上显示通知。

要启用此功能,WorkManager 提供了 getForegroundInfoAsync() 方法,您必须实现此方法,以便 WorkManager 可以显示通知以为您启动 ForegroundService(在必要时)。

协程工作器

如果您使用 CoroutineWorker,则必须实现 getForegroundInfo()。然后将其传递给 doWork() 中的 setForeground()。这样做将在 Android 12 之前的版本中创建通知。

请考虑以下示例

  class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
   CoroutineWorker(appContext, workerParams) {

   override suspend fun getForegroundInfo(): ForegroundInfo {
       return ForegroundInfo(
           NOTIFICATION_ID, createNotification()
       )
   }

   override suspend fun doWork(): Result {
       TODO()
   }

    private fun createNotification() : Notification {
       TODO()
    }

}

配额策略

当您的应用达到其执行配额时,您可以控制加急工作将发生什么。要继续,您可以传递 setExpedited()

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST,这会导致作业作为普通工作请求运行。上面的代码片段演示了这一点。
  • OutOfQuotaPolicy.DROP_WORK_REQUEST,如果配额不足,则会导致请求取消。

示例应用

要查看 WorkManager 2.7.0 如何使用加急工作的完整示例,请查看 GitHub 上的 WorkManagerSample

延迟的加急工作

系统尝试在作业被调用后尽快执行给定的加急作业。但是,与其他类型的作业一样,系统可能会延迟新加急工作的启动,例如以下情况

  • 负载:系统负载过高,当太多作业正在运行或系统内存不足时可能会发生这种情况。
  • 配额:已超过加急作业配额限制。加急工作使用基于应用待机存储桶的配额系统,并限制滚动时间窗口内的最大执行时间。用于加急工作的配额比用于其他类型后台作业的配额更严格。

安排周期性工作

您的应用有时可能需要某些工作定期运行。例如,您可能希望定期备份数据、下载应用中的最新内容或将日志上传到服务器。

以下是如何使用 PeriodicWorkRequest 创建一个定期执行的 WorkRequest 对象

Kotlin

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

在此示例中,工作计划以一小时的间隔执行。

间隔周期定义为重复之间的最短时间。工作器将被执行的确切时间取决于您在 WorkRequest 对象中使用的约束以及系统执行的优化。

灵活的运行间隔

如果您的工作的性质使其对运行时间敏感,您可以配置您的 PeriodicWorkRequest 在每个间隔周期内的弹性周期内运行,如图 1 所示。

You can set a flex interval for a periodic job. You define a repeat interval,
and a flex interval that specifies a certain amount of time at the end of the
repeat interval. WorkManager attempts to run your job at some point during the
flex interval in each cycle.

图 1. 图表显示了重复的间隔以及工作可以运行的弹性周期。

要定义具有弹性周期的周期性工作,在创建 PeriodicWorkRequest 时,您需要传递 flexInterval 以及 repeatInterval。弹性周期从 repeatInterval - flexInterval 开始,到间隔的末尾结束。

以下是在每个一小时周期的最后 15 分钟内可以运行的周期性工作的示例。

Kotlin

val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

Java

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 提供以下约束。

网络类型 约束您的工作运行所需的 网络类型。例如,Wi-Fi(UNMETERED)。
电池电量不低 设置为 true 时,如果设备处于低电量模式,您的工作将不会运行。
需要充电 设置为 true 时,您的工作仅在设备充电时运行。
设备空闲 设置为 true 时,这要求用户设备在工作运行之前处于空闲状态。这对于运行可能对用户设备上其他正在积极运行的应用产生负面性能影响的批量操作很有用。
存储空间不低 设置为 true 时,如果用户设备上的存储空间过低,您的工作将不会运行。

要创建一组约束并将其与某些工作关联,请使用 Contraints.Builder() 创建一个 Constraints 实例,并将其分配给您的 WorkRequest.Builder()

例如,以下代码构建了一个仅在用户设备同时充电且处于 Wi-Fi 状态下运行的工作请求

Kotlin

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

Java

Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

当指定多个约束时,只有在所有约束都满足时,您的工作才会运行。

如果在您的工作正在运行时某个约束不再满足,WorkManager 将停止您的工作器。然后,当所有约束都满足时,将重新尝试该工作。

延迟工作

如果您的工作没有约束或所有约束在您的工作入队时都已满足,系统可能会选择立即运行该工作。如果您不希望工作立即运行,您可以指定工作在最短初始延迟后启动。

以下是如何设置工作在入队后至少 10 分钟运行的示例。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java

WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

虽然该示例说明了如何为 OneTimeWorkRequest 设置初始延迟,但您也可以为 PeriodicWorkRequest 设置初始延迟。在这种情况下,只有周期性工作的第一次运行会被延迟。

重试和回退策略

如果您需要 WorkManager 重试您的工作,您可以从您的工作器中返回 Result.retry()。然后,您的工作将根据 回退延迟回退策略 重新安排。

  • 回退延迟指定在第一次尝试后重试工作之前等待的最短时间。此值不能小于 10 秒(或 MIN_BACKOFF_MILLIS)。

  • 回退策略定义了后续重试尝试中回退延迟如何随时间推移而增加。WorkManager 支持 2 种回退策略,LINEAREXPONENTIAL

每个工作请求都有一个回退策略和回退延迟。默认策略是 EXPONENTIAL,延迟为 30 秒,但您可以在工作请求配置中覆盖此设置。

以下是如何自定义回退延迟和策略的示例。

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                       TimeUnit.MILLISECONDS)
               .build();

在此示例中,最小回退延迟设置为允许的最小值 10 秒。由于策略是 LINEAR,因此每次新的尝试,重试间隔都会增加大约 10 秒。例如,第一次运行以 Result.retry() 结束,将在 10 秒后再次尝试,然后是 20 秒、30 秒、40 秒,依此类推,如果工作在后续尝试后继续返回 Result.retry()。如果回退策略设置为 EXPONENTIAL,则重试持续时间序列将更接近 20 秒、40 秒、80 秒,依此类推。

标记工作

每个工作请求都有一个唯一标识符,可用于稍后识别该工作,以便取消工作或观察其进度

如果您有一组逻辑相关的任务,您可能还会发现为这些任务添加标签很有帮助。标签允许您一起操作一组工作请求。

例如,WorkManager.cancelAllWorkByTag(String) 取消所有具有特定标签的工作请求,并且 WorkManager.getWorkInfosByTag(String) 返回一个 WorkInfo 对象列表,可用于确定当前工作状态。

以下代码展示了如何为您的工作添加“cleanup”标签

Kotlin

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

最后,可以将多个标签添加到单个工作请求中。在内部,这些标签存储为一组字符串。要获取与WorkRequest关联的标签集,可以使用WorkInfo.getTags()

在您的Worker类中,您可以通过ListenableWorker.getTags()检索其标签集。

分配输入数据

您的工作可能需要输入数据才能完成其工作。例如,处理图像上传的工作可能需要要上传的图像的 URI 作为输入。

输入值以键值对的形式存储在Data对象中,并且可以在工作请求上设置。当 WorkManager 执行工作时,它会将输入Data传递给您的工作。 Worker类可以通过调用Worker.getInputData()来访问输入参数。以下代码展示了如何创建需要输入数据的Worker实例以及如何在工作请求中发送它。

Kotlin

// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

Java

// 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类可用于输出返回值。输入和输出数据将在输入参数和返回值部分中详细介绍。

后续步骤

状态和观察页面中,您将了解有关工作状态以及如何监视工作进度的更多信息。