赞
踩
WorkManager 是Android最新一代的后台调度 API,主要是用来替换先前的旧版后台调度 API(如 JobScheduler 等)。
WorkManager 是适合用于持久性工作的推荐解决方案。如果工作始终要通过应用重启和系统重新启动来调度,便是持久性的工作。由于大多数后台处理操作都是通过持久性工作完成的,因此 WorkManager 是适用于后台处理操作的主要推荐 API。
WorkManager 可处理三种类型的持久性工作:
图 1 大致表明了不同类型的持久性工作彼此之间的关系。
下表大致列出了各种工作类型:
类型 | 周期 | 使用方式 |
---|---|---|
立即 | 一次性 | OneTimeWorkRequest 和 Worker。如需处理加急工作,请对 OneTimeWorkRequest 调用 setExpedited()。 |
长期运行 | 一次性或定期 | 任意 WorkRequest 或 Worker。在工作器中调用 setForeground() 来处理通知。 |
可延期 | 一次性或定期 | PeriodicWorkRequest 和 Worker。 |
除了具备更为简单且一致的 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()
WorkManager 适用于需要可靠运行的工作,即使用户导航离开屏幕、退出应用或重启设备也不影响工作的执行。例如:
一个最好用的场景是:当网络状态恢复时执行某个后台任务。可靠性:指定条件的任务,哪怕App进程被杀了,也一定会执行。
WorkManager 不适用于那些可在应用进程结束时安全终止的进程内后台工作。它也并非对所有需要立即执行的工作都适用的通用解决方案。
虽然协程是适合某些用例的推荐解决方案,但您不应将其用于持久性工作。请务必注意,协程是一个并发框架,而 WorkManager 是一个持久性工作库。同样,AlarmManager 仅适合用于时钟或日历。
API | 推荐使用场景 | 与 WorkManager 的关系 |
---|---|---|
Coroutines | 所有不需要持久的异步工作。 | 协程是在 Kotlin 中退出主线程的标准方式。不过,它们在应用关闭后会释放内存。对于持久性工作,请使用 WorkManager。 |
AlarmManager | 仅限闹钟。 | 与 WorkManager 不同,AlarmManager 会使设备从低电耗模式中唤醒。因此,它在电源和资源管理方面来讲并不高效。AlarmManager 仅适合用于精确闹钟或通知(例如日历活动)场景,而不适用于后台工作。 |
添加依赖
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")
}
一般只需使用work-runtime-ktx
版本的就可以了。
WorkManager 的使用主要包括三个核心类:
工作使用 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()
}
}
从 doWork()
返回的 Result
会通知 WorkManager
服务工作是否成功,以及工作失败时是否应重试工作。
Result.success()
:工作成功完成。Result.failure()
:工作失败。Result.retry()
:工作失败,应根据其重试政策在其他时间尝试。定义工作后,必须使用 WorkManager
服务进行调度该工作才能运行。对于如何调度工作,WorkManager
提供了很大的灵活性。您可以将其安排为在某段时间内定期运行,也可以将其安排为仅运行一次。
WorkRequest
对象包含 WorkManager
调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。Worker
负责定义工作内容,WorkRequest
则负责定义工作运行方式和时间。
执行工作器的确切时间取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供最佳行为。
WorkRequest
本身是抽象基类。该类有两个派生实现:
OneTimeWorkRequest
:适用于调度非重复性的一次性工作PeriodicWorkRequest
适合调度以一定间隔重复执行的工作对于无需额外配置的简单工作,请使用静态方法 from:
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
对于更复杂的工作,可以使用构建器:
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
// Additional configuration
.build()
最后,您需要使用 enqueue()
方法将 WorkRequest
提交到 WorkManager
WorkManager.getInstance(myContext).enqueue(uploadWorkRequest)
WorkManager 2.7.0
引入了加急工作的概念。这使 WorkManager 能够执行重要工作,同时使系统能够更好地控制对资源的访问权限。
加急工作具有以下特征:
在用户想要发送消息或附加的图片时,可能会在聊天应用内使用加急工作。同样,处理付款或订阅流程的应用也可能需要使用加急工作。这是因为这些任务对用户很重要,会在后台快速执行,并需要立即开始执行。
可以通过调用 setExpedited()
来声明 WorkRequest
应该使用加急作业,以尽可能快的速度运行。例如以下代码示例:
val request = OneTimeWorkRequestBuilder()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(context).enqueue(request)
在此示例中,我们初始化 OneTimeWorkRequest
的实例并对其调用 setExpedited()
,然后,此请求就会变成加急工作。如果配额允许,它将立即开始在后台运行。
为了保持加急作业的向后兼容性,WorkManager
可能会在 Android 12
之前版本的平台上运行前台服务。前台服务可以向用户显示通知。
在 Android 12
之前,工作器中的 getForegroundInfoAsync()
和 getForegroundInfo()
方法可让 WorkManager
在您调用 setExpedited()
时显示通知。
如果您想要请求任务作为加急作业运行,则所有的 ListenableWorker
都必须实现 getForegroundInfo
方法。
注意:如果未能实现对应的
getForegroundInfo
方法,那么在旧版平台上调用setExpedited
时,可能会导致运行时崩溃。
工作器不知道自身所执行的工作是否已加急。不过,在某些版本的 Android 上,如果 WorkRequest 被加急,工作器可以显示通知。
为此,WorkManager 提供了 getForegroundInfoAsync()
方法,您必须实现该方法,让 WorkManager 在必要时显示通知,以便启动 ForegroundService
。
如果您使用 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() } }
注意:您应该将
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()
在此示例中,工作的运行时间间隔定为一小时。
时间间隔定义为两次重复执行之间的最短时间。工作器的确切执行时间取决于您在 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()
重复间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS
(15分钟),而弹性间隔必须大于或等于 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS
(5分钟)。
您可以对定期工作设置约束。例如,您可以为工作请求添加约束,以便工作仅在用户设备充电时运行。在这种情况下,除非满足约束条件,否则即使过了定义的重复间隔,PeriodicWorkRequest 也不会运行。这可能会导致工作在某次运行时出现延迟,甚至会因在相应间隔内未满足条件而被跳过。
约束可确保将工作延迟到满足最佳条件时运行。WorkManager定义了一些类型的约束
true
,那么当设备处于“电量不足模式”时,工作不会运行。true
,那么工作只能在设备充电时运行。true
,则要求用户的设备必须处于空闲状态,才能运行工作。在运行批量操作时,此约束会非常有用;若是不用此约束,批量操作可能会降低用户设备上正在积极运行的其他应用的性能。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()
如果指定了多个约束,工作将仅在满足所有约束时才会运行。
如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。
如果工作没有约束,或者当工作加入队列时所有约束都得到了满足,那么系统可能会选择立即运行该工作。如果您不希望工作立即运行,可以将工作指定为在经过一段最短初始延迟时间后再启动。
下面举例说明了如何将工作设置为在加入队列后至少经过 10 分钟后再运行。
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
该示例说明了如何为 OneTimeWorkRequest
设置初始延迟时间,您也可以为 PeriodicWorkRequest
设置初始延迟时间(在这种情况下,定期工作只有首次运行时会延迟。)。
注意:执行工作器的确切时间还取决于 WorkRequest 中使用的约束和系统优化方式。WorkManager 经过设计,能够在满足这些约束的情况下提供可能的最佳行为。
如果您需要让 WorkManager
重试工作,可以在 doWork()
方法返回 Result.retry()
。然后,系统将根据退避延迟时间和退避策略重新调度工作。
退避延迟时间 指定了首次尝试后重试工作前的最短等待时间。此值不能超过 10 秒
(或 MIN_BACKOFF_MILLIS
)。
退避策略 定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长。WorkManager 支持 2
个退避策略,即 LINEAR
和 EXPONENTIAL
。
每个工作请求都有退避策略和退避延迟时间。默认策略是 EXPONENTIAL
,延迟时间为 10 秒
,但您可以在工作请求配置中替换此设置。
以下是自定义退避延迟时间和策略的示例:
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.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
对象列表,该列表可用于确定当前工作状态。
要标记一个 Worker
,请使用 WorkRequest 构建器的 addTag()
方法。以下代码展示了如何向工作添加“cleanup
”标记:
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.addTag("cleanup")
.build()
最后,可以向单个工作请求添加多个标记。这些标记在内部以一组字符串的形式进行存储。您可以使用 WorkInfo.getTags()
获取与 WorkRequest
关联的标记集。在 Worker
类中,您可以通过 getTags()
方法检索其标记集。
通过 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()
同样,可使用 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 ...
}
})
如果您链接多个任务,一个任务的输出可以作为任务链中下一个任务的输入。如果是简单链(即一个 OneTimeWorkRequest
后面跟着另一个 OneTimeWorkRequest
),第一个任务通过调用 Result.success(Data)
返回其结果,下一个任务通过调用 getInputData()
提取该结果。如果是更复杂的链(例如有多个任务都将输出发送给同一个后续任务),您可以在 OneTimeWorkRequest.Builder
上定义 InputMerger
,以指定当多个不同任务返回具有相同键的输出时应执行什么操作。
对于 one-time
工作请求,工作的初始状态为 ENQUEUED
。
在 ENQUEUED
状态下,您的工作会在满足其约束和初始延迟计时要求后立即运行。接下来,该工作会转为 RUNNING
状态,然后可能会根据工作的结果转为 SUCCEEDED
、FAILED
状态;或者,如果结果是 retry
,它可能会回到 ENQUEUED
状态。在此过程中,随时都可以取消工作,取消后工作将进入 CANCELLED
状态。
SUCCEEDED
、FAILED
和 CANCELLED
均表示此工作的终止状态。如果您的工作处于上述任何状态,WorkInfo.State.isFinished()
都将返回 true
。
成功和失败状态仅适用于一次性工作和链式工作。定期工作只有一个终止状态 CANCELLED
。这是因为定期工作永远不会结束。每次运行后,无论结果如何,系统都会重新对其进行调度。
定义 Worker
和 WorkRequest
后,最后一步是将工作加入队列。将工作加入队列的最简单方法是调用 WorkManager enqueue()
方法,然后传递要运行的 WorkRequest
。
val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(context).enqueue(myWork)
在将工作加入队列时请小心谨慎,以避免重复。例如,应用可能会每 24 小时尝试将其日志上传到后端服务。如果不谨慎,即使作业只需运行一次,您最终也可能会多次将同一作业加入队列。为了实现此目标,您可以将工作调度为唯一工作。
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)
现在,如果上述代码在 sendLogs 作业已处于队列中的情况下运行,系统会保留现有的作业,并且不会添加新的作业。
当您需要逐步构建一个长任务链时,也可以利用唯一工作序列。例如,照片编辑应用可能允许用户撤消一长串操作。其中的每一项撤消操作可能都需要一些时间来完成,但必须按正确的顺序执行。在这种情况下,应用可以创建一个“撤消”链,并根据需要将每个撤消操作附加到该链上。
调度 Unique Worker 时,必须告知 WorkManager 在发生冲突时要执行的操作。您可以通过在将工作加入队列时传递一个枚举值来实现此目的。
对于一次性工作,可以设置 ExistingWorkPolicy
,它提供了用于处理冲突的 4 个选项:
REPLACE:用新工作替换现有工作。此选项将取消现有工作。
KEEP:保留现有工作,并忽略新工作。
APPEND:将新工作附加到现有工作的末尾。此政策将导致您的新工作链接到现有工作,在现有工作完成后运行。
现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLED
或 FAILED
状态,新工作也会变为 CANCELLED
或 FAILED
。如果您希望无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE
。
APPEND_OR_REPLACE 函数类似于 APPEND
,不过它并不依赖于先决条件工作状态。即使现有工作变为 CANCELLED
或 FAILED
状态,新工作仍会运行。
对于定期工作,您需要提供一个 ExistingPeriodicWorkPolicy
,它支持 REPLACE 和 KEEP 这两个选项。选项的功能与其对应的 ExistingWorkPolicy
功能相同。
在将Worker
加入队列后,您可以随时按其 name
、id
或与其关联的 tag
在 WorkManager
中进行查询,以检查其状态。
// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
该查询会返回 WorkInfo
对象的 ListenableFuture
, WorkInfo
对象中包含了 Worker 的 id
、tag
标记、当前的状态 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()
}
}
其中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 ...
}
})
WorkManager 2.4.0
及更高版本支持使用 WorkQuery
对象对已加入队列的作业进行复杂查询。WorkQuery
支持按工作的标记、状态和 Unique Worker
名称的组合进行查询。
以下示例说明了如何查找带有“syncTag
”标记、处于 FAILED
或 CANCELLED
状态、且 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)
WorkQuery
中的每个组件(标记、状态或名称)与其他组件都是 AND
逻辑关系。组件中的每个值都是 OR
逻辑关系。
例如:(name1 OR name2 OR ... ) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)
。
WorkQuery
也适用于等效的 LiveData
方法 getWorkInfosLiveData()
。
如果您不再需要运行先前加入队列的工作, 可以按工作的 name
、id
或与其关联的 tag
取消工作。
// by id
workManager.cancelWorkById(syncWorker.id)
// by name
workManager.cancelUniqueWork("sync")
// by tag
workManager.cancelAllWorkByTag("syncTag")
WorkManager
会在后台检查工作的 State
。如果工作已经完成,系统不会执行任何操作。否则,工作的状态会更改为 CANCELLED
,之后就不会运行这个工作。任何依赖于此工作的 WorkRequest
作业也将变为 CANCELLED
。
目前,RUNNING
可收到对 ListenableWorker.onStopped()
的调用。如需执行任何清理操作,请覆写此方法。
注意:
cancelAllWorkByTag(String)
会取消具有给定标记的所有工作。
正在运行的 Worker 可能会由于以下几种原因而停止运行:
WorkManager.cancelWorkById(UUID)
取消)。ExistingWorkPolicy
为 REPLACE
的新 WorkRequest
加入到了队列中。旧的 WorkRequest
会立即被视为已取消。在这些情况下,您的工作器会停止。
您应该合作地取消正在进行的任何工作,并释放您的工作器保留的所有资源。例如,此时应该关闭所打开的数据库和文件句柄。有两种机制可让您获取工作器何时停止:
onStopped() 回调: 在您的工作器停止后,WorkManager
会立即调用 ListenableWorker.onStopped()
。覆写此方法可关闭您可能保留的所有资源。
isStopped() 属性: 您可以调用 ListenableWorker.isStopped()
方法以检查工作器是否已停止。如果您在工作器中执行长时间运行的操作或重复操作,您应经常检查此属性,并将其用作尽快停止工作的信号。
注意:
WorkManager
会忽略已收到onStop
信号的工作器所设置的Result
,因为工作器已被视为停止。
如果应用在前台运行时,工作器保持运行状态,那么可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息。
ListenableWorker
现在支持 setProgressAsync()
API,此类 API 允许保留中间进度。借助这些 API,开发者能够设置可通过界面观察到的中间进度。进度由 Data
类型表示,这是一个可序列化的属性容器(类似于 input
和 output
,并且受到相同的限制)。
只有在 ListenableWorker
运行时才能观察到和更新进度信息。如果尝试在 ListenableWorker
完成执行后在其中设置进度,则将会被忽略。您还可以使用 getWorkInfoByXXX()
或 getWorkInfoByXXXLiveData()
方法来观察进度信息。这两个方法会返回 WorkInfo
的实例,后者有一个返回 Data
类型的新 getProgress()
方法。
对于使用 ListenableWorker
或 Worker
的 Java
开发者,setProgressAsync()
API 会返回 ListenableFuture<Void>
;更新进度是异步过程,因为更新过程涉及将进度信息存储在数据库中。在 Kotlin
中,您可以使用 CoroutineWorker
对象的 setProgress()
扩展函数来更新进度信息。
此示例展示了一个简单的 ProgressWorker
。Worker
在启动时将进度设置为 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() } }
// 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。