入门指南介绍了如何创建 WorkRequest
并将其加入队列。
在本指南中,您将了解如何定义和自定义 WorkRequest
对象来处理常见用例,例如:
- 调度一次性工作和重复性工作
- 设置工作约束条件,例如要求连接到 Wi-Fi 网络或正在充电
- 确保至少延迟一定时间再执行工作
- 设置重试和退避策略
- 将输入数据传递给工作
- 使用标记将相关工作分组在一起
概览
工作是使用 WorkRequest
在 WorkManager 中定义的。为了使用 WorkManager 调度任何工作,您必须先创建一个 WorkRequest
对象,然后将其加入队列。
val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)
WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);
WorkRequest
对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。
WorkRequest
本身就是一个抽象基类。该类有两个派生实现,可用于创建 OneTimeWorkRequest
和 PeriodicWorkRequest
请求。顾名思义,OneTimeWorkRequest
适用于调度非重复性工作,而 PeriodicWorkRequest
则更适合调度以一定间隔重复执行的工作。
调度一次性工作
对于无需额外配置的基本工作,请使用静态方法 from
:
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);
对于更复杂的工作,可以使用构建器:
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
// Additional configuration
.build()
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
// Additional configuration
.build();
调度加急工作
WorkManager 2.7.0 引入了加急工作的概念。这使 WorkManager 能够执行重要工作,同时使系统能够更好地控制对资源的访问权限。
加急工作具有以下特征:
- 重要性:加急工作适用于对用户很重要或由用户启动的任务。
- 速度:加急工作最适合那些立即启动并在几分钟内完成的简短任务。
- 配额:限制前台执行时间的系统级配额决定了加急作业是否可以启动。
- 电源管理:电源管理限制(如省电模式和低电耗模式)不太可能影响加急工作。
- 延迟时间:系统立即执行加急工作,前提是系统的当前工作负载允许执行此操作。这意味着这些工作对延迟时间较为敏感,不能安排到以后执行。
在用户想要发送消息或附加的图片时,可能会在聊天应用内使用加急工作。同样,处理付款或订阅流程的应用也可能需要使用加急工作。这是因为这些任务对用户很重要,会在后台快速执行,并需要立即开始执行,即使用户关闭应用,也应继续执行
配额
系统必须先为加急作业分配执行时间,然后才能运行作业。执行时间并非无限制,而是每个应用都会获得执行时间配额。如果您的应用使用其执行时间并达到分配的配额,在配额刷新之前,您无法再执行加急工作。这样,Android 可以更有效地在应用之间平衡资源。
可供应用使用的执行时间取决于待机模式存储分区和进程的重要性。
您可以确定在执行配额不允许立即运行加急作业时会出现什么情况。如需了解详情,请参阅以下代码段。
执行加急工作
从 WorkManager 2.7 开始,您的应用可以调用 setExpedited()
来声明 WorkRequest
应该使用加急作业,以尽可能快的速度运行。以下代码段展示了关于如何使用 setExpedited()
的示例:
val request = OneTimeWorkRequestBuilder<SyncWorker>()
<b>.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)</b>
.build()
WorkManager.getInstance(context)
.enqueue(request)
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 之前,工作器中的 getForegroundInfoAsync()
和 getForegroundInfo()
方法可让 WorkManager 在您调用 setExpedited()
时显示通知。
如果您想要请求任务作为加急作业运行,则所有的 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
对象的方法如下:
val saveRequest =
PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
// Additional configuration
.build()
PeriodicWorkRequest saveRequest =
new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
// Constraints
.build();
在此示例中,工作的运行时间间隔定为一小时。
时间间隔定义为两次重复执行之间的最短时间。工作器的确切执行时间取决于您在 WorkRequest 对象中设置的约束以及系统执行的优化。
灵活的运行间隔
如果您的工作的性质致使其对运行时间敏感,您可以将 PeriodicWorkRequest
配置为在每个时间间隔的灵活时间段内运行,如图 1 所示。
图 1. 此图显示了可在灵活时间段内运行工作的的重复间隔。
如需定义具有灵活时间段的定期工作,请在创建 PeriodicWorkRequest
时传递 flexInterval
以及 repeatInterval
。灵活时间段从 repeatInterval - flexInterval
开始,一直到间隔结束。
以下是可在每小时的最后 15 分钟内运行的定期工作的示例。
val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
1, TimeUnit.HOURS, // repeatInterval (the period cycle)
15, TimeUnit.MINUTES) // flexInterval
.build()
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 网络时才会运行:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val myWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
.setConstraints(constraints)
.build()
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build();
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setConstraints(constraints)
.build();
如果指定了多个约束,工作将仅在满足所有约束时才会运行。
如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。
延迟工作
如果工作没有约束,或者当工作加入队列时所有约束都得到了满足,那么系统可能会选择立即运行该工作。如果您不希望工作立即运行,可以将工作指定为在经过一段最短初始延迟时间后再启动。
下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟后再运行。
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setInitialDelay(10, TimeUnit.MINUTES)
.build();
该示例说明了如何为 OneTimeWorkRequest
设置初始延迟时间,您也可以为 PeriodicWorkRequest
设置初始延迟时间。在这种情况下,定期工作只有首次运行时会延迟。
重试和退避政策
如果您需要让 WorkManager 重试工作,可以从工作器返回 Result.retry()
。然后,系统将根据退避延迟时间和退避政策重新调度工作。
退避延迟时间指定了首次尝试后重试工作前的最短等待时间。此值不能超过 10 秒(或 MIN_BACKOFF_MILLIS)。
退避政策定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。WorkManager 支持 2 个退避政策,即
LINEAR
和EXPONENTIAL
。
每个工作请求都有退避政策和退避延迟时间。默认政策是 EXPONENTIAL
,延迟时间为 30 秒,但您可以在工作请求配置中替换此设置。
以下是自定义退避延迟时间和政策的示例。
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build()
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”标记:
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.addTag("cleanup")
.build()
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.addTag("cleanup")
.build();
最后,可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。如需获取与 WorkRequest
关联的标记集,您可以使用 WorkInfo.getTags()
。
从 Worker
类中,您可以使用 ListenableWorker.getTags() 检索其标记集。
分配输入数据
您的工作可能需要输入数据才能正常运行。例如,处理图片上传的工作可能需要使用待上传图片的 URI 作为输入数据。
输入值以键值对的形式存储在 Data
对象中,并且可以在工作请求中设置。WorkManager 会在执行工作时将输入 Data
传递给工作。Worker
类可通过调用 Worker.getInputData()
访问输入参数。以下代码展示了如何创建需要输入数据的 Worker
实例,以及如何在工作请求中发送该实例。
// 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()
// 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
类输出返回值。如需详细了解输入和输出数据,请参阅输入参数和返回值部分。
后续步骤
在状态和观察页面中,您将详细了解工作状态以及如何监控工作的进度。