文章目录
一、标记工作
每个工作请求都有一个唯一标识符(这应该指 UUID),该标识符可用于在以后标识该工作,以便取消工作或观察其进度。
一个工作可以由多个 TAG
, 多个工作也可以有相同的 TAG
Tag使用示例:
// 会取消带有特定标记的所有工作请求
WorkManager.getInstance(this).cancelAllWorkByTag("tag")
// getWorkInfosByTag 会返回一个 WorkInfo 对象列表; 该列表可用于确定当前工作状态
val list: MutableList<WorkInfo> = WorkManager.getInstance(this).getWorkInfosByTag("tag").get()
添加 Tag:
val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
.addTag("tag")
.addTag("tag1") // 也可以添加多个标记;
.build()
二、输入输出数据
1.设置输入数据
我们为工作添加 Tag
, 并通过 setInputData()
添加输入数据
val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setInputData(workDataOf("input" to "我是输入数据")) // 添加数据
.addTag("transmit_data") //设置tag
.build()
WorkManager.getInstance(this).enqueue(myWorkRequest)
2.设置输出数据
在任务中, 获取 输入的数据; 并设置输出数据
class MyWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
// 接收输入数据
val imageUriInput = inputData.getString("input")
Log.d("wwwwwwwwwww", "输入数据为: ${imageUriInput.toString()}")
// 这样输出数据;
val outputData = Data.Builder()
.putString("result", "我是输出数据")
.build()
return Result.success(outputData)
}
}
3.拿到输出结果
等任务执行结束后, 我们通过 Tag
拿到 WorkInfo
, 再获取 outputData
val list: MutableList<WorkInfo> = WorkManager.getInstance(this).getWorkInfosByTag("transmit_data").get()
// 当 状态为 SUCCEEDED 时, 获取输出数据; 当然 FAILED 同样可以输出数据;
if(!list.isNullOrEmpty() && list[0].state == WorkInfo.State.SUCCEEDED){
val resultStr = list[0].outputData.getString("result")
Log.d("wwwwwwwwwww", "输出数据为: ${resultStr.toString()}")
}
三、管理工作
1.唯一工作:
假如, 有一个每24小时 上传日志的 定期工作. 我们启动它之前, 都要先检测一下它是否已在运行; 避免将同一工作多次入列的问题;
唯一工作 可确保同一时刻只有一个具有特定名称的工作实例。与 ID 不同的是,唯一名称是人类可读的,由开发者指定,而不是由 WorkManager 自动生成。与标记不同,唯一名称仅与一个工作实例相关联。
一次性的 唯一工作:
val myWorkRequest: OneTimeWorkRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(this).enqueueUniqueWork("single_onetime", ExistingWorkPolicy.KEEP, myWorkRequest)
定期的 唯一工作:
val myWorkRequest: PeriodicWorkRequest = PeriodicWorkRequestBuilder<MyWorker>(1, TimeUnit.HOURS).build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork("single_periodic", ExistingPeriodicWorkPolicy.KEEP, myWorkRequest)
这两种方法都接受 3 个参数:
uniqueWorkName
- 用于唯一标识工作请求的 String;existingWorkPolicy
- 此 enum 可告知 WorkManager:如果已有使用该名称且尚未完成的唯一工作链,应执行什么操作。work
- 要调度的 WorkRequest
冲突解决政策:
唯一工作下达时, 如果旧的工作仍在执行. 此时就要考虑, 是替换掉旧工作. 还是说让旧工作继续了
REPLACE
- 用新工作替换现有工作。此选项将取消现有工作KEEP
- 保留现有工作,并忽略新工作。APPEND
- 将新工作附加到现有工作的末尾。此政策将导致您的新工作链接到现有工作,在现有工作完成后运行。
现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLED 或 FAILED 状态,新工作也会变为 CANCELLED 或 FAILED。如果您希望无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE。APPEND_OR_REPLACE
- 类似于 APPEND,不过它并不依赖于先决条件工作状态。即使现有工作变为 CANCELLED 或 FAILED 状态,新工作仍会运行。
对于定期工作,您需要提供一个 ExistingPeriodicWorkPolicy
,它支持 REPLACE
和 KEEP
这两个选项。这些选项的功能与其对应的 ExistingWorkPolicy
功能相同。
2.观察您的工作
要观察工作, 先得获取对应的 WorkInfo
对象; 它包含工作的 id、标记、当前的 State 以及输出数据。
获取方法如下:
// by id; workId(UUID) 是工作的 ID.
// 创建 WorkRequest 后, 可以 myWorkRequest.id 获取ID; 也可以从 doWork() 函数中可以拿到 ID;
val workInfo: WorkInfo = WorkManager.getInstance(this).getWorkInfoById(workId).get()
// by name
val workInfos: MutableList<WorkInfo> = WorkManager.getInstance(this).getWorkInfosForUniqueWork("single_periodic").get()
// by tag
val list: MutableList<WorkInfo> = WorkManager.getInstance(this).getWorkInfosByTag("transmit_data").get()
拿到工作 ID 的方法:
// 1. 创建 WorkRequest 后
val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>().build()
Log.d("wwwwwwwwwww", "id=: ${myWorkRequest.id}") // myWorkRequest.id
// 2. doWork() 函数中
class MyWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
Log.d("wwwwwwwwwww", "id=: $id") // id
return Result.success()
}
}
观察工作状态变化:
以上每个方法都有 LiveData
变种, 可以通过注册监听器来观察 WorkInfo
的变化;
WorkManager.getInstance(this).getWorkInfoByIdLiveData(workId).observe(this){ workInfo ->
if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
// 工作完成;
}
}
复杂的工作查询:
WorkManager 2.4.0
及更高版本支持使用 WorkQuery
对象对已加入队列的作业进行复杂查询。WorkQuery
支持按工作的标记、状态和唯一工作名称的组合进行查询。
val workQuery = WorkQuery.Builder
// .fromStates(...)
// .fromIds(..)
// .fromUniqueWorkNames(...)
.fromTags(listOf("syncTag"))
.addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
.addUniqueWorkNames(listOf("preProcess", "sync"))
// .addIds()
// .addTags()
.build()
val workInfos: MutableList<WorkInfo> = WorkManager.getInstance(this).getWorkInfos(workQuery).get()
注意: 以 from 开头的函数, 是建造者类的静态函数; 先用 from 函数来获取 建造者对象, 再 add 附加条件
3.取消和停止工作
// by id
WorkManager.getInstance(this).cancelWorkById(syncWorker.id)
// by name
WorkManager.getInstance(this).cancelUniqueWork("single_onetime")
// by tag
WorkManager.getInstance(this).cancelAllWorkByTag("transmit_data")
注意:
如果工作已经完成,系统不会执行任何操作
否则,工作的状态会更改为
CANCELLED
之后就不会运行这个工作。
cancelAllWorkByTag(String)
会取消具有给定标记的所有工作。
假如工作代码 正在运行, 则可以收到 onStopped() 的调用, 该方法可以执行 清理操作, 资源释放等
停止正在运行的工作器:
正在运行的 Worker 可能会由于以下几种原因而停止运行:
- 您明确要求取消它(例如,通过调用
WorkManager.cancelWorkById(UUID)
取消)。 - 如果是唯一工作,您明确地将
ExistingWorkPolicy
为REPLACE
的新WorkRequest
加入到了队列中。旧的WorkRequest
会立即被视为已取消。 - 您的工作约束条件已不再满足。
- 系统出于某种原因指示您的应用停止工作。如果超过 10 分钟的执行期限,可能会发生这种情况。该工作会调度为在稍后重试。
在您的工作器停止后,WorkManager 会立即调用 ListenableWorker.onStopped()。重写此方法 做一些回收操作
注意: ListenableWorker 是所有Worker类 的总基类
isStopped()
您可以调用 ListenableWorker.isStopped()
方法以检查工作器是否已停止。如果您在工作器执行长时间运行的操作或重复操作,您应经常检查此属性,并尽快将其用作停止工作的信号。
class MyWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
// 在适当位置 检查停止状态;
while (!isStopped){
// doSomething
}
return Result.success()
}
override fun onStopped() {
// TODO 做资源回收操作, 例如IO, cursor, bitmap等
}
}
注意:WorkManager 会忽略已收到 onStop 信号的工作器所设置的 Result,因为工作器已被视为停止。
四、观察工作器执行进度
WorkManager 2.3.0-alpha01
为设置和观察工作器的中间进度添加了一流的支持。
对于使用 ListenableWorker
或 Worker
的 Java 开发者, 应使用 setProgressAsync()
API, 它会返回 ListenableFuture<Void>
;更新进度是异步过程; 在 Kotlin 中,您可以使用 CoroutineWorker
对象的 setProgress()
扩展函数来更新进度信息。
我们就用 Kotlin 写一个进度监听的例子:
// 使用协程 需要继承 CoroutineWorker; 重写 挂起函数 doWork()
class ProgressWorker(appContext: Context, workerParams: WorkerParameters):
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
var pro = 0
while (pro < 100){
// 这里每 100ms, 更新进度 +1
delay(100)
pro++
setProgress(workDataOf("progress" to pro))
}
return Result.success()
}
}
// 这里工作入列, 并监听进度, 然后用 ProgressBar 展示进度
private fun doProgressWork() {
val myWorkRequest = OneTimeWorkRequestBuilder<ProgressWorker>().build()
val workId = myWorkRequest.id
WorkManager.getInstance(this).let {
// 工作任务入列
it.enqueue(myWorkRequest)
// 用 getWorkInfoBy…LiveData() 变种方法, 注册 LiveData 观察者
it.getWorkInfoByIdLiveData(workId).observe(this){ workInfo ->
when(workInfo?.state){
WorkInfo.State.RUNNING -> {
// 更新进度条
val progress = workInfo.progress.getInt("progress", 0)
binding.pbProgress.progress = progress
}
WorkInfo.State.SUCCEEDED -> {
binding.pbProgress.progress = 100
toast("工作完成了")
}
}
}
}
}
五、WorkManager 中的线程处理
WorkManager 提供了四种不同类型的工作基元:
Worker
- 是最简单的实现,WorkManager
会在后台线程中自动运行该基元CoroutineWorker
- 是为Kotlin
用户建议的实现。它公开了后台工作的一个挂起函数; 它运行默认的Dispatcher
,您也可以自定义。RxWorker
- 是为 RxJava 用户建议的实现。ListenableWorker
- 是Worker
、CoroutineWorker
和RxWorker
的基类。
对于 Kotlin
用户,WorkManager
为协程提供了一流的支持。 不要扩展 Worker
,而应扩展 CoroutineWorker
,后者包含 doWork()
的挂起版本。 CoroutineWorker.doWork()
是一个“挂起”函数。 默认调度器为 Dispatchers.Default
, 当然我们可以用 withContext
来调整
class MyWorker(appContext: Context, workerParams: WorkerParameters):
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
withContext(Dispatchers.IO){
// doSomething
}
return Result.success()
}
}
总结
以上就是今天要讲的内容,本文主要讲了对工作的 标记、观察、管理操作
等 以及传入传出数据
还有一个 链接工作
说的多个任务执行顺序的问题, 工作可以并行、串行、向下传递输出的数据、工作状态结果
等. 博主实在看不动, 这部分跳过. 有该类业务时再看; 将工作链接在一起
上一篇: WorkManager笔记: 一、基础入门
下一篇: WorkManager笔记: 三、加急工作