当前位置:   article > 正文

Jetpack架构组件库:WorkManager_periodicworkrequest java

periodicworkrequest java

WorkManager简介

WorkManager 是Android最新一代的后台调度 API,主要是用来替换先前的旧版后台调度 API(如 JobScheduler 等)。

WorkManager 是适合用于持久性工作的推荐解决方案。如果工作始终要通过应用重启和系统重新启动来调度,便是持久性的工作。由于大多数后台处理操作都是通过持久性工作完成的,因此 WorkManager 是适用于后台处理操作的主要推荐 API。

持久性工作的类型

WorkManager 可处理三种类型的持久性工作:

  • 立即执行:必须立即开始很快就完成的任务,可以加急。
  • 长时间运行:运行时间可能较长(有可能超过 10 分钟)的任务。
  • 可延期执行延期开始并且可以定期运行的预定任务。

在这里插入图片描述

图 1 大致表明了不同类型的持久性工作彼此之间的关系。

下表大致列出了各种工作类型:

类型 周期 使用方式
立即 一次性 OneTimeWorkRequest 和 Worker。如需处理加急工作,请对 OneTimeWorkRequest 调用 setExpedited()。
长期运行 一次性或定期 任意 WorkRequest 或 Worker。在工作器中调用 setForeground() 来处理通知。
可延期 一次性或定期 PeriodicWorkRequest 和 Worker。

使用 WorkManager 的优势

除了具备更为简单且一致的 API 之外,WorkManager 还具备许多其他关键优势:

  • 工作约束: 使用工作约束明确定义工作运行的最佳条件。例如,仅在设备采用不按流量计费的网络连接时、当设备处于空闲状态或者有足够的电量时运行。

  • 强大的调度: WorkManager 允许您使用灵活的调度窗口调度工作,以运行一次性或重复工作。您还可以对工作进行标记或命名,以便调度唯一的、可替换的工作以及监控或取消工作组。
    已调度的工作存储在内部托管的 SQLite 数据库中,由 WorkManager 负责确保该工作持续进行,并在设备重新启动后重新调度。
    此外,WorkManager 遵循低电耗模式等省电功能和最佳做法,因此您在这方面无需担心。

  • 加急工作: 您可以使用 WorkManager 调度需在后台立即执行的工作。您应该使用加急工作来处理对用户来说很重要且会在几分钟内完成的任务。

  • 灵活的重试政策: 有时工作会失败,WorkManager 提供了灵活的重试政策,包括可配置的指数退避政策。

  • 工作链: 对于复杂的相关工作,您可以使用直观的接口将各个工作任务串联起来,这样您便可以控制哪些部分依序运行,哪些部分并行运行。对于每项工作任务,您可以定义工作的输入和输出数据。将工作串联在一起时,WorkManager 会自动将输出数据从一个工作任务传递给下一个工作任务。

WorkManager.getInstance(myContext)
    .beginWith(workA) 
    .then(workB) 
    .then(listOf(workC1, workC2, workC3)
    .then(workC) 
    .then(if (save) workD else workE)
    .enqueue()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 内置线程互操作性: WorkManager 无缝集成 Coroutines 和 RxJava,让您可以插入自己的异步 API,非常灵活。
    注意:尽管 Coroutines 和 WorkManager 分别是针对于不同用例推荐的解决方案,但它们二者并不是互斥的关系。您可以在通过 WorkManager 调度的工作中使用协程。

使用 WorkManager 保证工作可靠性

WorkManager 适用于需要可靠运行的工作即使用户导航离开屏幕、退出应用或重启设备也不影响工作的执行。例如:

  • 向后端服务发送日志或分析数据
  • 定期将应用数据与服务器同步。

一个最好用的场景是:当网络状态恢复时执行某个后台任务可靠性指定条件的任务,哪怕App进程被杀了,也一定会执行。

WorkManager 不适用于那些可在应用进程结束时安全终止的进程内后台工作。它也并非对所有需要立即执行的工作都适用的通用解决方案。

与其他 API 的关系

虽然协程是适合某些用例的推荐解决方案,但您不应将其用于持久性工作。请务必注意,协程是一个并发框架,而 WorkManager 是一个持久性工作库。同样,AlarmManager 仅适合用于时钟或日历。

API 推荐使用场景 与 WorkManager 的关系
Coroutines 所有不需要持久的异步工作。 协程是在 Kotlin 中退出主线程的标准方式。不过,它们在应用关闭后会释放内存。对于持久性工作,请使用 WorkManager。
AlarmManager 仅限闹钟。 与 WorkManager 不同,AlarmManager 会使设备从低电耗模式中唤醒。因此,它在电源和资源管理方面来讲并不高效。AlarmManager 仅适合用于精确闹钟或通知(例如日历活动)场景,而不适用于后台工作。

WorkManager 的使用

添加依赖

dependencies {
   
    val work_version = "2.7.1"
    // (Java only)
    // implementation("androidx.work:work-runtime:$work_version")
    // Kotlin + coroutines
    implementation("androidx.work:work-runtime-ktx:$work_version")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

一般只需使用work-runtime-ktx版本的就可以了。

WorkManager 的使用主要包括三个核心类:

在这里插入图片描述

定义 Worker

工作使用 Worker 类定义。 Worker 类的 doWork() 方法在 WorkManager 提供的后台线程上异步运行。

如需为 WorkManager 创建一些要运行的工作,请继承 Worker 类并重写 doWork() 方法。例如,如需创建上传图像的 Worker,您可以执行以下操作:

class UploadWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {
   

    override fun doWork(): Result {
   
        // Do the work here--in this case, upload the images.
        uploadImages()
        // Indicate whether the work finished successfully with the Result
        return Result.success()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

doWork() 返回的 Result 会通知 WorkManager 服务工作是否成功,以及工作失败时是否应重试工作。

  • Result.success():工作成功完成。
  • Result.failure():工作失败。
  • Result.retry():工作失败,应根据其重试政策在其他时间尝试。

创建 WorkRequest

定义工作后,必须使用 WorkManager 服务进行调度该工作才能运行。对于如何调度工作,WorkManager 提供了很大的灵活性。您可以将其安排为在某段时间内定期运行,也可以将其安排为仅运行一次。

WorkRequest 对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。Worker 负责定义工作内容,WorkRequest则负责定义工作运行方式和时间。

执行工作器的确切时间取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供最佳行为。

WorkRequest 本身是抽象基类。该类有两个派生实现:

  • OneTimeWorkRequest :适用于调度非重复性的一次性工作
  • PeriodicWorkRequest 适合调度以一定间隔重复执行的工作

调度一次性工作

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

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

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

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()
  • 1
  • 2
  • 3
  • 4

最后,您需要使用 enqueue() 方法将 WorkRequest 提交到 WorkManager

WorkManager.getInstance(myContext).enqueue(uploadWorkRequest)
  • 1

调度加急工作

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

加急工作具有以下特征:

  • 重要性:加急工作适用于对用户很重要或由用户启动的任务。
  • 速度:加急工作最适合那些立即启动并在几分钟内完成的简短任务。
  • 配额:限制前台执行时间的系统级配额决定了加急作业是否可以启动。
  • 电源管理:电源管理限制(如省电模式和低电耗模式)不太可能影响加急工作。
  • 延迟时间:系统立即执行加急工作,前提是系统的当前工作负载允许执行此操作。这意味着这些工作对延迟时间较为敏感,不能安排到以后执行。

在用户想要发送消息或附加的图片时,可能会在聊天应用内使用加急工作。同样,处理付款或订阅流程的应用也可能需要使用加急工作。这是因为这些任务对用户很重要,会在后台快速执行,并需要立即开始执行。

可以通过调用 setExpedited() 来声明 WorkRequest 应该使用加急作业,以尽可能快的速度运行。例如以下代码示例:

val request = OneTimeWorkRequestBuilder()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context).enqueue(request)
  • 1
  • 2
  • 3
  • 4
  • 5

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

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

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

如果您想要请求任务作为加急作业运行,则所有的 ListenableWorker 都必须实现 getForegroundInfo 方法。

注意:如果未能实现对应的 getForegroundInfo 方法,那么在旧版平台上调用 setExpedited 时,可能会导致运行时崩溃。

工作器不知道自身所执行的工作是否已加急。不过,在某些版本的 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 {
   
        try {
   
            setForeground(getForegroundInfo())
        } catch (e: IllegalStateException) {
   
            return  Result.failure()
        }
        // TODO()
    }

    private fun createNotification() : Notification {
   
        // TODO()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

注意:您应该将 setForeground() 封装在 try/catch 块中,以捕获可能出现的 IllegalStateException。如果您的应用此时无法在前台运行,便可能会发生这类异常。在 Android 12 及更高版本中,您可以使用更详细的 ForegroundServiceStartNotAllowedException

配额策略

系统必须先为加急作业分配应用执行时间,然后才能运行作业。执行时间并非无限制,而是受配额限制。如果您的应用使用其执行时间并达到分配的配额,在配额刷新之前,您无法再执行加急工作。这样,Android 可以更有效地在应用之间平衡资源。

每个应用均有自己的前台执行时间配额。可用的执行时间取决于待机模式存储分区和进程的重要性。

注意:当您的应用在前台运行时,配额不会限制加急工作的执行。仅当应用在后台运行或移至后台时,执行时间配额才适用。因此,您应加急想在后台继续的工作。当应用在前台运行时,您可以继续使用 setForeground()。

您可以控制当应用达到其执行配额时加急工作需要做出如何反应,可以通过 setExpedited() 设置如下值:

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST,这会导致作业作为普通工作继续请求运行。
  • OutOfQuotaPolicy.DROP_WORK_REQUEST,这会在配额不足时导致请求取消。

延迟加急工作

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

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

调度定期工作

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

使用 PeriodicWorkRequest 创建定期执行的 WorkRequest 对象的方法如下:

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

在此示例中,工作的运行时间间隔定为一小时。

时间间隔定义为两次重复执行之间的最短时间。工作器的确切执行时间取决于您在 WorkRequest 对象中设置的约束以及系统执行的优化。(注意:可以定义的最短重复间隔是 15 分钟

注意:WorkManager调度定期工作最少15分钟执行一次重复任务,它只能保证任务一定执行,但不能保证指定的精确时间执行。

弹性运行间隔

如果您的工作的性质致使其对运行时间敏感,您可以将 PeriodicWorkRequest 配置为在每个时间间隔的弹性时间段内运行,如图 1 所示。
在这里插入图片描述
如需定义具有弹性时间段的定期工作,请在创建 PeriodicWorkRequest 时传递 flexInterval 以及 repeatInterval。弹性时间段从 repeatInterval - flexInterval 开始,一直到间隔结束。

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

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

重复间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS(15分钟),而弹性间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS(5分钟)。

设置 Worker 的约束

您可以对定期工作设置约束。例如,您可以为工作请求添加约束,以便工作仅在用户设备充电时运行。在这种情况下,除非满足约束条件,否则即使过了定义的重复间隔,PeriodicWorkRequest 也不会运行。这可能会导致工作在某次运行时出现延迟,甚至会因在相应间隔内未满足条件而被跳过。

约束可确保将工作延迟到满足最佳条件时运行。WorkManager定义了一些类型的约束

  • NetworkType:约束运行工作所需的网络类型。例如 Wi-Fi (UNMETERED)。
  • BatteryNotLow:如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行。
  • RequiresCharging:如果设置为 true,那么工作只能在设备充电时运行。
  • DeviceIdle:如果设置为 true,则要求用户的设备必须处于空闲状态,才能运行工作。在运行批量操作时,此约束会非常有用;若是不用此约束,批量操作可能会降低用户设备上正在积极运行的其他应用的性能。
  • StorageNotLow:如果设置为 true,那么当用户设备上的存储空间不足时,工作不会运行。

如需创建一组约束并将其与某项工作相关联,请使用一个 Contraints.Builder() 创建 Constraints 实例,并将该实例分配给 WorkRequest.Builder()

例如,以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:

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

val myWorkRequest: WorkRequest = 
	OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果指定了多个约束,工作将仅在满足所有约束时才会运行。

如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。

调度延期工作

如果工作没有约束,或者当工作加入队列时所有约束都得到了满足,那么系统可能会选择立即运行该工作。如果您不希望工作立即运行,可以将工作指定为在经过一段最短初始延迟时间后再启动。

下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟后再运行。

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

该示例说明了如何为 OneTimeWorkRequest 设置初始延迟时间,您也可以为 PeriodicWorkRequest 设置初始延迟时间(在这种情况下,定期工作只有首次运行时会延迟。)。

注意:执行工作器的确切时间还取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供可能的最佳行为。

重试和退避策略

如果您需要让 WorkManager 重试工作,可以在 doWork() 方法返回 Result.retry()。然后,系统将根据退避延迟时间和退避策略重新调度工作。

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

  • 退避策略 定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。WorkManager 支持 2 个退避策略,即 LINEAREXPONENTIAL

每个工作请求都有退避策略和退避延迟时间。默认策略是 EXPONENTIAL,延迟时间为 10 秒,但您可以在工作请求配置中替换此设置。

以下是自定义退避延迟时间和策略的示例:

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在本示例中,最短退避延迟时间设置为允许的最小值,即 10 秒。由于策略设置为 LINEAR,每次尝试重试时,重试间隔都会增加10 秒。例如,第一次运行以 Result.retry() 结束并在 10 秒后重试;然后,如果工作在后续尝试后继续返回 Result.retry(),那么接下来会在 20 秒30 秒40 秒后重试,以此类推。如果退避政策设置为 EXPONENTIAL,那么重试时长序列将接近 204080 秒,以此类推。

注意:退避延迟时间不精确,在两次重试之间可能会有几秒钟的差异,但绝不会低于配置中指定的初始退避延迟时间。

标记 Worker

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

如果有一组在逻辑上相关的工作,对这些工作项进行标记可能也会很有帮助。通过标记,您可以将一组工作请求一起处理。例如,WorkManager.cancelAllWorkByTag(String) 会取消带有特定标记的所有工作请求,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。

要标记一个 Worker,请使用 WorkRequest 构建器的 addTag() 方法。以下代码展示了如何向工作添加“cleanup”标记:

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()
  • 1
  • 2
  • 3

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

向 Worker 传递参数

通过 WorkRequest 构建器创建 Worker 对象时,可以调用 setInputData() 方法设置需要向 Worker 对象传递的参数内容。

参数内容是以键值对Pair的形式存储在 Data 对象中, WorkManager 会在执行工作时将输入 Data 传递给Worker 对象。在 Worker 类中可通过调用 Worker.getInputData() 来获取输入参数。

例如,处理图片上传的工作可能需要使用待上传图片的 URI 作为输入数据:

class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {
   

   override fun doWork(): Result {
   
       val imageUriInput = inputData.getString("IMAGE_URI") ?: return Result.failure() // 根据key来查询
       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}
 
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf("IMAGE_URI" to "http://xxx")) // 支持多个 key to value 的 Pair 对
   .build()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

同样,可使用 Data 类输出返回值,例如返回 Result.success(Data) 。任务的 WorkInfo 中会提供返回值:

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.id)
        .observe(this, Observer {
    info ->
            if (info != null && info.state.isFinished) {
   
                val myResult = info.outputData.getInt(KEY_RESULT, myDefaultValue)
                // ... do something with the result ...
            }
        })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果您链接多个任务,一个任务的输出可以作为任务链中下一个任务的输入。如果是简单链(即一个 OneTimeWorkRequest 后面跟着另一个 OneTimeWorkRequest),第一个任务通过调用 Result.success(Data) 返回其结果,下一个任务通过调用 getInputData() 提取该结果。如果是更复杂的链(例如有多个任务都将输出发送给同一个后续任务),您可以在 OneTimeWorkRequest.Builder 上定义 InputMerger,以指定当多个不同任务返回具有相同键的输出时应执行什么操作。

Worker 的状态

一次性工作的状态

对于 one-time 工作请求,工作的初始状态为 ENQUEUED

ENQUEUED 状态下,您的工作会在满足其约束和初始延迟计时要求后立即运行。接下来,该工作会转为 RUNNING 状态,然后可能会根据工作的结果转为 SUCCEEDEDFAILED 状态;或者,如果结果是 retry,它可能会回到 ENQUEUED 状态。在此过程中,随时都可以取消工作,取消后工作将进入 CANCELLED 状态。

在这里插入图片描述
SUCCEEDEDFAILEDCANCELLED 均表示此工作的终止状态。如果您的工作处于上述任何状态,WorkInfo.State.isFinished() 都将返回 true

定期工作的状态

成功和失败状态仅适用于一次性工作和链式工作。定期工作只有一个终止状态 CANCELLED。这是因为定期工作永远不会结束。每次运行后,无论结果如何,系统都会重新对其进行调度。

在这里插入图片描述

管理 Worker

定义 WorkerWorkRequest 后,最后一步是将工作加入队列。将工作加入队列的最简单方法是调用 WorkManager enqueue() 方法,然后传递要运行的 WorkRequest

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(context).enqueue(myWork)
  • 1
  • 2

在将工作加入队列时请小心谨慎,以避免重复。例如,应用可能会每 24 小时尝试将其日志上传到后端服务。如果不谨慎,即使作业只需运行一次,您最终也可能会多次将同一作业加入队列。为了实现此目标,您可以将工作调度为唯一工作。

Unique Worker

Unique Worker 是一个很实用的概念,可确保同一时刻只有一个具有特定名称的工作实例。与 ID 不同的是,Unique Worker 的名称是人类可读的,由开发者指定,而不是由 WorkManager 自动生成。与标记不同,Unique Worker 的名称仅与一个工作实例相关联。

Unique Worker既可用于一次性工作,也可用于定期工作。您可以通过调用以下方法之一创建Unique Worker序列,具体取决于您是调度重复工作还是一次性工作:

  • WorkManager.enqueueUniqueWork()(用于一次性工作)
  • WorkManager.enqueueUniquePeriodicWork()(用于定期工作)

这两种方法都接受 3 个参数:

  • uniqueWorkName - 用于唯一标识工作请求的 String 类型名称。
  • existingWorkPolicy - 此 enum 可告知 WorkManager:如果已有使用该名称且尚未完成的唯一工作链时,应执行什么策略(见下文)。
  • work - 要调度的 WorkRequest

借助Unique Worker,我们可以解决前面提到的重复调度问题。


val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder().setRequiresCharging(true).build())
           .build()
           
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

现在,如果上述代码在 sendLogs 作业已处于队列中的情况下运行,系统会保留现有的作业,并且不会添加新的作业。

当您需要逐步构建一个长任务链时,也可以利用唯一工作序列。例如,照片编辑应用可能允许用户撤消一长串操作。其中的每一项撤消操作可能都需要一些时间来完成,但必须按正确的顺序执行。在这种情况下,应用可以创建一个“撤消”链,并根据需要将每个撤消操作附加到该链上。

冲突解决策略

调度 Unique Worker 时,必须告知 WorkManager 在发生冲突时要执行的操作。您可以通过在将工作加入队列时传递一个枚举值来实现此目的。

对于一次性工作,可以设置 ExistingWorkPolicy,它提供了用于处理冲突的 4 个选项:

  • REPLACE:用新工作替换现有工作。此选项将取消现有工作。

  • KEEP:保留现有工作,并忽略新工作。

  • APPEND:将新工作附加到现有工作的末尾。此政策将导致您的新工作链接到现有工作,在现有工作完成后运行。
    现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLEDFAILED 状态,新工作也会变为 CANCELLEDFAILED。如果您希望无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE

  • APPEND_OR_REPLACE 函数类似于 APPEND,不过它并不依赖于先决条件工作状态。即使现有工作变为 CANCELLEDFAILED 状态,新工作仍会运行。

对于定期工作,您需要提供一个 ExistingPeriodicWorkPolicy,它支持 REPLACEKEEP 这两个选项。选项的功能与其对应的 ExistingWorkPolicy 功能相同。

观察/查询 Worker 的信息

在将Worker加入队列后,您可以随时按其 nameid 或与其关联的 tagWorkManager 中进行查询,以检查其状态。

// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

该查询会返回 WorkInfo 对象的 ListenableFutureWorkInfo 对象中包含了 Worker 的 idtag标记、当前的状态 State 以及通过 Result.success(outputData) 设置的任何输出数据。

利用每个方法的 LiveData 变种,您可以通过注册监听器来观察 WorkInfo 的变化。例如,如果您想要在某项工作成功完成后向用户显示消息,您可以进行如下设置:

workManager.getWorkInfoByIdLiveData(syncWorker.id).observe(viewLifecycleOwner) {
    workInfo ->
   if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
   
       Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT).show()
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中workManager.getWorkInfoByIdLiveData()返回的是一个LiveData对象,可以给它添加一个Observer观察者回调。

需要注意的是,由于LiveData的特性,当doWork执行完后,发送给Activity中注册的观察者回调时,会收到多次,因为每一种状态都会执行回调,只有当 isFinished=true 时,才表示任务真的执行完成了。(LiveData绑定Activity的生命周期的观察者回调,在onCreate、onResume都会回调)如果不使用LiveData,就只会有一次。

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(mathWork.id).observe(this, Observer {
    info ->
     if (info != null && info.state.isFinished) {
   
          val myResult = info.outputData.getInt(KEY_RESULT, myDefaultValue)
          // ... do something with the result ...
      }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
使用 WorkQuery 进行复杂的 Worker 查询

WorkManager 2.4.0 及更高版本支持使用 WorkQuery 对象对已加入队列的作业进行复杂查询。WorkQuery 支持按工作的标记、状态和 Unique Worker 名称的组合进行查询。

以下示例说明了如何查找带有“syncTag”标记、处于 FAILEDCANCELLED 状态、且 Unique Worker 名称为“preProcess”或“sync”的所有工作。

val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

WorkQuery 中的每个组件(标记、状态或名称)与其他组件都是 AND 逻辑关系。组件中的每个值都是 OR 逻辑关系。
例如:(name1 OR name2 OR ... ) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)

WorkQuery 也适用于等效的 LiveData 方法 getWorkInfosLiveData()

取消和停止工作

如果您不再需要运行先前加入队列的工作, 可以按工作的 nameid 或与其关联的 tag 取消工作。

// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

WorkManager 会在后台检查工作的 State。如果工作已经完成,系统不会执行任何操作。否则,工作的状态会更改为 CANCELLED,之后就不会运行这个工作。任何依赖于此工作的 WorkRequest 作业也将变为 CANCELLED

目前,RUNNING 可收到对 ListenableWorker.onStopped() 的调用。如需执行任何清理操作,请覆写此方法。

注意:cancelAllWorkByTag(String) 会取消具有给定标记的所有工作。

监听 Worker 的停止

正在运行的 Worker 可能会由于以下几种原因而停止运行:

  • 您明确要求取消它(例如,通过调用 WorkManager.cancelWorkById(UUID) 取消)。
  • 如果是唯一工作,您明确地将 ExistingWorkPolicyREPLACE 的新 WorkRequest 加入到了队列中。旧的 WorkRequest 会立即被视为已取消。
  • 您的工作约束条件已不再满足。
  • 系统出于某种原因指示您的应用停止工作。如果超过 10 分钟的执行期限,可能会发生这种情况。该工作会调度为在稍后重试。

在这些情况下,您的工作器会停止。

您应该合作地取消正在进行的任何工作,并释放您的工作器保留的所有资源。例如,此时应该关闭所打开的数据库和文件句柄。有两种机制可让您获取工作器何时停止:

  • onStopped() 回调: 在您的工作器停止后,WorkManager 会立即调用 ListenableWorker.onStopped()。覆写此方法可关闭您可能保留的所有资源。

  • isStopped() 属性: 您可以调用 ListenableWorker.isStopped() 方法以检查工作器是否已停止。如果您在工作器中执行长时间运行的操作或重复操作,您应经常检查此属性,并将其用作尽快停止工作的信号。

注意:WorkManager 会忽略已收到 onStop 信号的工作器所设置的 Result,因为工作器已被视为停止。

观察 Worker 的中间进度

如果应用在前台运行时,工作器保持运行状态,那么可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息。

ListenableWorker 现在支持 setProgressAsync() API,此类 API 允许保留中间进度。借助这些 API,开发者能够设置可通过界面观察到的中间进度。进度由 Data 类型表示,这是一个可序列化的属性容器(类似于 inputoutput,并且受到相同的限制)。

只有在 ListenableWorker 运行时才能观察到和更新进度信息。如果尝试在 ListenableWorker 完成执行后在其中设置进度,则将会被忽略。您还可以使用 getWorkInfoByXXX()getWorkInfoByXXXLiveData() 方法来观察进度信息。这两个方法会返回 WorkInfo 的实例,后者有一个返回 Data 类型的新 getProgress() 方法。

更新进度

对于使用 ListenableWorkerWorkerJava 开发者,setProgressAsync() API 会返回 ListenableFuture<Void>;更新进度是异步过程,因为更新过程涉及将进度信息存储在数据库中。在 Kotlin 中,您可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息。

此示例展示了一个简单的 ProgressWorkerWorker 在启动时将进度设置为 0,在完成后将进度值更新为 100

// Kotlin 版本
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import kotlinx.coroutines.delay

class ProgressWorker(context: Context, parameters: WorkerParameters) :
    CoroutineWorker(context, parameters) {
   

    companion object {
   
        const val Progress = "Progress"
        private const val delayDuration = 1L
    }

    override suspend fun doWork(): Result {
   
        val firstUpdate = workDataOf(Progress to 0)
        val lastUpdate = workDataOf(Progress to 100)
        setProgress(firstUpdate)
        delay(delayDuration)
        setProgress(lastUpdate)
        return Result.success()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
// Java 版本
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

public class ProgressWorker extends Worker {
   

    private s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/253926
推荐阅读
相关标签
  

闽ICP备14008679号