目录
jetpackd的WorkManager可以用来处理后台任务
本篇介绍jetpack的WorkManager的基本使用方法
1.配置信息
def work_version = "2.3.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
2.使用方式
2.1 定义Worker
class MyWork(var context: Context, var params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
Log.e(TAG_1234, "开始执行 任务1")
val data = params.inputData.getString("key")
data?.let {
Log.e(TAG_1234, "获取到传入的值 ->> $it")
}
val outData = Data.Builder().putString("dataKey", "传递的值任务1").build()
return Result.success(outData)
}
}
这里定义了一个参数获取和参数传递的方法,并回调方法Result.success() 表示方法执行完成
2.2 创建WorkRequest
private fun getOneTimeRequest():OneTimeWorkRequest{
val sendData = Data.Builder().putString("key", "1234").build()
var oneTimeRequest = OneTimeWorkRequest.Builder(MyWork::class.java)
.setInputData(sendData).build()
observerWorkRequest(oneTimeRequest, type)
return oneTimeRequest
}
private fun observerWorkRequest(request: WorkRequest, type: Int) {
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
.observe(this, object : Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
val status = t?.state
Log.e(TAG_1234, "work status ->> $status $type ")
status?.isFinished?.apply {
if (this) {
val key = t!!.outputData.getString("dataKey")
if (key != null) {
Log.e(TAG_1234, "执行完成 $key")
}
}
}
}
});
}
这里定义了一个OneTimeWorkRequest,传入了自定义的字符串,并设置了任务执行的监听,在回调中获取完成的值; 打印出当前观测的状态和结束后拿到的值,这里的键和任务Worker那里的得保持一致
2.3 执行任务
private fun testBackgroundWork1() {
WorkManager.getInstance(this).enqueue(getOneTimeRequest())
}
输出结果是
E/1234: 开始执行 任务1
E/1234: 获取到传入的值 ->> 1234
E/1234: work status ->> ENQUEUED 1
E/1234: work status ->> SUCCEEDED 1
E/1234: 执行完成 传递的值任务1
这样一个基础的用法就完成了
3.拓展
3.1 WorkRequest
目前WorkRequest只有两个实现类 OneTimeWorkRequest 和 PeriodicWorkRequest
OneTimeWorkRequest 是只执行一次的任务,执行完就销毁
PeriodicWorkRequest 是一个周期性任务,可以指定一个周期去执行任务,但这里的周期的最小值一般是15分钟,少于15分钟的都会按15分钟来处理
二者都是通过建造模式构建的,但定期任务会额外传入时间间隔
public static final class Builder extends WorkRequest.Builder<Builder, OneTimeWorkRequest> {
Builder(@NonNull Class<? extends ListenableWorker> workerClass){...}
}
public static final class Builder extends WorkRequest.Builder<Builder, PeriodicWorkRequest> {
public Builder(
@NonNull Class<? extends ListenableWorker> workerClass,
long repeatInterval,
@NonNull TimeUnit repeatIntervalTimeUnit) {
...
}
}
因为二者都是继承自 WorkRequest.Builder,有些公用方法可以设置,具体有
public final @NonNull B setInputData(@NonNull Data inputData)
setInputData 设置传给Worker的值,通过 Data.Builder().putString(key,value).build() 创建数据Data对象
public final @NonNull B addTag(@NonNull String tag)
addTag 给任务添加标签,可以添加多个,会放在一个set集合中,可以通过任务对象,或者在observer观测结果中获取
public final @NonNull B setConstraints(@NonNull Constraints constraints)
setConstraints 设置约束条件,可以选择任务在满足什么样的条件下执行
public @NonNull B setInitialDelay(long duration, @NonNull TimeUnit timeUnit)
setInitialDelay 设置开始执行任务的延迟,当设置这个值时,那么该任务在加入到队列后延迟一定时间才开始执行任务
3.2 约束
我们可以指定应用在满足什么样的情况下可以执行任务,不满足情况则会等待相应条件满足再去执行
约束一般定义有以下几种
private fun buildConstraint(): Constraints {
return Constraints.Builder()
.setRequiresCharging(true) //充电中
.setRequiresDeviceIdle(false) //空闲
.setRequiresBatteryNotLow(false) //低电量
.setRequiresStorageNotLow(false) //低存储
.setRequiredNetworkType(NetworkType.CONNECTED) //联网状态
.build()
}
setRequiresCharging
设置是否在充电状态下执行
setRequiresDeviceIdle
设置是否在空闲状态下执行,这个空闲指的是一段时间没有使用手机,而不是指手机里没开应用就是空闲
setRequiresBatteryNotLow
设置是否在电量低于阈值时执行,默认flase
setRequiresStorageNotLow
设置是否在存储低于阈值时执行,默认false
setRequiredNetworkType
设置执行任务的网络状态
NetworkType | 描述 |
---|---|
CONNECTED | 需要网络连接 |
NOT_REQUIRED | 无需网络 |
UNMETERED | 非计费网络,比如wifi |
NOT_ROAMING | 非漫游网络 |
METERED | 计费网络,比如4G |
3.3 Worker
worker就是我们所创建的任务,只有一个doWork的实现方法,这里是线程池执行的
class MyWork2(var context: Context, var params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return Result.success()
}
}
Result有三种返回
success 成功
failure 失败
retry 重试
其中success和failure都是可以传递返回值Data的
当回传的结果是Result.retry()时,并不是马上就会重新重新尝试再次执行,而是会等一定时间再去尝试,比如我这里返回重试打印的日志是
2020-04-11 18:39:18.308 E/1234: 开始执行 任务1
2020-04-11 18:39:50.246 E/1234: 开始执行 任务1
2020-04-11 18:39:58.266 E/1234: 开始执行 任务1
2020-04-11 18:40:35.753 E/1234: 开始执行 任务1
2020-04-11 18:40:50.393 E/1234: 开始执行 任务1
而且重试时间也不是固定的,另外如果一直没重试成功过,下次进入应用还会进行重试
3.4 批次任务
3.4.1 串行
private fun testBackgroundWork2() {
WorkManager.getInstance(this)
.beginWith(getOneTimeRequest(1))
.then(getOneTimeRequest(2))
.then(getOneTimeRequest(3))
.then(getOneTimeRequest(4))
.then(getOneTimeRequest(5))
.enqueue()
}
或者
val list = mutableListOf<OneTimeWorkRequest>()
list.add(getOneTimeRequest(1))
list.add(getOneTimeRequest(2))
list.add(getOneTimeRequest(3))
WorkManager.getInstance(this).beginWith(list)
.then(getOneTimeRequest(4))
.then(getOneTimeRequest(5))
.enqueue()
都是可以的
比如我在上面的监听中加上任务的position信息,然后打印日志是
E/1234: 开始执行 任务1
E/1234: work status ->> ENQUEUED 1
E/1234: work status ->> BLOCKED 2
E/1234: work status ->> BLOCKED 3
E/1234: 开始执行 任务2
E/1234: work status ->> BLOCKED 4
E/1234: work status ->> BLOCKED 5
E/1234: work status ->> SUCCEEDED 1
E/1234: 开始执行 任务3
E/1234: work status ->> SUCCEEDED 2
E/1234: work status ->> SUCCEEDED 3
E/1234: work status ->> RUNNING 4
E/1234: 开始执行 任务4
E/1234: work status ->> ENQUEUED 5
E/1234: 开始执行 任务5
E/1234: work status ->> SUCCEEDED 4
E/1234: work status ->> SUCCEEDED 5
然后我把任务2的返回改成Result.failure(),打印结果是
E/1234: 开始执行 任务1
E/1234: work status ->> BLOCKED 2
E/1234: work status ->> BLOCKED 3
E/1234: work status ->> BLOCKED 4
E/1234: work status ->> BLOCKED 5
E/1234: 开始执行 任务2
E/1234: work status ->> SUCCEEDED 1
E/1234: work status ->> RUNNING 2
E/1234: work status ->> FAILED 3
E/1234: work status ->> FAILED 4
E/1234: work status ->> FAILED 5
E/1234: work status ->> FAILED 2
可见第二个任务执行出错后,后面的任务将全部不会执行,而且也全部会通知失败,所以才有并行
3.4.2 并行
val worker1 = WorkManager.getInstance(this).beginWith(getOneTimeRequest(1)).then(getOneTimeRequest(2)).then(getOneTimeRequest(3))
val worker2 = WorkManager.getInstance(this).beginWith(getOneTimeRequest(4)).then(getOneTimeRequest(5))
val list = mutableListOf<WorkContinuation>()
list.add(worker1)
list.add(worker2)
WorkContinuation.combine(list).enqueue()
这里定义了两个串行任务,然后把这两个任务并发处理
E/1234: 开始执行 任务1
E/1234: work status ->> ENQUEUED 1
E/1234: 开始执行 任务4
E/1234: work status ->> BLOCKED 2
E/1234: work status ->> BLOCKED 3
E/1234: 开始执行 任务2
E/1234: work status ->> SUCCEEDED 4
E/1234: 开始执行 任务5
E/1234: work status ->> ENQUEUED 5
E/1234: 开始执行 任务3
E/1234: work status ->> SUCCEEDED 1
E/1234: 开始执行 任务1
E/1234: work status ->> SUCCEEDED 2
E/1234: work status ->> SUCCEEDED 3
E/1234: work status ->> SUCCEEDED 5
可以看出任务1 ,2 , 3 和 4, 5之间的串并行关系,然后继续吧任务2改成返回false
E/1234: application onCreate
E/1234: 开始执行 任务1
E/1234: work status ->> ENQUEUED 1
E/1234: 开始执行 任务4
E/1234: work status ->> BLOCKED 2
E/1234: work status ->> BLOCKED 3
E/1234: 开始执行 任务2
E/1234: work status ->> SUCCEEDED 4
E/1234: 开始执行 任务5
E/1234: work status ->> ENQUEUED 5
E/1234: work status ->> SUCCEEDED 1
E/1234: work status ->> FAILED 2
E/1234: work status ->> FAILED 3
E/1234: work status ->> SUCCEEDED 5
可见两并行任务互不干扰,串行的现象和上面的一致
3.4.3 唯一任务
根据指定的唯一key名称,保证只有同一个key对应的任务正常执行,但这并不是说一定只有一个
private fun testBackgroundWork6() {
val request1 = getOneTimeRequest(1)
val request2 = getOneTimeRequest(2)
val uniqueName = "1234";
WorkManager.getInstance(this).enqueueUniqueWork(uniqueName, ExistingWorkPolicy.KEEP, request1);
WorkManager.getInstance(this).enqueueUniqueWork(uniqueName, ExistingWorkPolicy.REPLACE, request2)
}
这个的输出结果是
E/1234: work status ->> ENQUEUED 2
E/1234: 开始执行 任务2
E/1234: work status ->> SUCCEEDED 2
可见任务1没有执行,直接被任务2替换掉
这里有一个ExistingWorkPolicy参数,主要有三个值
REPLACE 替换掉之前的任务并执行
KEEP 保留之前的任务,后续的任务将不执行
APPEND 加在之前任务后面,类似串行执行
当然这个也得看执行的时机,如果任务1执行耗时较长,然后期间任务2执行了,并指定策略是REPLACE,会怎么样
比如我把任务1的任务执行方法期间睡眠1000ms,然后任务2的插入时间稍微往后挪一点
private fun testBackgroundWork6() {
val request1 = getOneTimeRequest(1)
val request2 = getOneTimeRequest(2)
val uniqueName = "1234";
WorkManager.getInstance(this).enqueueUniqueWork(uniqueName, ExistingWorkPolicy.KEEP, request1);
GlobalScope.launch(Dispatchers.IO) {
delay(200)
WorkManager.getInstance(this@WorkManagerActivity).enqueueUniqueWork(uniqueName, ExistingWorkPolicy.REPLACE, request2)
}
}
输出结果就变为了
E/1234: 开始执行 任务1
E/1234: work status ->> RUNNING 1
E/1234: 开始执行 任务2
E/1234: work status ->> null 1
E/1234: work status ->> RUNNING 2
E/1234: work status ->> SUCCEEDED 2
E/1234: 任务1 doWork方法结束
任务1方法会继续走完,也就是说并不会强制杀死任务1,但是不会通知成功的状态,而且状态通知也变成了null;之后只会回调任务2的成功通知
3.4.4 周期任务
private fun getPeriodRequest(constraints: Constraints?, type: Int): PeriodicWorkRequest {
val perioadRequest = PeriodicWorkRequest.Builder(getWorkByType(type), 10, TimeUnit.SECONDS)
.build()
observerWorkRequest(perioadRequest, type)
return perioadRequest
}
private fun getWorkByType(type: Int): Class<out Worker> {
when (type) {
1 -> return MyWork::class.java
2 -> return MyWork2::class.java
3 -> return MyWork3::class.java
4 -> return MyWork4::class.java
5 -> return MyWork5::class.java
}
return MyWork::class.java
}
private fun testPeriod() {
WorkManager.getInstance(this).enqueue(getPeriodRequest(buildConstraint(), 1))
}
打印日志为,大致为15分钟左右,但也可能会有偏差
2020-04-11 19:40:57.745 E/1234: work status ->> ENQUEUED 1
2020-04-11 19:40:57.760 E/1234: 开始执行 任务1
2020-04-11 19:40:57.767 E/1234: work status ->> RUNNING 1
2020-04-11 19:40:58.800 E/1234: 任务1 doWork方法结束
2020-04-11 19:40:58.884 E/1234: work status ->> ENQUEUED 1
2020-04-11 19:56:37.181 E/1234: 开始执行 任务1
2020-04-11 19:56:38.185 E/1234: 任务1 doWork方法结束
周期任务执行比较特殊,一般会设置约束条件,让在满足的条件下执行,即便此次存活期间没有执行,下次重新启动后还是会检测执行;也就是说这些任务会配合Room写进数据库,任务执行会有数据库的校验,当然卸载了也就没了
此外测试期间,如果我主动从后台把应用杀死,那么周期任务也就不会执行了,会在下次进入应用后重新检测