WorkManager 最全攻略
1. 介绍
关于workmanager 的介绍 官网上是这么说的: 使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。 重点是后面的几个字,运行可延迟的异步任务, 在退出或者重启的时候,我们在之前要实现这种任务可能需要 BroadcastReceiver 或者 AlarmManager,现在的话可以使用WorkManager,这个框架最高兼容至Android api 14
- Android 23以上是采用JobScheduler
- Android 14-22 采用的是BroadcastReceiver 和 AlarmManager
2. 优点
- 向下兼容至api 14
- 可以添加任务执行的约束条件,比如说 延迟执行,是否在低电量模式下执行,是否在充电模式下执行,是否在设备空闲时执行等等
- 调度一次性或周期性异步任务
- 监管任务,可以随时取消任务
- 将任务链接起来,比如说执行可以指定多个任务的执行顺序
以上传图片为例:我们分解一下任务:1.图片添加滤镜 -> 图片压缩 -> 图片上传,这个是有执行顺序的,采用workmanager 我们可以很方便的实现
- 确保任务执行,即使应用或设备重启也同样执行任务
3. 使用
3.1 为项目添加依赖
//根据项目需要自行添加依赖,不需要全部添加
dependencies {
def work_version = "2.3.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"//java 语言选这个
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"//kotlin 选这个
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"//配合rxjava2 使用
}
3.2 创建一个后台任务
代理示例如下(以上传图片为例):
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
override fun doWork(): Result {
uploadPic()//具体上传图片的逻辑
return Result.success()
}
}
3.3 创建一个workrequest
//此处的 UploadPicWork 就是之前创建的任务
val uploadPicWork = OneTimeWorkRequestBuilder<UploadPicWork>()
.setConstraints(triggerContentMaxDelay).build()
3.4 执行任务
//此处的 uploadPicWork 就是前一步创建的 workrequest
WorkManager.getInstance(myContext).enqueue(uploadPicWork)
到这里一个简单的任务就可以执行了,但往往我们在开发的过程中,可能满足不了我们的需求,再继续往下看!
4. 创建一个"复杂的任务"
4.1 创建任务执行的约束条件
//注意 以下条件都是 && 的关系
val triggerContentMaxDelay =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)//网络链接的时候使用,避免各种网络判断,省时省力
.setRequiresDeviceIdle(false)//是否在设备空闲的时候执行
.setRequiresBatteryNotLow(true)//是否在低电量的时候执行
.setRequiresStorageNotLow(true)//是否在内存不足的时候执行
.setRequiresCharging(true)//是否时充电的时候执行
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)//延迟执行
.build()
//目前就提供这几种约束条件,大家可以在使用的过程中酌情添加
4.2 为任务添加约束条件
val uploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setConstraints(triggerContentMaxDelay)//约束条件
.build()
WorkManager.getInstance(myContext).enqueue(uploadPicWork)//执行
4.3 为worker 传递参数
这个可能比较重要,我们一般在执行任务的时候需要把参数传递到Worker中,可以采用构造或者 写一个set方法给传递进去,但是这种方式在这可能不适用
//可以采用这种方式传递参数
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
//此处set input data 需要的参数 是一个Data对象,注意只可以添加一次,如果有多个参数需要传递,可以封装成一个data 数据类
.setInputData(workDataOf("params_tag" to "params"))
.setConstraints(triggerContentMaxDelay).build()
...
/**
* Converts a list of pairs to a [Data] object.
*
* If multiple pairs have the same key, the resulting map will contain the value
* from the last of those pairs.
*
* Entries of the map are iterated in the order they were specified.
*/
inline fun workDataOf(vararg pairs: Pair<String, Any?>): Data {
val dataBuilder = Data.Builder()
for (pair in pairs) {
dataBuilder.put(pair.first, pair.second)
}
return dataBuilder.build()
}
可以看看这个 workDataOf 函数,就是将一个 Pair对象转成一个Data对象,将
pair.first 作为 key ,pair.second 作为 value 构建了一个Data 对象. 有的同学可能对 "params_tag" to "params" 这种写法比较差异,解释一下,这是构造了一个 Pair 对象,
不了解 Pair 这个类的同学,可以看下Pair,参数的类可以是基本数据类型,也可以引用数据类型
4.4 获取参数
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
override fun doWork(): Result {
val params = inputData.getString("params_tag")//获取传递的参数
uploadPic()//上传图片
return Result.success()
}
}
inputData 是 Worker 类 父类的一个函数,在Java 中 可以 类似 this.getInputData() 返回的是一个 Data 对象,就可以获取传递的参数了.
5. Worker 的状态
在之前的创健的过程中,在 doWork 函数中,我们返回的 Result.success(); 我们默认 ,任务 uploadPic 函数顺利的执行完成了,所以返回了 success 状态,但是在实际开发过程中 可以能因为各种各样的问题会导致 失败,这时候就不能返回success了,类似这样:
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
override fun doWork(): Result {
val params = inputData.getString("params_tag")//获取传递的参数
try {
uploadPic()//上传图片
} catch (e: Exception) {
return Result.failure(Data())//执行失败了
}
return Result.success()
}
}
Result.failure(Data()),这个函数可以什么都不传,但是如果关注失败的原因的话,可以把封装一个Data对象,传递出去!!,至于怎么观察任务的执行结果,以及拿到执行失败传递的参数,后面会讲!
5.1 Worker 的各种状态说明
在Worker 生命周期内,会经历不同的 State
- 如果有尚未完成的前提性工作,则工作处于 BLOCKED State。
- 如果工作能够在满足 约束条件 和时机条件后立即运行,则被视为处于 ENQUEUED 状态。
- 当 Worker 在活跃地执行时,其处于 RUNNING State。
- 如果 Worker 返回 Result.success(),则被视为处于 SUCCEEDED 状态。这是一种终止 State;只有 OneTimeWorkRequest 可以进入这种 State。
- 如果 Worker 返回 Result.failure(),则被视为处于 FAILED 状态。这也是一个终止 State;只有 OneTimeWorkRequest 可以进入这种 State。所有依赖工作也会被标记为 FAILED,并且不会运行。
- 当取消尚未终止的 WorkRequest 时,它会进入 CANCELLED State。所有依赖工作也会被标记为 CANCELLED,并且不会运行。
6. 观察Worker 的状态(需要搭配 LiveData组件使用)
将工作加入队列后,可以通过 WorkManager 检查其状态。相关信息在 WorkInfo 对象中获取,包括 Worker 的 id、tag、当前 State 和返回数据。
6.1 获取 WorkInfo
- 听过 id 获取,可以听过 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 来通过 WorkRequest id 来获取 WorkInfo。
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(UploadPicWork.id)// 通过id 获取
.observe(this, Observer { //it:WorkInfo
it?.apply {
when (this.state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this.state}")
}
}
})
- 通过 tag 获取,可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 来通过 WorkRequest 的 WorkInfo 对象。
//要通过 tag 获取,则需要先设置 tag
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setInputData(workDataOf("params_tag" to "params"))//传递参数
.setConstraints(triggerContentMaxDelay)//设置约束条件
.addTag("tag")//设置tag
.build()
//获取 workInfo
WorkManager.getInstance(this)
.getWorkInfosByTagLiveData("tag")
.observe(this, Observer {it:List<WorkInfo>//此处返回的是一个集合,作为示例代码,默认只取 0 index
it?.apply {
when (this[0].state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this[0]}")
}
}
})
- 对于 唯一工作名称 的一个 Worker ,可以利用 WorkManager.getWorkInfosForUniqueWork(String) 或 WorkManager.getWorkInfosForUniqueWorkLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象。此处估计不太好理解,唯一工作是一个概念性非常强的术语,可确保一次只有一个具有特定名称的工作链。与 id 不同的是,唯一名称是人类可读的,由开发者指定,而不是由 WorkManager 自动生成。与标记不同,唯一名称仅与“一个”工作链关联。您可以通过调用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 或 WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) 创建唯一工作序列。
可以参考官网的说法 唯一工作名称
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData("UploadPicWork")//唯一工作名称
.observe(this, Observer {it:List<WorkInfo> //此处返回的是一个集合,作为示例代码,默认只取 0
it?.apply {
when (this[0].state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this[0]}")
}
}
})
注意如采用这种方式获取 workinfo ,在执行 worker 的时候与之前不一样,需要采用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 或 WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) 来执行
//全部代码如下
//创建约束条件
val triggerContentMaxDelay =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
.build()
// 创建workrequest
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setInputData(workDataOf("params_tag" to "params"))
.setConstraints(triggerContentMaxDelay)
.build()
//注意!!!,此处区别与之前的 WorkManager.getInstance(this).enqueue(UploadPicWork)
// "UploadPicWork" 需要与下面代码 getWorkInfosForUniqueWorkLiveData("UploadPicWork") 中字符串对应
// ExistingWorkPolicy.APPEND 一个枚举值,worker 执行的策略,想要了解的同学,可以看下面的链接
WorkManager.getInstance(this).enqueueUniqueWork("UploadPicWork",ExistingWorkPolicy.APPEND,UploadPicWork)
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData("UploadPicWork")
.observe(this, Observer {
it?.apply {
when (this[0].state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this[0]}")
}
}
7. 多个Worker 的顺序执行
在我们实际开发中可能遇到如下场景.一个任务可能依赖与其他的任务,并且每个任务之间有先后顺序,以前面简介中,图片上传为例:上传图片之前 需要 先压缩,压缩之前需要先剪裁, 流程如下 滤镜--> 压缩 --> 上传图片,对于这部分内容,官网上介绍的已经足够清清楚了! 链接工作
- 您可以使用 WorkManager 创建工作链并为其排队。工作链用于指定多个关联任务并定义这些任务的运行顺序。当您需要以特定的顺序运行多个任务时,这尤其有用。
要创建工作链,您可以使用 WorkManager.beginWith(OneTimeWorkRequest) 或 WorkManager.beginWith(List<OneTimeWorkRequest>),这会返回 WorkContinuation 实例。
然后,可以通过 WorkContinuation 使用 WorkContinuation.then(OneTimeWorkRequest) 或 WorkContinuation.then(List<OneTimeWorkRequest>) 来添加从属 OneTimeWorkRequest。
每次调用 WorkContinuation.then(...) 都会返回一个新的 WorkContinuation 实例。如果添加了 OneTimeWorkRequest 的 List,这些请求可能会并行运行。
最后,您可以使用 WorkContinuation.enqueue() 方法为 WorkContinuation 链排队。
让我们看一个示例:某个应用对3个不同的图像执行图像滤镜(可能会并行执行),然后将这些图像压缩在一起,再上传它们。
WorkManager.getInstance(myContext)
// Candidates to run in parallel
//滤镜1 滤镜2 滤镜3 ...
.beginWith(listOf(filter1, filter2, filter3))
// Dependent work (only runs after all previous work in chain)
//压缩
.then(compress)
//上传
.then(upload)
// Don't forget to enqueue()
.enqueue()//执行
8. 执行重复任务
很好理解,就是在给定的时间间隔内定期执行任务,比如说 每个一个小时,上报位置信息,每个3个小时备份一个日志等等... 代码示例如下:
val triggerContentMaxDelay =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
.build()
// val UploadPicWork =
// OneTimeWorkRequestBuilder<UploadPicWork>()
// .setInputData(workDataOf("params_tag" to "params"))
// .setConstraints(triggerContentMaxDelay)
// .addTag("tag")
// .build()
//
val build = PeriodicWorkRequestBuilder<UploadPicWork>(
1000 * 60 *15,
TimeUnit.MICROSECONDS
).setConstraints(triggerContentMaxDelay).build()
WorkManager.getInstance(this).enqueue(build)
注意!!!这个时间间隔不可低于15分钟
9.任务执行失败重试
这个场景在实际开发中也经常遇到,比如在任务执行的过程中可能由于一些别的原因导致任务执行失败,但是我们希望过一段时间可以重试 代码示例如下:
//约束条件
val triggerContentMaxDelay =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
.build()
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10 * 1000, TimeUnit.MICROSECONDS)//10s 后失败重试
.setInputData(workDataOf("params_tag" to "params"))
.setConstraints(triggerContentMaxDelay)
.addTag("tag")
.build()
WorkManager.getInstance(this).enqueue(UploadPicWork)
...
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
private var count: Int = 1
override fun doWork(): Result {
uploadPic()
return Result.retry()//失败重试状态
}
private fun uploadPic() {
SystemClock.sleep(1000 * 3)//模拟图片上传
}
}