Android Jetpack架构组件之WorkManger

一、前言

最近简单看了下google推出的框架Jetpack,感受此框架的内容能够对平时的开发有很大的帮助,也能够解决不少开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。java

Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:android

上述时Android Architecture所提供的架构组件,本文将详细介绍下WorkManger的使用bash

二、WorkManger简介

WorkManger是Android Jetpack提供执行后台任务管理的组件,它适用于须要保证系统即便应用程序退出也会运行的任务,WorkManager API能够轻松指定可延迟的异步任务以及什么时候运行它们,这些API容许您建立任务并将其交给WorkManager当即运行或在适当的时间运行。网络

WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。若是WorkManager在应用程序运行时执行您的任务之一,WorkManager能够在您应用程序进程的新线程中运行您的任务。若是您的应用程序未运行,WorkManager会选择一种合适的方式来安排后台任务 - 具体取决于设备API级别和包含的依赖项,WorkManager可能会使用 JobScheduler,Firebase JobDispatcher或AlarmManager架构

三、WorkManager基础知识

3.一、使用WorkManager以前需先了解几个类:框架

  • Worker:指定须要执行的任务,能够继承抽象的Worker类,在实现的方法中编写执行的逻辑
  • WorkRequest:执行一项单一任务
  1. WorkRequest对象必须指定Work执行的任务
  2. WorkRequest都有一个自动生成的惟一ID; 您可使用ID执行取消排队任务或获取任务状态等操做
  3. WorkRequest是一个抽象的类;系统默认实现子类 OneTimeWorkRequest或PeriodicWorkRequest(循环执行)
  4. WorkRequest.Builder建立WorkRequest对象;相应的子类:OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder
  5. Constraints:指定对任务运行时间的限制(任务约束);使用Constraints.Builder建立Constraints对象 ,并传递给WorkRequest.Builder
  • WorkManager:排队和管理工做请求;将WorkRequest 对象传递WorkManager的任务队列
  1. 若是您未指定任何约束, WorkManager当即运行任务
  • WorkStatus:包含有关特定任务的信息;可使用LiveData保存 WorkStatus对象,监放任务状态;如LiveData<WorkStatus>

3.二、工做流程异步

  • 添加WorkManger的工做依赖
def work_version = "1.0.0-alpha10"
implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
androidTestImplementation "android.arch.work:work-testing:$work_version"复制代码
  • 定义Worker类,并覆盖其 doWork()方法
class TestWorker(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TestWorker", "执行了 doWork() 操做!")
        return Result.SUCCESS
    }
}复制代码
  • 使用 OneTimeWorkRequest.Builder 建立对象Worker,而后将任务排入WorkManager队列
  1. OneTimeWorkRequest.Builder建立一个单次执行的WorkQuest
val workRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
WorkManager.getInstance().enqueue(workRequest)复制代码
  • 执行结果:

  • WorkStatus
  1. 调用WorkManger的getStatusByIdLiveData()返回任务执行的状态LiveData<WorkStatus>
  2. 添加Observe()执行观察回调
WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)  // 返回LiveData
    .observe(this, Observer {
        Log.e("TestWorker", it?.state?.name)
        if (it?.state!!.isFinished) {
            Log.e("TestWorker", "Finish")
        }
    })复制代码

从上面执行过程当中看出,回调了Work的三个运行状态RUNNING、SUCCESSESD、FINISHide

  • 任务约束
  1. 使用Constraints.Builder()建立并配置Constraints对象
  2. 能够指定任务运行时间的约束,例如,能够指定该任务仅在设备空闲并链接到电源时运行
// 在链接网络、插入电源且设备处于空闲时运行
val myConstraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED) 
    .setRequiresCharging(true)
    .setRequiresDeviceIdle(true)
    .build()复制代码

除了上面设置的约束外,WorkManger还提供了如下的约束做为Work执行的条件:post

  1. setRequiredNetworkType:网络链接设置
  2. setRequiresBatteryNotLow:是否为低电量时运行 默认false
  3. setRequiresCharging:是否要插入设备(接入电源),默认false
  4. setRequiresDeviceIdle:设备是否为空闲,默认false
  5. setRequiresStorageNotLow:设备可用存储是否不低于临界阈值

配置好Constraints后,建立Work对象性能

val compressionWork = OneTimeWorkRequest.Builder(TestWorker::class.java)
        .setConstraints(myConstraints)
        .build()复制代码

执行结果:在为链接网络时,Work处于阻塞状态当链接网络后Work当即执行变为SUCCESSED状态

  • 取消任务
  1. 从WorkRequest()获取Worker的ID
  2. 根据ID取消任务
WorkManager.getInstance().cancelWorkById(workRequest.id)复制代码
  • 添加TAG
  1. 经过为WorkRequest对象分配标记字符串来对任务进行分组
  2. WorkManager.getStatusesByTag() 返回WorkStatus具备该标记的全部任务的全部任务的列表
  3. WorkManager.cancelAllWorkByTag() 取消具备特定标记的全部任务
OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
    .addTag("cleanup")
    .build()

   /./ 使用:
 WorkManager.getInstance().getStatusesByTag("TAG")
 WorkManager.getInstance().cancelAllWorkByTag("TAG")复制代码
  • 重复的任务
  1. 前面建立了单次任务使用OneTimeWorkRequest,对于重复任务使用 PeriodicWorkRequest.Builder该类建立PeriodicWorkRequest对象
  2. 而后将任务添加到WorkManager的任务队列
val timesRequest = PeriodicWorkRequest.Builder(TestWorker::class.java,10,TimeUnit.SECONDS)
   ......
    .build()复制代码

四、Workmanger的高级用法

  • 链式任务
  1. WorkManager容许您建立和排队指定多个任务的工做序列,以及它们应运行的顺序
  2. 使用该WorkManager.beginWith() 方法建立一个序列 ,并传递第一个OneTimeWorkRequest对象; 该方法返回一个WorkContinuation对象
  3. 使用 WorkContinuation.then()添加剩余的对象
  4. 最后调用WorkContinuation.enqueue()将整个序列排入队列
  5. 若是任何任务返回 Worker.Result.FAILURE,则整个序列结束
  6. Example:
WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)   
    .then(workC)
    .enqueue()复制代码

执行结果:任务会按照设置的顺序依次执行A、B、C

有一点就是WorkManger在执行过程当中,当遇到一个WOrk不成功,则会中止执行,现修改WorkB返回FAILURE状态,再次运行程序结果以下:

override fun doWork(): Result {
    Log.e("Worker", "WorkerB 执行了 doWork() 操做!")
    return Result.FAILURE
}复制代码

代码执行到WorkB就已经结束了,WorkC并未执行。

  • beginWith() 和 then()传递多个对象 : 同一方法传递的对象将会并行
WorkManager.getInstance()
    .beginWith(workA1, workA2, workA3)   // 三个对象将并行
    .then(workB)                                            // 执行完3个A后,在执行B
    .then(workC1, workC2)                          //...
    .enqueue()复制代码
  • WorkContinuation.combine()
  1. 使用 WorkContinuation.combine() 方法链接多个链来建立更复杂的序列,如:

// ......继续建立WorkD 和 WorkE 及相应的Request
val configA_B = WorkManager.getInstance().beginWith(workRequest)
     .then(workRequestB)

val configC_D = WorkManager.getInstance().beginWith(workRequestC)
     .then(workRequestD)

WorkContinuation.combine(configA_B,configC_D)
     .then(workRequestE)
     .enqueue()复制代码

执行结果与上面一致:开始时执行A/C,其他为阻塞状态,A/C执行结束后执行B/D,最后执行WoekerE
若是此时把WorkB中返回FAILURE,执行结果也一致执行到WorkerB就结束了,WorkerE不会执行

  • 特定的工做方式
  1. 经过调用 beginUniqueWork() 来建立惟一的工做序列,被标记的Work在执行过程当中只能出现一次
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequest)
//参数:一、工做序列的名称
             二、当有相同名称序列时采起的策略方式
             三、须要执行的Worker复制代码
  1. 每一个独特的工做序列都有一个名称; 同一时间只容许执行一个使用该名称工做序列
  2. ExistingWorkPolicy提供如下策略:

* ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列
* ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求
* ExistingWorkPolicy.APPEND:将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务

以ExistingWorkPolicy.REPLACE为例,让WorkA睡眠3秒,模拟同时运行的状态:

override fun doWork(): Result {
    Log.e("Worker", "WorkerA begin Sleep()!")
    Thread.sleep(3000)
    Log.e("Worker", "WorkerA 执行了 doWork() 操做!")
    return Result.SUCCESS
}复制代码

Append:两个都会执行

WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequest)
    .enqueue()
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequestB)
    .enqueue()复制代码

运行结果:

REPLACE:只会执行WorkerB

KEEP:只会执行WorkerA

  • 输入参数和返回值
  1. 将参数传递给任务并让任务返回结果。传递和返回的值是键值对
  2. 使用 Data.Builder建立 Data 对象,保存参数的键值对
  3. 在建立WorkQuest以前调用WorkRequest.Builder.setInputData()传递Data的实例
  4. 调用 Worker.getInputData()获取参数值
  5. 调用Worker.setOutputData()设置返回数据

修改WorkerA以下

const val MIN_NUMBER = "minNumber"
const val MAX_NUMBER = "maxNumber"
const val RESULT_CODE = "Result"

class WorkerA(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    private var minNumber = 0
    private var maxNumber = 0

    override fun doWork(): Result {
        minNumber = inputData.getInt(MIN_NUMBER, 0) // 使用InputData获取传入的参数
        maxNumber = inputData.getInt(MAX_NUMBER, 0)

        val result = maxNumber - minNumber  // 计算结果
        val outData: Data = Data.Builder().putAll(mapOf(RESULT_CODE to result)).build() // 建立返回的数据Data
        outputData = outData   // 设置返回的数据Data

        return Result.SUCCESS
    }
}复制代码

建立Worker并传递参数

val map = mapOf(MIN_NUMBER to 5, MAX_NUMBER to 15)   
val data = Data.Builder().putAll(map).build()   // 建立输入参数Data

val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(data)   // 传递参数
        .build()复制代码

观察任务WorkStatus获取返回结果

WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)
    .observe(this, Observer {
        if (it?.state!!.isFinished) {
            Log.e("WorkerA", "${it.outputData.getInt(RESULT_CODE, 0)}")  // 获取执行结果
        }
    })复制代码
  • 链式调用
  1. 对于链式调用的Work,使用方法和单个Work一直,只是前一个的结果会做为后一个传入的参数,现添加WorkB接收WorkA传入的差值并添加平方处理,而后返回计算结果,在Activity中添加两个Work的工做监听:
const val NUMBER = "NUMBER"
const val RESULT_B = "RESULT_B"
class WorkerB(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){

    override fun doWork(): Result {
       val input = inputData.getInt(RESULT_CODE,5) // 获取第一个Worker返回的结果
        Log.e("Worker", "WorkerB 接受数据 input = $input ")
        val map = mapOf(RESULT_B to input*input)
        outputData = Data.Builder().putAll(map).build()  // 设置返回数据
        return Result.SUCCESS
    }
}

// Activity中监听WorkerB的执行结果
WorkManager.getInstance().getStatusByIdLiveData(workRequestB.id)
    .observe(this, Observer {
        if (it?.state!!.isFinished) {
            Log.e("WorkerB", "${it.outputData.getInt(RESULT_B, 0)}")
        }
    })复制代码

执行结果:对于WorkA依次传入5和15,在WorkA中计算差值15-5 = 10,因此WorkA中输出10,那么链式调用的WorkB中接收的就是10,加平方后输出为100,输出Log与分析一致,

到此Android Jetpack组件就介绍完了,从第一篇开始到如今有一个月了,中间断断续续总算分析完了,经过对组件的学习发现Android Jetpack组件的推出不只加速了咱们开发的速度,并且避免了程序运行中的一些生命周期、内存问题以及一些性能问题,但愿能够经过这几篇文章对想学习组件的同窗有所帮助,后续将会使用全部的组件编写一个客户端,将全部组件综合使用。

相关文章
相关标签/搜索