定义工作请求

入门指南介绍了如何创建 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>()
    <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 之前的平台版本上运行前台服务。前台服务可以向用户显示通知。

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

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

当目标平台为 Android 12 或更高版本时,前台服务仍可通过相应的 setForeground 方法为您提供。

Worker

Worker 不知道它们正在做的工作是否是加急的。但是,当 WorkRequest 已加急时,Worker 可以在某些 Android 版本上显示通知。

为了实现这一点,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();

在此示例中,工作计划每小时运行一次。

间隔周期定义为重复之间的最短时间。Worker 的确切执行时间取决于您在 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 时会传递 flexIntervalrepeatInterval。弹性期从 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 将停止您的 Worker。然后,当所有约束都满足时,工作将重试。

延迟工作

如果您的工作没有约束,或者所有约束在工作排队时都已满足,系统可能会选择立即运行该工作。如果您不想立即运行该工作,可以指定您的工作在最小初始延迟后开始。

以下是如何设置您的工作在排队后至少 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 重试您的工作,您可以从 Worker 返回 Result.retry()。然后,您的工作将根据退避延迟退避策略重新调度。

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

  • 退避策略定义了后续重试尝试时退避延迟应如何随时间增加。WorkManager 支持两种退避策略: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 秒后再次尝试,如果工作在后续尝试后继续返回 Result.retry(),则依次为 20、30、40,依此类推。如果退避策略设置为 EXPONENTIAL,则重试持续时间序列将更接近 20、40 和 80。

标记工作

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

如果您有一组逻辑上相关的工作,您可能还会发现标记这些工作项很有用。标记允许您将一组工作请求一起操作。

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

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

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

后续步骤

状态和观察页面中,您将了解更多关于工作状态以及如何监控工作进度。