文章目录
一、前言
WorkManager属于Android Jetpack的一部分,通过WorkManager API可以轻松第调度可延迟的任务,即使是那些在应用退出或者设备重启时仍需要运行的任务。
WorkManager的主要功能
- 最高可向后兼容到 API 14
- 在 API 23 及以上级别的设备上运行使用 JobScheduler实现
- 在 API 14-22 的设备上运行结合使用 BroadcastReceiver 和 AlarmManager实现
- 支持网络可用性或充电状态等工作约束
- 支持调度一次性任务和周期行的任务
- 支持监控和管理计划任务
- 将任务链接起来
- 确保任务执行,即使应用退出或者设备重启也会执行任务
- 遵循低电耗模式等省电功能
WorkManager的主要用途
WorkManager主要用于可延迟执行的任务,并且在应用退出或者设备重启时必须能可靠运行的任务。例如:
- 向服务端发送日志或者分析数据
- 定期同步应用数据到服务器
WorkManager的局限性
WorkManager 不适用于应用进程结束时能够安全终止的运行中后台任务,也不适用于需要立即执行的任务。因此,也不要滥用WorkManager。
二、WorkManager入门指南
WorkManager 的使用主要有以下几点操作:
- 将 WorkManager 添加到您的 Android 项目中(添加依赖)
- 创建后台任务
- 配置运行任务的方式和时间
- 将任务提交给系统
2.1 添加依赖
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 库版本历史
2.2 创建后台任务
任务使用 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()
}
}
2.3 配置运行任务的方式和时间
使用 Worker 定义任务之后,使用 WorkRequest 定义任务的运行方式和时间。任务可以是一次性的,也可以是周期性的。对于一次性 WorkRequest,请使用 OneTimeWorkRequest,对于周期性工作,请使用 PeriodicWorkRequest。
示例:
val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>().build()
2.4 将任务提交给系统
定义 WorkRequest 之后,现在可以通过 WorkManager 的 enqueue() 方法来调度它,将任务提交给系统进行管理和运行。
示例:
WorkManager.getInstance(applicationContext).enqueue(checkSystem)
三、WorkManager 进阶
3.1 设定任务请求
前面介绍了如何创建简单的任务请求(WorkRequest)并将其放到系统队列中。在这个章节中,将会详细介绍任务和任务请求的各种设定。
3.1.1 任务约束
可以通过向任务请求中添加 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)
上面的示例中,为任务添加了需要网络连接的约束,在联网状态下,任务放入队列之后将会安排执行,但是如果当前设备断开了网络,并不会立刻执行,而是在队列中等到网络恢复,网络恢复后,等待的任务会继续执行。
如果为一个任务设定了多个约束,当所有约束都满足时任务才会执行。如果运行期间任务的约束不满足,将会停止任务的执行,等到约束满足时,系统将会尝试恢复执行任务。
3.1.2 初始延迟
如果任务没有设定约束或者所有约束都满足,任务有可能会立即执行。如果不想任务立即执行,可以设定一个最短的延迟时间。需要设定最短初始延迟时间,需要向任务请求中调用setInitialDelay()接口设定。
说明:为什么说最短延迟时间呢?因为将任务提交给系统,本来就有可能出现等待延迟,同理,设定了初始延迟时间的任务即使到了延迟时间,任务也有可能需要等待资源。换句话说,任务开始执行的时间 >= 设定的初始延迟时间
val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
.setInitialDelay(3000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)
上面的例子可以发现,当任务请求提交到系统时,并不会立即执行,而是等待一段时间才会开始执行。
3.1.3 重试和退避政策
如果任务执行失败,需要让 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. 因为任务提交给系统到真是执行并不是实时的,所以,真实的延长时间不一定是预期的(有可能会出现后一次重试时间间隔比前一次的还要短),但是一定是大于等于预期值。
3.1.4 定义任务的输入/输出
将任务提交给系统,但是需要和任务进行交互,就需要实现任务的输入/输出。将需要的数据以参数的形式传入到任务(输入),任务执行完毕后返回结果(输出)。传入参数通过 WorkerReuqest 的 setInputData() 接口传入。输入和输出值以键值对的形式存储在 Data 对象中。在 Worker 中通过getInputData()接口获取输入参数。类似地,从 Worker 中返回数据(输出)也是通过 Data 对象,并通过Result 对应带有返回数据参数的方法返回结果,例如:Result.success(Data)、Result.failure(Data)
说明:
Data对象可以通过原始的Data.Builder进行构建,也可以使用worker库提供的内联函数workDataOf(vararg pairs: Pair<String, Any?>)进行构建,原理一样
示例:
// 定义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<WorkInfo>对象 - 为
LiveData<WorkInfo>对象添加观察者 - 在观察者的
onChanged()方法中获取返回的数据 - 将
WorkRequest提交到系统
获取
LiveData<WorkInfo>对象的接口有多个,详细请参考: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)
参考资料:
3.1.5 标记任务
可以为任意 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"
WorkManager深入解析

本文全面解析WorkManager在Android开发中的应用,涵盖入门指南、进阶技巧、高级概念及线程处理,助您掌握任务调度核心。
最低0.47元/天 解锁文章
2177

被折叠的 条评论
为什么被折叠?



