定义工作请求

入门指南 中介绍了如何创建一个简单的 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 在您调用 setExpedited()(在 Android 12 之前)时显示通知。

如果您想请求将任务作为快速作业运行,任何 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,这会导致请求在配额不足时取消。

示例应用

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

NetworkType 限制您的工作运行所需的 网络类型。例如,Wi-Fi (UNMETERED)。
BatteryNotLow 设置为 true 时,如果设备处于低电量模式,您的工作将不会运行。
RequiresCharging 设置为 true 时,您的工作只会在设备充电时运行。
DeviceIdle 设置为 true 时,这需要用户设备处于闲置状态,然后工作才会运行。这对于运行可能对用户设备上积极运行的其他应用产生负面性能影响的批量操作很有用。
StorageNotLow 设置为 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 类可用于输出返回值。输入和输出数据将在部分 输入参数和返回值 中详细介绍。

下一步

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