WorkManager属于Android Jetpack的一部分,通过WorkManager API可以轻松第调度可延迟的任务,即使是那些在应用退出或者设备重启时仍需要运行的任务。
WorkManager的主要功能
WorkManager的主要用途
WorkManager主要用于可延迟执行的任务,并且在应用退出或者设备重启时必须能可靠运行的任务。例如:
WorkManager的局限性
WorkManager 不适用于应用进程结束时能够安全终止的运行中后台任务,也不适用于需要立即执行的任务。因此,也不要滥用WorkManager。
本文测试Demo源码下载
WorkManager 的使用主要有以下几点操作:
WorkManager 是属于 Android Jetpack 的一部分,使用需要在应用模块下的build.gradle
引入相关的依赖。
dependencies {
def work_version = "2.3.4"
// (仅Java使用,如果你的项目是基于Java开发的,引入这个)
implementation "androidx.work:work-runtime:$work_version"
// (Kotlin + coroutines,如果你的项目基于Kotlin开发,引入这个)
implementation "androidx.work:work-runtime-ktx:$work_version"
// 可选 - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// 可选 - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// 可选 - 测试支持
androidTestImplementation "androidx.work:work-testing:$work_version"
}
说明:关于最新的WorkManager依赖,请参考: WorkManager 库版本历史
任务使用 Worker 类定义,类内部的doWork() 方法在 WorkManager 提供的后台线程上同步运行。要创建后台任务,扩展 Worker 类并重写 doWork() 方法即可。
示例:
class CheckSystemWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "Checking system。。。。。。。。")
Thread.sleep(3000)
Log.e("TEST", "Checking system done.")
return Result.success()
}
}
使用 Worker
定义任务之后,使用 WorkRequest 定义任务的运行方式和时间。任务可以是一次性的,也可以是周期性的。对于一次性 WorkRequest
,请使用 OneTimeWorkRequest,对于周期性工作,请使用 PeriodicWorkRequest。
示例:
val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>().build()
定义 WorkRequest
之后,现在可以通过 WorkManager 的 enqueue() 方法来调度它,将任务提交给系统进行管理和运行。
示例:
WorkManager.getInstance(applicationContext).enqueue(checkSystem)
前面介绍了如何创建简单的任务请求(WorkRequest)并将其放到系统队列中。在这个章节中,将会详细介绍任务和任务请求的各种设定。
可以通过向任务请求中添加 Constraints 指明任务运行的条件。Constraints
通过 Constraints.Builder 创建,里面定义了设定不同约束的成员函数。例如电池非低电量、需要网络等等。
示例:
val constraints = Constraints.Builder()
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED) // 需要联网
.build()
val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)
上面的示例中,为任务添加了需要网络连接的约束,在联网状态下,任务放入队列之后将会安排执行,但是如果当前设备断开了网络,并不会立刻执行,而是在队列中等到网络恢复,网络恢复后,等待的任务会继续执行。
如果为一个任务设定了多个约束,当所有约束都满足时任务才会执行。如果运行期间任务的约束不满足,将会停止任务的执行,等到约束满足时,系统将会尝试恢复执行任务。
如果任务没有设定约束或者所有约束都满足,任务有可能会立即执行。如果不想任务立即执行,可以设定一个最短的延迟时间。需要设定最短初始延迟时间,需要向任务请求中调用setInitialDelay()
接口设定。
说明:为什么说最短延迟时间呢?因为将任务提交给系统,本来就有可能出现等待延迟,同理,设定了初始延迟时间的任务即使到了延迟时间,任务也有可能需要等待资源。换句话说,任务开始执行的时间 >= 设定的初始延迟时间
val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
.setInitialDelay(3000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)
上面的例子可以发现,当任务请求提交到系统时,并不会立即执行,而是等待一段时间才会开始执行。
如果任务执行失败,需要让 WorkManager
重新尝试执行任务,可以从工作器(Worker)中返回 Result.retry(),然后,系统会根据退避延迟时间和政策重现调整调度任务。
设置任务的退避政策通过调用 WorkerRequest.Builder
的 setBackoffCriteria()) 接口实现,设置的值包括退避延迟时间的增长方式、重试任务最短等待时间、重试等待时间的单位。退避延迟时间的增长方式在 BackoffPolicy 中定义,默认是 BackoffPolicy.EXPONENTIAL (指数增长)。
提示:退避延迟时间增长方式有两种
- 线性增长(BackoffPolicy.LINEAR):线性函数增长,即 f ( n ) = t n f(n)=tn f(n)=tn,其中 t 为重试任务最短时间, n 为重试次数( n ≥ 1 n\geq1 n≥1)。
- 指数正在(BackoffPolicy.EXPONENTIAL ):2的指数倍增长,即 f ( n ) = t ∗ 2 n f(n)=t*2^n f(n)=t∗2n,其中 t 为重试任务最短时间, n 为重试次数( n ≥ 1 n\geq1 n≥1)。
// 定义Worker
var times: Int = 0
var lastTime = 0L
class CheckDiskWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "Checking disk。。。。。。。。")
if(times > 0) {
Log.w("TEST", "Retry ${times}, Delay is ${(System.currentTimeMillis() - lastTime) / 1000} s")
}
Thread.sleep(3000)
lastTime = System.currentTimeMillis()
if(times < 5) {
Log.e("TEST", "Checking disk failure.")
times++
// 需要重试,比如操作失败了,返回Result.retry()
return Result.retry()
}
Log.e("TEST", "Checking disk done.")
times = 0
// 返回成功时,将不会再重试
return Result.success()
}
}
// 设定任务请求的重试和回避策略
val checkDisk = OneTimeWorkRequestBuilder<CheckDiskWorker>()
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(applicationContext).enqueue(checkDisk)
注意事项
1.OneTimeWorkRequest
重试的最小时间间隔必须是OneTimeWorkRequest.MIN_BACKOFF_MILLIS
和OneTimeWorkRequest.MAX_BACKOFF_MILLIS
,超出这个范围的将会取临界值;
2. 因为任务提交给系统到真是执行并不是实时的,所以,真实的延长时间不一定是预期的(有可能会出现后一次重试时间间隔比前一次的还要短),但是一定是大于等于预期值。
将任务提交给系统,但是需要和任务进行交互,就需要实现任务的输入/输出。将需要的数据以参数的形式传入到任务(输入),任务执行完毕后返回结果(输出)。传入参数通过 WorkerReuqest
的 setInputData() 接口传入。输入和输出值以键值对的形式存储在 Data 对象中。在 Worker
中通过getInputData()
接口获取输入参数。类似地,从 Worker 中返回数据(输出)也是通过 Data
对象,并通过Result
对应带有返回数据参数的方法返回结果,例如:Result.success(Data)
、Result.failure(Data)
说明:
Data
对象可以通过原始的Data.Builder
进行构建,也可以使用worker
库提供的内联函数workDataOf(vararg pairs: Pair
进行构建,原理一样)
示例:
// 定义Worker
class CheckNetworkWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "Checking network。。。。。。。。")
Log.d("TEST", "Checking network, params......")
for((key, value) in inputData.keyValueMap) {
Log.d("TEST", "$key ---- $value")
}
Thread.sleep(3000)
Log.e("TEST", "Checking network done.")
return Result.success(Data.Builder().let {
it.putInt("code", 200)
it.putString("msg", "Network is fine")
it.build()
})
}
}
// 构建WorkerRequest,并提交系统
val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
.setInputData(Data.Builder().let {
// Data以Builder的方式进行构建,传入的是键值对
it.putString("operator", "Owen")
it.putString("description", "Check network state")
it.build()
})
.build()
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)
注意:按照设计要求,
Data
对象应该很小,大小上限为 10KB,值可以是字符串、基元类型或数组变体。如果需要将更多数据传入和传出工作器,应该将数据放在其他位置,例如 存储或者数据库。
向任务输入数据已经知道如何实现了,可是在程序中如何获取任务输出的数据呢?主要有以下几步:
WorkManager
的 getWorkInfoByXXXLiveData()
获取 WorkRequest
的 LiveData
对象LiveData
对象添加观察者onChanged()
方法中获取返回的数据WorkRequest
提交到系统获取
LiveData
对象的接口有多个,详细请参考:WorkManager,使用合适的方法获取,在后续章节也会详细介绍。
那么,以上的例子可以改为:
val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
.setInputData(Data.Builder().let {
// Data以Builder的方式进行构建,传入的是键值对
it.putString("operator", "Owen")
it.putString("description", "Check network state")
it.build()
})
.build()
WorkManager.getInstance(applicationContext)
.getWorkInfoByIdLiveData(checkNetwork.id)
// observe方法是添加观察者,这个方法有两个参数,第一个参数是LifecycleOwner,可以传入
.observe(this, object : Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
// 任务执行完毕之后,会在这里获取到返回的结果
if(t?.state == WorkInfo.State.SUCCEEDED) {
for((key, value) in t.outputData!!.keyValueMap) {
Log.d("TEST", "Out Data $key ---- $value")
}
}
}
})
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)
参考资料:
- LifecycleOwner
- Observer
可以为任意 WorkRequest 对象分配标记字符串(tag),实现对任务按逻辑进行分组,这样就可以对使用特定标记的所有任务执行操作。例如,WorkManager.cancelAllWorkByTag(String) 会取消所有使用特定标记的任务,而 WorkManager.getWorkInfosByTagLiveData(String) 会返回具有该标记的所有任务的 LiveData 和状态列表。
在构建 WorkRequest
对象时,通过 WorkRequest.Builder.addTag(String) 向任务添加标记.
示例:
val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
.setInputData(Data.Builder().let {
// Data以Builder的方式进行构建,传入的是键值对
it.putString("operator", "Owen")
it.putString("description", "Check network state")
it.build()
})
.addTag("networkWork")
.build()
在任务的整个生命周期内,它会经历多个不同的状态(State)。下面将介绍这些状态:
FAILED
状态,则其所依赖的任务也会被标记为 FAILED
,并且不会运行。CANCELLED
状态,则其所依赖的任务也会被标记为 CANCELLED
,并且不会运行。这里介绍了各种状态,在后续会探讨如何跟踪这些状态
将工作任务加入队列后,可以通过 WorkManager
检查其状态。任务的相关信息在 WorkInfo 对象中提供,包括工作任务的 id、标签(tag)、当前状态(State) 和所有输出数据。
可以通过以下三种方式之一来获取 WorkInfo:
如果需要针对特定的 WorkRequest,您可以通过 WorkRequest id
,利用 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 接口来检索其 WorkInfo
。
如果需要针对指定的标记,您可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象。
对于有唯一名称的工作任务,可以利用 WorkManager.getWorkInfosForUniqueWork(String) 或 WorkManager.getWorkInfosForUniqueWorkLiveData(String) 检索所有匹配的 WorkRequest
的 WorkInfo
对象。
利用以上各种方法获取到 LiveData 变量,然后通过注册监听器来观察 WorkInfo
的变化。
示例:
WorkManager.getInstance(applicationContext)
.getWorkInfoByIdLiveData(checkNetwork.id)
.observe(this, object : Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
// 任务执行完毕之后,会在这里获取到返回的结果
Log.e("TEST", "WorkRequest state: ${t?.state}")
if(t?.state == WorkInfo.State.SUCCEEDED) {
// 显示任务完成通知提示
Toast.makeText(this@MainActivity, "Check network success", Toast.LENGTH_LONG).show()
for((key, value) in t.outputData!!.keyValueMap) {
Log.d("TEST", "Out Data $key ---- $value")
}
}
}
})
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)
从 WorkManager 2.3.0-alpha01
开始,为设置和观察工作器的中间进度添加了一些支持。如果应用在前台运行时,工作器保持运行状态,也可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息。
ListenableWorker 现在支持 setProgressAsync() API,此类 API 可以保留中间进度。开发者能够借助这些 API实现通过界面观察工作任务的中间进度。进度由 Data 类型表示,这是一个可序列化的属性容器(类似于任务数据输入输出,并且受到相同的限制)。
值得注意的是,只有在 ListenableWorker 运行时才能观察到和更新进度信息。如果尝试在 ListenableWorker
完成执行后在其中设置进度,则将会被忽略。可以通过WorkManager
的 getWorkInfoByXXX()
或 getWorkInfoByXXXLiveData()
方法获取 WorkInfo 的实例,然后通过 getProgress() 方法获取包含进度信息的 Data
对象。
使用 ListenableWorker 或 Worker 调用 setProgressAsync() API 设置进度,会返回 ListenableFuture
;更新进度是异步过程,因为更新过程包括将进度信息存储到数据库中。在 Kotlin 中,您可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息。
示例:
// 定义Worker
class CheckDiskProgressWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "Checking disk。。。。。。。。")
Thread.sleep(1000)
// 更新进度
setProgressAsync(Data.Builder().let {
it.putInt("progress", 20)
it.build()
})
Thread.sleep(1000)
// 更新进度
setProgressAsync(Data.Builder().let {
it.putInt("progress", 40)
it.build()
})
Thread.sleep(1000)
// 更新进度
setProgressAsync(Data.Builder().let {
it.putInt("progress", 60)
it.build()
})
Thread.sleep(1000)
// 更新进度
setProgressAsync(Data.Builder().let {
it.putInt("progress", 80)
it.build()
})
Thread.sleep(1000)
// 更新进度
setProgressAsync(Data.Builder().let {
it.putInt("progress", 100)
it.build()
})
Log.e("TEST", "Checking disk done.")
return Result.success(Data.Builder().let {
it.putInt("code", 200)
it.putString("msg", "Disk is fine")
it.build()
})
}
}
提示:
Worker
类就是继承自ListenableWorker
,所以工作任务类继承自Worker
类,就拥有了设置中间进度的API。- 进度的设置类似于任务数据输入输出,但是获取的接口不一样,使用
getProgress()
跟踪进度信息也很简单。可以通过WorkManager
的 getWorkInfoByXXX()
或 getWorkInfoByXXXLiveData()
方法获取 WorkInfo 的实例,然后通过 getProgress() 方法获取包含进度信息的 Data
对象。
示例:
val checkDisk = OneTimeWorkRequestBuilder<CheckDiskProgressWorker>()
.build()
WorkManager.getInstance(applicationContext)
.getWorkInfoByIdLiveData(checkDisk.id)
.observe(this, object : Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
// 任务执行完毕之后,会在这里获取到返回的结果
Log.e("TEST", "WorkRequest state: ${t?.state}")
if(t?.state == WorkInfo.State.RUNNING) {
Log.d("TEST", "Work progress --- ${t.progress.getInt("progress", 0)}")
Toast.makeText(this@MainActivity, "Check disk... ${t.progress.getInt("progress", 0)}%", Toast.LENGTH_LONG).show()
} else if(t?.state == WorkInfo.State.SUCCEEDED){
Toast.makeText(this@MainActivity, "Check disk success", Toast.LENGTH_LONG).show()
} else if(t?.state == WorkInfo.State.FAILED){
Toast.makeText(this@MainActivity, "Check disk failed", Toast.LENGTH_LONG).show()
}
}
})
WorkManager.getInstance(applicationContext).enqueue(checkDisk)
注意:跟踪的观察者的
Lifecycle
如果终止,那么改观察者将不会接受到更新消息。
工作链用于将多个任务关联起来,并定义这些任务的运行顺序,当需要以特定的顺序运行多个任务时,工作链就非常有用。可以使用 WorkManager 创建工作链并对多个任务进行排队。
创建工作链,主要有一下几步:
WorkContinuation
对象使用 WorkContinuation.then(OneTimeWorkRequest) 或 WorkContinuation.then(List注意:
1. 每次调用WorkContinuation.then(...)
都会返回一个新的WorkContinuation
实例;
2. 通过带List
类型参数的 API 添加的工作任务,可能会并行运行。
示例:
//定义Worker
class ContinuationCheck1(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "ContinuationCheck1 .......")
// 打印输入数据
val strB = StringBuilder("ContinuationCheck1 输入数据:\n")
for ((key, value) in inputData.keyValueMap) {
strB.append(key)
.append(" : ")
.append(value)
.append("\n")
}
Log.e("TEST", strB.toString())
Thread.sleep(3000)
Log.e("TEST", "ContinuationCheck1 done .......")
return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck1"))
}
}
class ContinuationCheck2(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "ContinuationCheck2 .......")
// 打印输入数据
val strB = StringBuilder("ContinuationCheck2 输入数据:\n")
for ((key, value) in inputData.keyValueMap) {
strB.append(key)
.append(" : ")
.append(value)
.append("\n")
}
Log.e("TEST", strB.toString())
Thread.sleep(3000)
Log.e("TEST", "ContinuationCheck2 done .......")
return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck2"))
}
}
class ContinuationCheck3(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "ContinuationCheck3 .......")
// 打印输入数据
val strB = StringBuilder("ContinuationCheck3 输入数据:\n")
for ((key, value) in inputData.keyValueMap) {
strB.append(key)
.append(" : ")
.append(value)
.append("\n")
}
Log.e("TEST", strB.toString())
Thread.sleep(3000)
Log.e("TEST", "ContinuationCheck3 done .......")
return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck3"))
}
}
class ContinuationCheck4(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "ContinuationCheck4 .......")
// 打印输入数据
val strB = StringBuilder("ContinuationCheck4 输入数据:\n")
for ((key, value) in inputData.keyValueMap) {
strB.append(key)
.append(" : ")
.append(value)
.append("\n")
}
Log.e("TEST", strB.toString())
Thread.sleep(3000)
Log.e("TEST", "ContinuationCheck4 done .......")
return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck4"))
}
}
// 创建工作链并提交到系统排队
WorkManager.getInstance(applicationContext)
.beginWith(OneTimeWorkRequestBuilder<ContinuationCheck1>().also {
it.setInputData(workDataOf("name" to "Owen", "position" to "Manager"))
}.build())
.then(OneTimeWorkRequestBuilder<ContinuationCheck2>().build())
.then(listOf(OneTimeWorkRequestBuilder<ContinuationCheck3>().build(), OneTimeWorkRequestBuilder<ContinuationCheck4>().build()))
.enqueue()
OneTimeWorkRequest
类型的工作链,父级的输出将会成为下一级的输入,所以在工作链中的输入存在两种情况:
OneTimeWorkRequest
:父级的输出即是下一级的输入,没有冲突。OneTimeWorkRequest
:可以使用 WorkerManager 的 InputMerger 对输入参数进行合并(在构建 OneTimeWorkRequest
对象时通过 OneTimeWorkRequest.Builder.setInputMerger() API 进行设置),WorkerManager 提供了两种参数合并的方式:示例:
// 定义Worker
class ContinuationCheck5(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TEST", "ContinuationCheck5 .......")
// 打印输入数据
val strB = StringBuilder("ContinuationCheck5 输入数据:\n")
for ((key, value) in inputData.keyValueMap) {
strB.append(key)
.append(" : ")
// 使用了ArrayCreatingInputMerger,判断值是否为数组
if (value is Array<*>) {
strB.append("[")
(value as Array<*>).forEach {
strB.append(it)
.append(",")
}
strB.also {
it.delete(it.lastIndexOf(",") - 1, it.length)
}.append("]")
} else {
strB.append(value)
}
strB.append("\n")
}
Log.e("TEST", strB.toString())
Thread.sleep(3000)
Log.e("TEST", "ContinuationCheck5 done .......")
return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck5"))
}
}
// 创建工作链并提交到系统排队
WorkManager.getInstance(applicationContext)
.beginWith(OneTimeWorkRequestBuilder<ContinuationCheck1>().also {
it.setInputData(workDataOf("name" to "Owen", "position" to "Manager"))
}.build())
.then(OneTimeWorkRequestBuilder<ContinuationCheck2>().build())
.then(listOf(OneTimeWorkRequestBuilder<ContinuationCheck3>().build(), OneTimeWorkRequestBuilder<ContinuationCheck4>().build()))
.then(OneTimeWorkRequestBuilder<ContinuationCheck5>().also {
// 设置输入数据合并模式
it.setInputMerger(ArrayCreatingInputMerger::class)
}.build())
.enqueue()
注意:如果工作任务链中可能存在多个父级,输出可能存在冲突且使用了
ArrayCreatingInputMerger
合并模式的情况下,在下一级OneTimeWorkRequest
读取参数时一定要判断值是否为数组
OneTimeWorkRequest
类型的工作链中,工作任务的状态需要注意以下几点:
一个从属的 OneTimeWorkRequest
仅在其所有的父级 OneTimeWorkRequest
都完成并且成功(即返回 Result.success()
)时才会被解除阻塞(变为 ENQUEUED
状态)。
如果有任何父级 OneTimeWorkRequest
失败(即返回 Result.failure()
),则所有从属 OneTimeWorkRequest
也会被标记为 FAILED
且不会执行。
如果有任何父级 OneTimeWorkRequest
被取消,则所有从属 OneTimeWorkRequest
也会被标记为 CANCELLED
且不会执行。
如果不再需要运行先前加入队列的作业,则可以申请取消。最简单的方法是使任务 id
并调用 WorkManager.cancelWorkById(UUID) 来取消单个 WorkRequest
。
示例:
WorkManager.getInstance(applicationContext).cancelWorkById(workId)
在后台,WorkManager
会检查工作任务的状态(State),并做相应的处理:
如果工作任务已经完成,则不会发生任何变化。否则,其状态将更改为 CANCELLED,之后就不会运行这个工作。任何依赖于这项工作任务(从属)的 WorkRequests
的状态也将变为 CANCELLED。
如果工作当前的状态为 RUNNING,则工作器也会收到对 ListenableWorker.onStopped() 回调。重写此方法以处理所有可能需要的清理操作。
您也可以使用 WorkManager.cancelAllWorkByTag(String) API 取消指定标记的所有工作任务。请注意,此方法会取消所有具有此标记的工作任务。此外,您还可以使用 WorkManager.cancelUniqueWork(String) 取消具有唯一名称的所有工作任务。
WorkManager
停止正在运行的工作器可能以下几种原因:
WorkManager.cancelWorkById(UUID)
取消)。WorkRequest
到队列。旧的 WorkRequest
会立即被视为已终止。 在上面这些情况下,WorkRequest
收到对 ListenableWorker.onStopped() 回调,在必要时,应该重写这个方法,执行清理工作并以协作方式完成工作器(例如,您应该在此时或者尽早关闭数据库和文件的打开句柄)。此外,可以通过调用 ListenableWorker.isStopped() 接口确认系统是否已经停止您的应用。另外需要注意的是,在调用了 onStopped()
后,即使通过返回 Result
来指示工作已完成,WorkManager
都会忽略该 Result
,因为工作器已经被视为停止。
注意:当停止一个正在运行的工作任务时,会回调
onStopped()
,如果在doWork()
里面的任务有多个步骤,需要在每一个步骤完成处使用isStopped
(或者其他方法)判断是否停止执行下一步,否则仍旧会继续往下执行代码(这个类似Java中的Thread
),当然,除非你想在工作停止后继续执行所有代码,可以不进行处理。
如果应用需要定期执行某些任务,例如,需要定期备份数据或者上传日志到服务器等。那么将考虑使用 PeriodicWorkRequest 实现定期任务。
示例:
val checkDisk = PeriodicWorkRequestBuilder<CheckDiskProgressWorker>(
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS).build()
WorkManager.getInstance(applicationContext).enqueue(checkDisk!!)
注意事项
1.PeriodicWorkRequest
最小重复执行时间间隔必须是PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS
(900000毫秒,15分钟),小于这个值将取该临界值作为重复执行时间间隔;
2.PeriodicWorkRequest
跟OneTimeWorkRequest
一样可以可以添加任务约束、跟踪任务状态、跟踪任务的中间状态以及取消和停止共工作等;
3.PeriodicWorkRequest
无法使用链接功能,如果需要将服务链接起来,请使用 OneTimeWorkRequest;
4. 重复性工作在单次任务执行完毕后即返回了Result.success()
,也不会监听到State.SUCCEEDED
状态。
唯一工作任务是一个概念性非常强的术语,它可确保一次只有一个具有特定名称的工作任务。与 id
不同的是,唯一名称是人类可读的,由开发者指定,而不是由 WorkManager
自动生成。也与标记不同,唯一名称仅与“一个”工作任务关联。
创建为一个工作任务序列,可以通过调用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 或 WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) API 接口。第一个参数是唯一名称(这是我们用来标识 WorkRequest
的键);第二个参数是冲突解决策略(它指定了如果已经存在一个具有该唯一名称的未完成工作任务,WorkManager
应该如何处理);第三个参数是WorkRequest
示例:
class DoWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
val op = inputData.getString("op");
Log.e("TEST", "$id $op .......")
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
onStopped()
}
if(isStopped) {
Log.e("TEST", "$id $op stopped!")
return Result.failure()
}
Log.e("TEST", "$id $op 50%.......")
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
onStopped()
}
if(isStopped) {
Log.e("TEST", "$id $op stopped!")
return Result.failure()
}
Log.e("TEST", "$id $op done .......")
return Result.success()
}
override fun onStopped() {
super.onStopped()
}
}
// 添加唯一任务
WorkManager.getInstance(applicationContext).enqueueUniqueWork("checkDisk",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<DoWorker>()
.setInputData(workDataOf("op" to "Check"))
.build().also {
Log.e("TEST", "${it.id} Check")
})
唯一工作任务的冲突策略有以下几种:
注意:
APPEND
策略不能和PeriodicWorkRequest
一起使用,因为PeriodicWorkRequest
不会自动进入完成状态。
当程序存在不能多次排队的任务时,唯一工作就非常有用。例如,如果程序需要同步日志到服务器,可以添加一个名为“uploadLog”的唯一任务进行排队,并且使用 KEEP
策略,如果任务未完成再次添加这个唯一名称的任务,将会忽略后面添加的。
当要对一个工作任务链进行限制时,可以使用具有唯一名称的唯一名称任务链,使用 WorkManager.beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 代替 WorkManager.beginWith() 即可,这样就可以采用唯一名称的策略来处理具有相同唯一名称的工作链了。例如:检查设备,需要检查设备的多个项目,每一项检查需要一定的时间来完成,将每一个检查项的执行写成一个Worker,添加到唯一工作链中,在工作链执行过程中,如果多次请求检查,可以通过策略 KEEP
来忽略掉后面的检查操作。
示例:
WorkManager.getInstance(applicationContext).beginUniqueWork("check",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<DoWorker>()
.setInputData(workDataOf("op" to "Check Disk"))
.build())
.then(OneTimeWorkRequestBuilder<DoWorker>()
.setInputData(workDataOf("op" to "Check Network"))
.build())
.then(OneTimeWorkRequestBuilder<DoWorker>()
.setInputData(workDataOf("op" to "Check System"))
.build())
.enqueue()
在 Workmanager
库中,提供了异步工作任务的支持 RxWorker,这个 Worker
的实现基于 RxJava;还有Kotlin专有的CoroutineWorker,这个 Worker
重写了 doWork
方法,便于执行挂起工作。
示例:
class CWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result { // 不同之处是doWork方法多了个suspend关键字
val op = inputData.getString("op");
Log.e("TEST", "$id $op .......")
delay(3000)
if(isStopped) {
Log.e("TEST", "$id $op stopped!")
return Result.failure()
}
Log.e("TEST", "$id $op 50%.......")
delay(3000)
if(isStopped) {
Log.e("TEST", "$id $op stopped!")
return Result.failure()
}
Log.e("TEST", "$id $op done .......")
return Result.success()
}
}
默认情况下,当您的应用启动时,WorkManager 使用适合大多数应用的合理选项自动进行配置。如果您需要进一步自定义 WorkManager 管理和调度工作的方式,可以通过自己自定义 WorkManager 配置来初始化 WorkManager 。
WorkManager 2.1.0 及更高版本有多种配置 WorkManager 的方式。其中最灵活方式是按需初始化。通过按需初始化,可以只在需要 WorkManager 时创建该组件,而不是每次启动应用时都创建。这样做可将 WorkManager 从关键启动路径中移除,从而提高应用启动性能。要使用按需初始化,请执行以下操作:
要提供自己的配置,必须先移除默认的初始化程序。为此,请使用合并规则 tools:node="remove"
更新 AndroidManifest.xml
:
AndroidManifest.xml
引入 tools
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.owen.workmanagerdm">
manifest>
androidx.work.impl.WorkManagerInitializer
provider 组件声明,并添加合并规则 tools:node="remove"
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove"/>
说明: WorkManager aar库中的
AndroidManifest.xm
包含了初始化组件的声明,添加合并规则tools:node="remove"
之后,在编译过程中,会将该组件删除。
Application
类,并实现 Configuration.Provider 接口,重写 getWorkManagerConfiguration() 方法,返回 Configuration 对象。示例:
class MainApp : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.setMaxSchedulerLimit(Configuration.MIN_SCHEDULER_LIMIT + 100)
.build()
}
}
在 AndroidManifest.xml
中添加 application
声明,在
标签内添加 android:name
属性,并指向自定义的 Application
.
当需要使用 WorkManager 时,调用方法 WorkManager.getInstance(Context)
。WorkManager 将会调用应用的自定义的 getWorkManagerConfiguration()
方法来获取 Configuration
。(无需手动调用 WorkManager.initialize()
。)
WorkManager 2.0.1 更早版本,有两个初始化选项:
当应用启动时,WorkManager 使用自定义的 ContentProvider
进行初始化(此代码位于内部类 androidx.work.impl.WorkManagerInitializer
中)。默认初始化使用默认初始化程序(除非明确停用它,默认初始化程序适合大多数应用)和默认 Configuration
对 WorkManager 进行初始化。
如果想完全控制初始化程序,按以下步骤进行:
val config = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.setMaxSchedulerLimit(Configuration.MIN_SCHEDULER_LIMIT + 100)
.build()
WorkManager.initialize()
手动初始化 WorkManagerWorkManager.initialize(this, config)
更多关于配置项的说明参考: Configuration.Builder
注意:为了避免获取WorkManager单例时出现未初始化的情况,请确保初始化在
Application.onCreate()
或ContentProvider.onCreate()
中进行。
在前面的内中,提到了 WorkManager 可以异步执行后台工作,这个实现可满足大多数应用的需求。
WorkManager 提供了四种不同类型的工作基元:
Worker 是最简单的实现,WorkManager 会在后台线程上自动运行它。
CoroutineWorker 是基于Kotlin协程实现的,建议 Kotlin 用户实现这个基类。CoroutineWorker
对后台工作公开挂起函数,在默认情况下它们运行默认的 Dispatcher,也可以对其进行自定义。
RxWorker 是基于 RxJava2 实现,如果项目中很多现有异步代码是用 RxJava 建模的,则应使用 RxWorker
。与所有 RxJava2 概念一样,您可以自由选择所需的线程处理策略。
ListenableWorker 是 Worker
、CoroutineWorker
和 RxWorker
的基类。该类专为需要与基于回调的异步 API(例如 FusedLocationProviderClient)进行交互并且不使用 RxJava2 的 Java 开发者而设计。
当使用 Worker
时,WorkManager 会自动在后台线程中调用 Worker.doWork()
。这个后台线程是来自于 WorkManager 的 Configuration
中指定的 Executor
。默认情况下,WorkManager 会为您设置 Executor
,但也可以通过手动初始化时自定义 Executor
,详情请参考:WorkManager配置和初始化
class MainApp : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.setMaxSchedulerLimit(Configuration.MIN_SCHEDULER_LIMIT + 100)
.setExecutor(Executors.newFixedThreadPool(16)) // 使用自定义的Executor
.build()
}
}
下面是一个简单的工作器示例,检查设备状态:
class CWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
val op = inputData.getString("op");
Log.e("TEST", "$id $op .......")
delay(3000)
Log.e("TEST", "$id $op 50%.......")
delay(3000)
Log.e("TEST", "$id $op done .......")
return Result.success()
}
}
请注意,Worker.doWork()
是同步调用的,在 Worker.doWork()
中将会以阻塞方式完成整个后台工作,并在方法退出时完成工作。如果在 doWork()
中调用异步 API 并返回 Result
,则回调可能无法正常运行。如果您遇到这种情况,请考虑使用 ListenableWorker
(请参阅在 ListenableWorker 中进行线程处理)。
正在运行的 Worker
停止时(无论是何种原因),工作器会收到对 Worker.onStopped()
的调用。重写此方法或在代码的检查点处调用 Worker.isStopped()
,并在必要时释放资源。
示例:
class CWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
val op = inputData.getString("op");
Log.e("TEST", "$id $op .......")
delay(3000)
if(isStopped) {
Log.e("TEST", "$id $op stopped!")
return Result.failure()
}
Log.e("TEST", "$id $op 50%.......")
delay(3000)
if(isStopped) {
Log.e("TEST", "$id $op stopped!")
return Result.failure()
}
Log.e("TEST", "$id $op done .......")
return Result.success()
}
}
注意:当
Worker
停止后,从Worker.doWork()
返回什么已不重要了,因为Result
将被忽略。
WorkManager 为 Kotlin 协程提供了非常完美的支持。如果使用 Kotlin 开发,请使用 work-runtime-ktx
依赖,定义工作器时不要扩展 Worker
,而应扩展 CoroutineWorker,CoroutineWorker
使用的 API 有些不同。
前面提到,doWork()
是同步运行的,这意味着内部的代码是按顺序执行,如果在内部实现了单项任务,这个也没有什么问题,但是如果里面有多项任务时,顺序执行无疑是增大了运行时间,如果将每一项任务放到协助中异步执行,将会节省很多时间,当然,前提是这些任务都是互不干扰,可以独立执行的,例如,检查多个文件的完整性,可以将对每个文件检查降到一个独立的协程中,这样就可以节省大量的检查时间。
示例:
class CheckFileWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result = coroutineScope {
val jobs = (0 until 20).map {
async {
checkFile(it)
}
}
val results = jobs.awaitAll()
results.forEach {
if(!it) {
Result.failure()
}
}
Result.success()
}
private suspend fun checkFile(num: Int): Boolean {
Log.e("TEST", "Checking file $num .......")
delay(3000)
if(isStopped) {
Log.e("TEST", "Checking file $num stopped!")
return false
}
Log.e("TEST", "Checking file $num 50%.......")
delay(3000)
if(isStopped) {
Log.e("TEST", "Checking file $num stopped!")
return false
}
Log.e("TEST", "Checking file $num 100% done .......")
return true
}
}
注意:
CoroutineWorker.doWork()
是一个“挂起”函数。跟Worker
不同的是,CoroutineWorker.doWork()
的码不会在Configuration
中指定的Executor
上运行,而是在默认的Dispatchers.Default
上运行,也可以通过提供自己的CoroutineContext
来自定义这个行为。
WorkManager 提供了与 RxJava2 之间的互操作性。要开始使用这种互操作性,除了 work-runtime
,还应添加 work-rxjava2
依赖。定义工作器时扩展 RxWorker
,而不是扩展 Worker
。最后替换 RxWorker.createWork()
方法以返回 Single
,用于表示您执行的 Result
。
示例:
public class RxCheckFileWorker extends RxWorker {
public RxCheckFileWorker(Context context, WorkerParameters params) {
super(context, params);
}
@Override
public Single<Result> createWork() {
return Observable.range(0, 100)
.flatMap {
checkFile(it)
}
.toList()
.map {
Result.success()
};
}
}
注意:
RxWorker.createWork()
在主线程上调用,但默认情况下会在后台线程上订阅返回值。您可以替换RxWorker.getBackgroundScheduler()
来更改订阅线程。另外,如果使用Kotlin开发,强烈建议使用CoroutineWorker
。
RxWorker
在停止时会妥善处理 Observer
,因此,在停止工作器时无需做任何的特殊处理。
在某些情况下,可能需要自定义线程处理策略。例如,需要处理基于回调的异步操作。在这种情况下,不能只依靠 Worker
来完成操作,因为它无法以阻塞方式完成这项任务。这时就需要通过 ListenableWorker 来实现了。ListenableWorker
是最低层级的工作器 API;Worker
、CoroutineWorker
和 RxWorker
都是从这个类衍生而来的。ListenableWorker
只会发出应该开始和停止工作的信号,而线程处理则完全自定义编码实现。开始工作的信号是在主线程上调用,因此手动选择转到后台的线程也就只管重要。
抽象方法 ListenableWorker.startWork()
会返回一个包含操作 Result
的 ListenableFuture
。ListenableFuture
是一个轻量级接口:它是一个 Future
,用于提供设置监听器和传递异常信息的功能。在 startWork
方法中,必须返回一个 ListenableFuture
,在操作完成后,需要将操作的 Result
设置到到这个 ListenableFuture
。
可以通过以下两种方式创建 ListenableFuture
:
Guava
,请使用 ListeningExecutorService。councurrent-futures
依赖,然后使用 CallbackToFutureAdapter
。示例:
class CheckFileListenableWorker(context: Context, workerParameters: WorkerParameters) : ListenableWorker(context, workerParameters) {
override fun startWork(): ListenableFuture<Result> {
return CallbackToFutureAdapter.getFuture(CallbackToFutureAdapter.Resolver {
var succ = 0
val callback = object : Callback {
override fun onSuccess(id: Int) {
Log.e("TEST", "File $id check success")
succ++
if(succ == 8) {
it.set(Result.success())
}
}
override fun onFail(id: Int) {
Log.e("TEST", "File $id check failed")
it.set(Result.failure())
}
}
val jobs = (0 until 8).map { index ->
checkFileAsync(index, callback)
}
callback
})
}
private fun checkFileAsync(num: Int, callback: Callback): Boolean {
Thread(Runnable {
Log.e("TEST", "Checking file $num .......")
if(isStopped) {
Log.e("TEST", "Checking file $num stopped!")
return@Runnable
}
Log.e("TEST", "Checking file $num 50%.......")
Thread.sleep(3000)
if(isStopped) {
Log.e("TEST", "Checking file $num stopped!")
return@Runnable
}
Log.e("TEST", "Checking file $num 100% done .......")
callback.onSuccess(num)
}).start()
return true
}
interface Callback {
fun onSuccess(id: Int)
fun onFail(id: Int)
}
}
如果工作停止,则始终会取消 ListenableWorker
的 ListenableFuture
。通过使用 CallbackToFutureAdapter
的 addCancellationListener()
API 添加一个取消监听器,即可监听到 Work 被取消的事件。如果 Work 被取消,可以处理 Work 内部启动的异步线程,节省资源,而不是任由那些线程自生自灭。
示例:
class CheckFileListenableWorker(context: Context, workerParameters: WorkerParameters) : ListenableWorker(context, workerParameters) {
override fun startWork(): ListenableFuture<Result> {
return CallbackToFutureAdapter.getFuture(CallbackToFutureAdapter.Resolver {
var succ = 0
val callback = object : Callback {
override fun onSuccess(id: Int) {
Log.e("TEST", "File $id check success")
succ++
if(succ == 8) {
it.set(Result.success())
}
}
override fun onFail(id: Int) {
Log.e("TEST", "File $id check failed")
it.set(Result.failure())
}
}
it.addCancellationListener(object : Runnable {
override fun run() {
Log.e("Test", "Worker was canceled!!!!!!!")
// TODO 在这里可以取消异步线程的运行
}
}, Executors.newFixedThreadPool(8))
val jobs = (0 until 8).map { index ->
checkFileAsync(index, callback)
}
callback
})
}
private fun checkFileAsync(num: Int, callback: Callback): Boolean {
Thread(Runnable {
Log.e("TEST", "Checking file $num .......")
if(isStopped) {
Log.e("TEST", "Checking file $num stopped!")
return@Runnable
}
Log.e("TEST", "Checking file $num 50%.......")
Thread.sleep(3000)
if(isStopped) {
Log.e("TEST", "Checking file $num stopped!")
return@Runnable
}
Log.e("TEST", "Checking file $num 100% done .......")
callback.onSuccess(num)
}).start()
return true
}
interface Callback {
fun onSuccess(id: Int)
fun onFail(id: Int)
}
}
本文篇幅有点长,是笔者在研究 WorkManager 过程中书写,如果有不正确之处请指教。另外,喜欢阅读官方文档的同学可以参考:WorkManager
本文测试Demo源码下载