前言
最近在学习 Google 推出的框架Jetpack,虽然目前网上的资料已经很多了,但为了加深印象和边动手练习跟着学习,所以站在了下面的巨人的肩膀上,并根据当前最新的API 和编写实际Demo,记录下一些学习笔记,大部分是参考巨人们的,整理和休整,加入自己理解和更新吧,学习领略了Android Jetpack组件的一点魅力
目前学习笔记系列为:
- Android Jetpack 之 Lifecycles ---入门使用
- Android Jetpack 之 LiveData---入门使用
- Android Jetpack 之 Room ---入门使用
- Android Jetpack 之 WorkManger---入门使用
- ....待续
日常鸣谢巨人
正题
WorkManger 初识
WorkManger是Android Jetpack提供执行后台任务管理的组件
适用于需要保证系统即使应用程序退出也会运行的任务
WorkManager API可以轻松指定可延迟的异步任务以及何时运行它们,这些API允许您创建任务并将其交给WorkManager立即运行或在适当的时间运行
WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。如果WorkManager在应用程序运行时执行您的任务之一,WorkManager可以在您应用程序进程的新线程中运行您的任务
如果您的应用程序未运行,WorkManager会选择一种合适的方式来安排后台任务 - 具体取决于设备API级别和包含的依赖项,WorkManager可能会使用 JobScheduler,Firebase JobDispatcher或AlarmManager
WorkManager 相关
- Worker
指定需要执行的任务,可以继承抽象的Worker类,在实现的 doWork 方法中编写执行的逻辑
- WorkRequest
执行一项单一任务
- WorkRequest对象必须指定Work执行的任务
- WorkRequest都有一个自动生成的唯一ID; 您可以使用ID执行取消排队任务或获取任务状态等操作
- WorkRequest是一个抽象的类;系统默认实现子类 OneTimeWorkRequest或PeriodicWorkRequest(循环执行)
- WorkRequest.Builder创建WorkRequest对象;相应的子类:OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder
- Constraints:指定对任务运行时间的限制(任务约束);使用Constraints.Builder创建Constraints对象 ,并传递给WorkRequest.Builder
- WorkManager
排队和管理工作请求、将WorkRequest 对象传递WorkManager的任务队列
如果未指定任何约束, WorkManager立即运行任务
- WorkStatus
包含有关特定任务的信息;可以使用LiveData保存 WorkStatus对象,监听任务状态;如LiveData<WorkStatus>
WorkManager 入门使用
- 添加依赖
def work_version = "1.0.0-alpha11"
implementation "android.arch.work:work-runtime:$work_version"
// optional - Firebase JobDispatcher support
implementation "android.arch.work:work-firebase:$work_version"
// optional - Test helpers
androidTestImplementation "android.arch.work:work-testing:$work_version"
但这里注意,如果项目做搭配了其他 jetpack 组件的依赖使用
可能会在运行时,报错
Program type already present: com.google.common.util.concurrent.ListenableFuture
是因为重复依赖包冲突了,在导入上述依赖时,可忽略掉上面的 ListenableFuture
def work_version = "1.0.0-alpha11"
implementation("android.arch.work:work-runtime:$work_version"){
exclude group:'com.google.guava', module:'listenablefuture'
}
// optional - Firebase JobDispatcher support
implementation ("android.arch.work:work-firebase:$work_version"){
exclude group:'com.google.guava', module:'listenablefuture'
}
// optional - Test helpers
androidTestImplementation "android.arch.work:work-testing:$work_version"
- 定义Worker类,并重写其 doWork()方法
class MyWorkA(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {
override fun doWork(): Result {
Log.i(TAG,"doWork A !")
Thread.sleep(1000)
// 模拟任务执行成功
return Result.SUCCESS
}
}
- 使用 OneTimeWorkRequest.Builder 创建对象Worker,然后将任务排入WorkManager队列
val workRequestA = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
WorkManager.getInstance().enqueue(workRequestA)
- 执行结果
I MyWorkA : doWork A !
- WorkStatus
- 调用WorkManger的getStatusByIdLiveData()返回任务执行的状态LiveData<WorkStatus>
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA.id)
.observe(this, Observer {
Log.i(TAG, it?.state?.name)
if (it?.state!!.isFinished) {
Log.i(TAG, "Finish !")
}
})
- 添加Observe()执行观察回调
I MyWorkActivity: ENQUEUED
I MyWorkA: doWork A !
I MyWorkActivity: RUNNING
I MyWorkActivity: SUCCEEDED
I MyWorkActivity: Finish !
可以看出,回调了Work的三个运行状态ENQUEUED 、RUNNING、SUCCESSESD
但实际上,源码中定义的状态如下
/**
* The current state of a unit of work.
*/
public enum State {
/**
* The state for work that is enqueued (hasn't completed and isn't running)
*/
ENQUEUED,
/**
* The state for work that is currently being executed
*/
RUNNING,
/**
* The state for work that has completed successfully
*/
SUCCEEDED,
/**
* The state for work that has completed in a failure state
*/
FAILED,
/**
* The state for work that is currently blocked because its prerequisites haven't finished
* successfully
*/
BLOCKED,
/**
* The state for work that has been cancelled and will not execute
*/
CANCELLED;
/**
* Returns {@code true} if this State is considered finished.
*
* @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and
* {@link #CANCELLED} states
*/
public boolean isFinished() {
return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
}
}
清晰一目了然...
- 任务约束
- 使用Constraints.Builder()创建并配置Constraints对象
val myConstrains = Constraints.Builder()
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.build()
- 可以指定任务运行时间的约束,例如,可以指定该任务仅在设备空闲并连接到电源时运行,具体源码中目前支持的条件为
- setRequiredNetworkType:网络连接设置
- setRequiresBatteryNotLow:是否为低电量时运行 默认false
- setRequiresCharging:是否要插入设备(接入电源),默认false
- setRequiresDeviceIdle:设备是否为空闲,默认false
- setRequiresStorageNotLow:设备可用存储是否不低于临界阈值
- 配置好Constraints后,利用它创建Work对象,并安排进队列
val compressionWork = OneTimeWorkRequest.Builder(MyWorkA::class.java)
.setConstraints(myConstrains)
.build()
WorkManager.getInstance().enqueue(compressionWork)
- 取消任务
根据ID取消任务,从WorkRequest()获取Worker的ID
WorkManager.getInstance().cancelWorkById(compressionWork.id)
- 添加TAG
- 通过为WorkRequest对象分配标记字符串来对任务进行分组
val workRequestWithTag = OneTimeWorkRequest.Builder(MyWorkA::class.java)
.addTag("MyTAG")
.build()
- WorkManager.getWorkInfosByTag() 返回WorkStatus具有该标记的所有任务的所有任务的列表
val list = WorkManager.getInstance().getWorkInfosByTag("MyTAG")
- WorkManager.cancelAllWorkByTag() 取消具有特定标记的所有任务
WorkManager.getInstance().cancelAllWorkByTag("MyTAG")
- 重复的任务
创建单次任务使用 OneTimeWorkRequest
创建重复任务使用 PeriodicWorkRequest.Builder创建PeriodicWorkRequest对象,然后将任务添加到WorkManager的任务队列
val repeatRequest = PeriodicWorkRequest.Builder(MyWorkA::class.java,10,TimeUnit.SECONDS).build()
然后其他的设置条件,Tag 都喝单次任务一致
Workmanger的高级用法
- 链式任务
- WorkManager允许您创建和排队指定多个任务的工作序列,以及它们应运行的顺序
- 使用该WorkManager.beginWith() 方法创建一个序列 ,并传递第一个OneTimeWorkRequest对象; 该方法返回一个WorkContinuation对象
- 使用 WorkContinuation.then()添加剩余的对象
- 最后调用WorkContinuation.enqueue()将整个序列排入队列
- 如果任何任务返回 Worker.Result.FAILURE,则整个序列结束
val workRequestA = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
val workRequestB = OneTimeWorkRequest.Builder(MyWorkB::class.java).build()
val workRequestC = OneTimeWorkRequest.Builder(MyWorkC::class.java).build()
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA.id)
.observe(this, Observer {
Log.i("MyWorkA", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkA", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB.id)
.observe(this, Observer {
Log.i("MyWorkB", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkB", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestC.id)
.observe(this, Observer {
Log.i("MyWorkC", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkC", "Finish !")
}
})
WorkManager.getInstance()
.beginWith(workRequestA)
.then(workRequestB)
.then(workRequestC)
.enqueue()
执行结果 任务会按照设置的顺序依次执行A、B、C
MyWorkA: doWork A !
MyWorkA: ENQUEUED
MyWorkB: BLOCKED
MyWorkC: BLOCKED
MyWorkA: RUNNING
MyWorkA: SUCCEEDED
MyWorkA: Finish !
MyWorkB: ENQUEUED
MyWorkB: doWork B !
MyWorkB: RUNNING
MyWorkB: SUCCEEDED
MyWorkB: Finish !
MyWorkC: doWork C !
MyWorkC: ENQUEUED
MyWorkC: RUNNING
MyWorkC: SUCCEEDED
MyWorkC: Finish !
一个任务执行时,从Log 已经可以清晰看到其他顺序任务的状态...
WorkManger在执行过程中,当遇到一个WOrk不成功,则会停止执行
修改WorkB返回FAILURE状态
override fun doWork(): Result {
Log.i(TAG,"doWork B !")
Thread.sleep(1000)
// 模拟任务执行失败
return Result.FAILURE
}
执行结果
MyWorkB: BLOCKED
MyWorkC: BLOCKED
MyWorkA: doWork A !
MyWorkA: ENQUEUED
MyWorkA: RUNNING
MyWorkA: SUCCEEDED
MyWorkA: Finish !
MyWorkB: ENQUEUED
MyWorkB: ENQUEUED
MyWorkB: doWork B !
MyWorkB: RUNNING
MyWorkB: FAILED
MyWorkB: Finish !
MyWorkC: FAILED
MyWorkC: Finish !
代码执行到WorkB就已经结束了,WorkC并未执行
但,细心点可以发现
虽然 workC 未执行,也就是说 C 的 doWork 方法未被执行,但是这里最后判断 workC 的 isFinished ,也是通过的,所以才会出现 MyWorkC: Finish ! 打印 . 所以要注意 isFinished 方法只是判断这个work 是否已经结束了,并不关心执行的结果
- beginWith() 和 then()传递请求数组 List<OneTimeWorkRequest>, 同一方法传递的对象将会并行执行
val requestList = arrayListOf<OneTimeWorkRequest>(workRequestA, workRequestB)
WorkManager.getInstance()
.beginWith(requestList)
.then(workRequestC)
.enqueue()
直观点,直接看Log ...
MyWorkA: ENQUEUED
MyWorkB: ENQUEUED
MyWorkA: doWork A !
MyWorkC: BLOCKED
MyWorkA: RUNNING
MyWorkC: BLOCKED
MyWorkB: doWork B !
MyWorkB: RUNNING
MyWorkA: SUCCEEDED
MyWorkA: Finish !
MyWorkC: doWork C !
MyWorkB: SUCCEEDED
MyWorkB: Finish !
MyWorkC: ENQUEUED
MyWorkC: RUNNING
MyWorkC: SUCCEEDED
MyWorkC: Finish !
- WorkContinuation.combine
- 使用 WorkContinuation.combine() 方法连接多个链来创建更复杂的序列
graph TB
A1-->B1
A2-->B2
B1-->C
B2-->C
预测执行流程应该是
开始
执行A1/A2、其余为阻塞状态
A1/A2执行结束后执行B1/B2
最后执行C
编写代码
val workRequestA1 = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
val workRequestA2 = OneTimeWorkRequest.Builder(MyWorkA2::class.java).build()
val workRequestB1 = OneTimeWorkRequest.Builder(MyWorkB::class.java).build()
val workRequestB2 = OneTimeWorkRequest.Builder(MyWorkB2::class.java).build()
val workRequestC = OneTimeWorkRequest.Builder(MyWorkC::class.java).build()
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA1.id)
.observe(this, Observer {
Log.i("MyWorkA1", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkA1", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA2.id)
.observe(this, Observer {
Log.i("MyWorkA2", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkA2", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB1.id)
.observe(this, Observer {
Log.i("MyWorkB1", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkB1", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB2.id)
.observe(this, Observer {
Log.i("MyWorkB2", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkB2", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestC.id)
.observe(this, Observer {
Log.i("MyWorkC", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkC", "Finish !")
}
})
val requestA1_B1 = WorkManager.getInstance().beginWith(workRequestA1)
.then(workRequestB1)
val requestA2_B2 = WorkManager.getInstance().beginWith(workRequestA2)
.then(workRequestB2)
WorkContinuation.combine(requestA1_B1, requestA2_B2)
.then(workRequestC)
.enqueue()
实际运行结果,一目了然...
MyWorkB2: BLOCKED
MyWorkC: BLOCKED
MyWorkA1: doWork A1 !
MyWorkA1: ENQUEUED
MyWorkA2: doWork A2 !
MyWorkA2: ENQUEUED
MyWorkB1: BLOCKED
MyWorkA1: RUNNING
MyWorkA2: RUNNING
MyWorkA1: SUCCEEDED
MyWorkA1: Finish !
MyWorkB1: ENQUEUED
MyWorkA1: SUCCEEDED
MyWorkA1: Finish !
MyWorkA2: SUCCEEDED
MyWorkA2: Finish !
MyWorkB1: ENQUEUED
MyWorkB2: ENQUEUED
MyWorkB1: doWork B1 !
MyWorkB2: doWork B2 !
MyWorkB1: RUNNING
MyWorkB2: RUNNING
MyWorkB1: SUCCEEDED
MyWorkB1: Finish !
MyWorkB1: SUCCEEDED
MyWorkB1: Finish !
MyWorkB2: SUCCEEDED
MyWorkB2: Finish !
MyWorkC: ENQUEUED
MyWorkC: doWork C !
MyWorkC: RUNNING
MyWorkC: SUCCEEDED
MyWorkC: Finish !
同上,中途任意一环的 work 执行失败,返回FAILURE,后续的emm.....
- 特定的工作方式
- 通过调用 beginUniqueWork() 来创建唯一的工作序列,被标记的Work在执行过程中只能出现一次
WorkManager.getInstance().beginUniqueWork("MyUniqueWork",
ExistingWorkPolicy.APPEND, workRequestA1).enqueue()
- 每个独特的工作序列都有一个名称
- 同一时间只允许执行一个使用该名称工作序列
- beginUniqueWork 的参数二 ExistingWorkPolicy 的策略有:
/**
* An enum that determines what to do with existing {@link OneTimeWorkRequest}s with the same unique
* name in case of a collision.
*/
public enum ExistingWorkPolicy {
/**
* If there is existing pending work with the same unique name, cancel and delete it. Then,
* insert the newly-specified work.
*/
REPLACE,
/**
* If there is existing pending work with the same unique name, do nothing. Otherwise, insert
* the newly-specified work.
*/
KEEP,
/**
* If there is existing pending work with the same unique name, append the newly-specified work
* as a child of all the leaves of that work sequence. Otherwise, insert the newly-specified
* work as the start of a new sequence.
*/
APPEND
}
注释写的很详细了
ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列
ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求
ExistingWorkPolicy.APPEND:将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务
- 输入参数和返回值
- 将参数传递给任务并让任务返回结果。传递和返回的值是键值对
- 使用 Data.Builder创建 Data 对象,保存参数的键值对
- 在创建WorkQuest之前调用WorkRequest.Builder.setInputData()传递Data的实例
- 调用 Worker.getInputData()获取参数值
- 调用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 {
// 使用InputData获取传入的参数
minNumber = inputData.getInt(MIN_NUMBER, 0)
maxNumber = inputData.getInt(MAX_NUMBER, 0)
val result = maxNumber - minNumber
// 创建返回的数据Data
val outData: Data = Data.Builder().putAll(mapOf(RESULT_CODE to result)).build()
// 设置返回的数据Data
outputData = outData
return Result.SUCCESS
}
}
创建Worker并传递参数
val map = mapOf(MIN_NUMBER to 5, MAX_NUMBER to 15)
// 创建输入参数Data
val data = Data.Builder().putAll(map).build()
// 传递参数
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)}")
}
})