WorkManager笔记: 二、管理工作


一、标记工作

每个工作请求都有一个唯一标识符(这应该指 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,它支持 REPLACEKEEP 这两个选项。这些选项的功能与其对应的 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) 取消)。
  • 如果是唯一工作,您明确地将 ExistingWorkPolicyREPLACE 的新 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 为设置和观察工作器的中间进度添加了一流的支持。

对于使用 ListenableWorkerWorker 的 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 - 是 WorkerCoroutineWorkerRxWorker 的基类。

对于 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笔记: 三、加急工作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值