随笔博文

Jetpack之—WorkManger

2022-12-12 13:21:38 michael007js 155

简介

Jetpack的文档库里面是这样介绍的:

  • 使用WorkManager API可以轻松地调度那些必须可靠运行的可延期异步任务。通过这些 API,您可以创建任务并提交给WorkManager,以便在满足工作约束条件时运行。

WorkManger是Android Jetpack里面的一个重量级组件,它是一个提供异步执行任务的管理框架,会根据系统的版本和应用的状态来选择执行任务的时机。当应用在运行的时候会在应用的进程中开一条线程来执行任务,当退出应用时,WorkManager会选择根据设备的系统版本使用适合的算法调用JobScheduler或者Firebase JobDispatcher或者AlarmManager来执行任务。所以WorkManger最重要的作用是可确保重要的后台任务一定会执行,例如上传,下载,同步服务器等操作。

WorkManager调度服务根据的条件如下:

微信图片_20220422233531.png

小弟目前公司项目最低兼容版本已经去到了21,其实在用户机型统计上,23以下的机器都是极少了,几十万用户中才占了百分之零点几,所以后面的新项目直接最低兼容到23,但如果是在终端项目的话,我们还是兼容到19,也就是4.4版本的系统。对于国内应用开发,低于23的版本也只需关注通过BroadcastReceiverAlarmManager调度就行了。

使用WorkManager有哪些优势

  • 版本兼容性强,最低可兼容至API 14;

  • 可以指定任务执行的约束条件,比如可以选择必须在有网络的条件或充电状态状态下执行;

  • 可定时执行也可单次执行;

  • 监听和管理任务状态;

  • 多个任务可使用任务链;

  • 保证任务执行,如当前执行条件不满足或者App进程被杀死,它会等到下次条件满足或者App进程打开后执行;

  • 支持省电模式(这很重要)。 WorkManager最重要的宗旨就是延迟任务的执行,并且保证了在退出应用或重启设备任务依然可靠执行,这是因为它通过Room组件实现了数据的本地持久化。由于是延迟任务执行,所以WorkManager的适用于一些即时性不高的任务场景,比如:

  • 定时或者不定期向服务器提交应用的一些用户数据或者使用统计日志;

  • 后台更新用户信息,比如查询订阅用户状态是否到期。

WorkManager的使用

依赖库

implementation "androidx.work:work-runtime-ktx:2.7.1"

自定义Worker

在构建自定义Worker之前,我们来了解WorkManager几个关键的类:

  • Worker:自定义一个类继承Worker,并复写doWork()方法,在doWork()方法中放入你需要在后台执行的代码;

  • WorkRequest:后台工作的请求,你可以在后台工作的请求中添加约束条件;

  • WorkManager:真正让Worker在后台执行的类。

WorkManger的执行流程分为三步走:

  1. WorkRequest生成以后,Internal TaskExecutor将它存入WorkMangerRoom数据库中,这也是为什么即使在程序退出之后,WorkManger也能保证后台任务在下次启动后条件满足的情况下执行;

  2. 当约束条件满足的情况下,Internal TaskExecutor告诉WorkFactory生成Worker;

  3. 后台任务Worker执行。

构建自定义Worker

创建一个类继承抽象类Worker,重写doWork()方法在doWork()方法中实现具体任务的逻辑

class CustomWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
   override fun doWork(): Result {

       Log.d("CustomWorker", "当前Worker执行的线程 ${Thread.currentThread()}")
       //这个是构建请求传进来的数据,在
       val delayTime = inputData.getLong("delay", 0)
       val isStart = inputData.getBoolean("isStart", false)
       Log.d("CustomWorker","$delayTime")
       Thread.sleep(delayTime)
       //任务完成发送回调数据,在WorkRequest中体现
       val outputData = Data.Builder().putString("doWork", "任务已执行完").build()
       if (isStart) {
           return Result.success(outputData)
     }
       return Result.success()
 }

   override fun onStopped() {
       super.onStopped()
       //任务结束后会回调此方法
 }
}

这里可以看到doWork()方法是有返回值的,可以根据你的任务执行情况的来决定返回,它分为以下三种情况:

  • Result.SUCCESS:任务执行成功;

  • Result.FAILURE:任务执行失败;

  • Result.RETRY:任务重试通知。

WorkRequest构建

构建WorkRequest主要要做的是指定任务执行的自定义Worker,并且对WorkRequest设置一些配置,如给Worker传递一些数据,指定任务执行的时机和执行次数。另外,WorkRequest都对应一个唯一ID,根据这个id可以获取Worker的信息从而知道任务的状态,并可以对任务作出是否要取消的处理。

WorkRequest可以分为两类:

  • PeriodicWorkRequest:多次、定时执行的任务请求,不支持任务链;

  • OneTimeWorkRequest:只执行一次的任务请求,支持任务链。

OneTimeWorkRequest:

val inputData = Data.Builder().putBoolean("isStart", true)
 .putInt("delay", 200).build()
val request = OneTimeWorkRequest.Builder(CustomWorker::class.java)
 .setInputData(inputData)
 .build()

任务只会执行一次。

PeriodicWorkRequest:

PeriodicWorkRequest会重复执行任务,只有被取消了才会停止。任务创建被提交会立即执行或者根据Constraints设置的条件来执行,而后的执行都会根据所定的时间间隔执行。另外要注意的任务执行可能存在延时,这是因为WorkManager会根系统的电量进行优化。

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.CONNECTED) //在网络连接的情况下
   .setRequiresDeviceIdle(true) //设备待机空闲
   .setRequiresBatteryNotLow(true) //设备不处在低电量的情况
   .setRequiresStorageNotLow(true) //内存不紧张的情况下
   .setRequiresCharging(true) //充电状态
   .build()
   
//20是重复间隔时间
val request = PeriodicWorkRequest.Builder(CustomWorker::class.java, 20, TimeUnit.MINUTES)
   .addTag("CustomWorker")
   .setInputData(inputData)
   .setConstraints(constraints)
   .build()

WorkManager启动任务执行

我们要启动任务只需要使用WorkManagerWorkRequest通过enqueue(...)方法添加到它管理的请求队列里面,然后根据系统API版本和设置的约束条件选择执行的时机。一般情况下如果给队列设置ContraintsWorkManager添加任务后会立即执行。

//用WorkManager启动任务
WorkManager.getInstance(applicationContext).enqueue(request)

或者可以通过enqueueUniquePeriodicWork(...)方法防止任务重复:

WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork("CustomWorker", ExistingPeriodicWorkPolicy.REPLACE, request)

任务状态监听并接受任务执行输出的数据,这里结合自定义的CustomWorkerdoWork()来看:

//订阅获取回传的数据
WorkManager.getInstance(applicationContext)
   // requestId is the WorkRequest id
   .getWorkInfoByIdLiveData(request.id)
   .observe({ lifecycle }, {
       if (it.state.isFinished) {
           it.outputData.getString("doWork")?.let { it1 -> Log.d("CustomWorker", it1) }
     }
 })

任务取消

任务的取消通过WorkRequest的唯一id来实现,这里注意的是并非所有任务都可取消,正在执行的任务是不可取消的,执行完的任务也没有取消的意义,所以任务取消的对象主要是加入了执行任务队列但又没有开始执行的任务。

根据UUID取消特定任务

WorkManager.getInstance(applicationContext).cancelWorkById(request.id)

通过uniqueWorkName取消单独的任务

通过enqueueUniquePeriodicWork(...)方法启动的任务是有传一个uniqueWorkName的,可以根据它取消任务:

WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork("CustomWorker", ExistingPeriodicWorkPolicy.REPLACE, request)

WorkManager.getInstance(applicationContext).cancelUniqueWork("CustomWorker")

取消所有设置同一tag的任务

val request = OneTimeWorkRequest.Builder(CustomWorker::class.java)
   .setInputData(inputData)
   .addTag("CustomWorker")
   .build()

这里添加了tagCustomWorker,那取消可以是:

//所有tag为CustomWorker的都取消
WorkManager.getInstance(applicationContext).cancelAllWorkByTag("CustomWorker")

取消所有任务

WorkManager.getInstance(applicationContext).cancelAllWork()

多任务执行调度

实际开发场景中,我们应该不止只有一个任务,而且任务之间也可能存在联系,可能需要任务A执行完才执行任务B,或者是我们需要简便地一下子并发执行几个任务,这些情况WorkManager提供方法去处理。

先创建几个简单的自定义Worker

class AWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
   override fun doWork(): Result {
       Thread.sleep(200)
       Log.d("AWorker","A执行了")
       return Result.success()
 }
}

class BWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
   override fun doWork(): Result {
       Thread.sleep(200)
       Log.d("BWorker","B执行了")
       return Result.success()
 }
}

class CWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
   override fun doWork(): Result {
       Thread.sleep(200)
       Log.d("CWorker","C执行了")
       return Result.success()
 }
}

class DWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
   override fun doWork(): Result {
       Thread.sleep(200)
       Log.d("DWorker","D执行了")
       return Result.success()
 }
}

val requestA = OneTimeWorkRequest.Builder(AWorker::class.java)
 .build()

val requestB = OneTimeWorkRequest.Builder(BWorker::class.java)
 .build()

val requestC = OneTimeWorkRequest.Builder(CWorker::class.java)
 .build()

val requestD = OneTimeWorkRequest.Builder(DWorker::class.java)
 .build()

顺序执行单个任务

WorkManager.getInstance(applicationContext)
   .beginWith(requestA)
   .then(requestB)
   .then(requestC)
   .then(requestD)
   .enqueue()

WorkManager会代码设置的顺序依次执行A、B、C、D任务,如果执行过程中任意一个任务失败了,那整个执行队列的任务都会结束,并返回Result.failure()

并发执行任务

WorkManager.getInstance(applicationContext)
   .beginWith(listOf(requestA,requestB,requestC,requestD))
   .enqueue()
复制代码

等同于WorkManager通过enqueue方法一个一个添加进队列,队列中任务是并行的,相互之间的执行结果不受影响。

多个任务列先后执行

WorkManager.getInstance(applicationContext)
   .beginWith(listOf(requestA, requestB))
   .then(requestC)
   .then(listOf(requestD, requestF))
   .enqueue()

combine操作符多路径执行任务

val chain1 = WorkManager.getInstance(applicationContext)
   .beginWith(requestA)
   .then(requestB)

val chain2 = WorkManager.getInstance(applicationContext)
   .beginWith(requestC)
   .then(requestD)
WorkContinuation.combine(listOf(chain1, chain2))
   .then(requestE)
   .enqueue()

这里逻辑要理清楚,A、B任务、C、D任务是串行式的,然后两个组合任务之间是并发的,最后是要把两个组合并发的结果回归到E任务。

唯一的工作序列

调用beginUniqueWork()可以创建唯一的工作序列,被标记的Work在执行过程中只能出现一次:

//tag、工作序列的名称
//ExistingWorkPolicy.REPLACE 对有相同名称序列情况采取的策略
//需要执行的WorkRequest
WorkManager.getInstance(applicationContext).beginUniqueWork("tag",ExistingWorkPolicy.REPLACE, listOf(requestA,requestB,requestB))

同一时间只允许执行一个相同序列名的Work

ExistingWorkPolicy在出现同一个名称的工作序列时提供以下策略:

  • REPLACE:取消当前序列并将其替换为新序列;

  • KEEP:保留现有序列并忽略新的序列;

  • APPEND:添加新序列到现有序列后面,现有序列最后一个任务完成后新序列马上开始运行它的第一个任务。

总结

WorkManger组件的基本使用就介绍完了,后续有时间再探讨一下它的源码实现分析。WorkManger无疑是一个可以优化应用和提高开发的组件,它可以处理大部分的后台任务,值得我们深入学习。


首页
关于博主
我的博客
搜索