jetpack之WorkManager的使用方法

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写进数据库,任务执行会有数据库的校验,当然卸载了也就没了
此外测试期间,如果我主动从后台把应用杀死,那么周期任务也就不会执行了,会在下次进入应用后重新检测

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值