定义工作请求

入门指南介绍了如何创建WorkRequest 并将其加入队列。

在本指南中,您将了解如何定义和自定义 WorkRequest 对象 来处理常见用例,例如:

  • 调度一次性工作和重复性工作
  • 设置工作约束条件,例如要求连接到 Wi-Fi 网络或正在充电
  • 确保至少延迟一定时间再执行工作
  • 设置重试和退避策略
  • 将输入数据传递给工作
  • 使用标记将相关工作分组在一起

概览

工作通过 WorkRequest 在 WorkManager 中进行定义。为了使用 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>()
    <b>.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)</b>
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

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

在此示例中,我们初始化 OneTimeWorkRequest 的实例并对其调用 setExpedited()。然后,此请求就会变成加急工作。如果配额 允许,它将立即在后台开始运行。如果配额已 用完,OutOfQuotaPolicy 参数表示该请求应 作为正常的非加急工作运行。

向后兼容性和前台服务

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

工作器中的 getForegroundInfoAsync()getForegroundInfo() 方法可让 WorkManager 在您调用 setExpedited() 时显示通知,前提是 Android 版本低于 12。

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

以 Android 12 或更高版本为目标平台时,前台服务仍然可通过对应的 setForeground 方法使用。

工作器

工作器不知道自身所执行的工作是否已加急。不过,在某些版本的 Android 上,如果 WorkRequest 被加急,工作器可以显示通知。

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

CoroutineWorker

如果您使用 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,这会在 配额不足时导致请求取消。

延迟加急工作

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

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

调度定期工作

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

下面介绍了如何使用 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 所示。

您可以为定期作业设置一个灵活间隔。您要定义一个重复间隔,然后再定义一个灵活间隔(指定一个在重复间隔末尾开始的具体时间段)。WorkManager 会尝试在每个周期的灵活间隔内运行作业。

图 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 也不会运行,除非满足约束条件。这可能会导致工作在某次运行时出现延迟,甚至会因在相应间隔内未满足条件而被跳过。

工作约束

Constraints 可确保工作延迟到满足最佳条件时再 执行。以下约束适用于 WorkManager:

NetworkType 约束运行工作所需的网络类型。例如 Wi-Fi (UNMETERED)。
BatteryNotLow 如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行。
RequiresCharging 如果设置为 true,那么工作只能在设备充电时运行。
DeviceIdle 如果设置为 true,则需要用户的设备处于空闲状态,工作才会运行。这对于运行批量操作非常有用,否则可能会对用户设备上正在运行的其他应用产生负面性能影响。
StorageNotLow 如果设置为 true,那么当用户设备上的存储空间过低时,工作不会运行。

如需创建一组约束并将其与某项工作相关联,请使用一个Constraints.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,
       WorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       WorkRequest.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();

最后,可以向单个工作请求添加多个标记。这些 标记在内部以一组字符串的形式进行存储。您可以使用 WorkInfo.getTags() 获取与 WorkRequest 关联的标记集。

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 类输出返回值。

后续步骤

状态和观察页面中,您将详细了解工作状态 以及如何监控工作的进度。