WorkManager 概述:WorkManager 可以自动维护后台链式任务的执行时机、执行状态。
WorkManager 一般用于多文件上传,复杂计算,多流程控制等。
// WorkManager
api 'androidx.work:work-runtime:2.2.0'
Data的优势: 1)一组持久的键(字符串)/ 值(字符串、基本元素或其数组变量)对,用作ListenableWorkers的输入和输出; 2)有效负载的串行化(字节数组)大小限制10KB; 3)支持序列化和反序列化持久操作,更轻量级(轻量级HashMap)。
WorkManager操作:
// 同时执行A(先)->B(后)、C(先)->D(后), 最后执行E WorkContinuation left = workManager.beginWith(A).then(B);WorkContinuation right = workManager.beginWith(C).then(D);WorkContinuation final = WorkContinuation.combine(Arrays.asList(left, right)).then(E);final.enqueue();
// step 1.创建 Worker
/*** 上传文件Worker (Worker一般用做文件上传,任务的计算等操作)** WorkManager在运行时实例化Worker类,并在预先指定的后台线程上调用doWork()方法,* 此doWork方法用于同步处理您的工作,这意味着一旦您从该方法返回,Worker将被视为已完成并将被销毁。* 如果工作因任何原因被抢占,则不会重用Worker的同一实例。这意味着每个Worker实例只调用一次doWork(),** 如果需要重新运行某个工作单元,将创建一个新的Worker。* 如果需要异步工作或调用异步API,则应使用ListenableWorker。** Worker 最多有十分钟的时间完成其执行并返回 ListenableWorker.Result。在此时间到期后,将向 Worker 发出停止的信号.*/
class UploadFileWorker(context: Context, workerParams: WorkerParameters) :Worker(context, workerParams) {// getBackgroundExecutor().execute 该方法在后台线程中执行override fun doWork(): Result {// 获取Data携带到数据val filePath = inputData.getString("file")// 真正执行任务val fileUrl = FileUploadManager.upload(filePath)return if (TextUtils.isEmpty(fileUrl)) {Result.failure()} else {// 将拿到的数据回传出去val outputData = Data.Builder().putString("fileUrl", fileUrl).build()Result.success(outputData)}}
}// step 2.创建 Data,作为worker的输入和输出
val inputData = Data.Builder().putString("file", filePath)// 这里只添加一个file参数.build()// step 3.创建 WorkRequest。一般有两种:
// PeriodicWorkRequest 多次、周期性任务请求,不支持任务链,
// OneTimeWorkRequest 只执行一次的任务请求,支持任务链。
// 注意:每个WorkRequest都有个id标识(uuid类型)唯一请求。
以OneTimeWorkRequest为例:val workRequest = OneTimeWorkRequest.Builder(UploadFileWorker::class.java)// 指定worker.setInputData(inputData)// 设置Data(作为入参)//.setXXX ,也可以设置一大堆.build()如:
// .setConstraints(constraints)
// // 设置一个拦截器,在任务执行之前 可以做一次拦截,
// 去修改入参的数据, 然后返回新的数据交由worker使用
// .setInputMerger(null)
// //当一个任务被调度失败后,所要采取的重试策略,可以通过BackoffPolicy来执行具体的策略
// .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
// //任务被调度执行的延迟时间
// .setInitialDelay(10, TimeUnit.SECONDS)// 设置该任务尝试执行的最大次数
// .setInitialRunAttemptCount(2)
// //设置这个任务开始执行的时间
// //System.currentTimeMillis()
// .setPeriodStartTime(0, TimeUnit.SECONDS)
// //指定该任务被调度的时间
// .setScheduleRequestedAt(0, TimeUnit.SECONDS)//当一个任务执行状态变成finish时,又没有后续的观察者来消费这个结果,那么workManager会在//内存中保留一段时间的该任务的结果。超过这个时间,这个结果就会被存储到数据库中//下次想要查询该任务的结果时,会触发workManager的数据库查询操作,可以通过uuid来查询任务的状态
// .keepResultsForAtLeast(10, TimeUnit.SECONDS)而constraints可以设置:
// val constraints = Constraints()
// // 设备存储空间充足的时候才能执行. >=15%才算充足
// constraints.setRequiresStorageNotLow(true)
// // 必须在指定的网络条件下才能执行.不计流量(Wi-Fi)
// constraints.requiredNetworkType = NetworkType.UNMETERED
// // 设备电量充足. >=15%才算充足
// constraints.setRequiresCharging(true)
// // 必须在充电情况下才能执行
// constraints.setRequiresCharging(true)
// // 必须在设备空闲情况下才能被执行. 比如息屏、cpu利用率不高等。
// constraints.setRequiresDeviceIdle(true)// workManager利用contentObserver监控传递进来的这个uri对应的内容是否发生变化,// 当且仅当它发生变化了. 我们的任务才会被触发执行,以下三个api是关联的
// constraints.contentUriTriggers = null
// //设置从content变化到被执行中间的延迟时间,如果在这期间。content发生了变化,延迟时间会被重新计算//这个content就是指 我们设置的setContentUriTriggers uri对应的内容
// constraints.setTriggerContentUpdateDelay(0);
// //设置从content变化到被执行中间的最大延迟时间//这个content就是指 我们设置的setContentUriTriggers uri对应的内容
// constraints.setTriggerMaxContentDelay(0);// step 4.加入队列
// workRequests可以是一个请求,也可以是列表请求
val workContinuation = WorkManager.getInstance(this).beginWith(workRequests).//then()//.combine()
workContinuation.enqueue()// step 5.监听执行状态及结果
workContinuation.workInfosLiveData.observe(this) { workInfos ->// 注意:这里的onChanged方法会回调多次,// 状态:block/ running/ enqueued/ failed/ success/ finishvar completedCount = 0var failedCount = 0workInfos.forEach {val state = it.stateval outputData = it.outputDataval uuid = it.id// 拿到workRequest的idwhen (state) {WorkInfo.State.FAILED -> {if (uuid == coverUploadUUID) {InteractionPresenter.showToast("封面图上传失败")} else if (uuid == filePathUUID) {if (isVideo) {InteractionPresenter.showToast("视频上传失败")} else {InteractionPresenter.showToast("图片上传失败")}}failedCount++}WorkInfo.State.SUCCEEDED -> {val fileUrl = outputData.getString("fileUrl")// 拿到出参(也就是worker回传的数据)if (uuid == coverUploadUUID) {coverFileUrl = fileUrl} else if (uuid == filePathUUID) {fileUploadUrl = fileUrl}completedCount++}else -> {}}}// 若所有任务都执行完成,才能请求http发布帖子if (completedCount >= workInfos.size) {publishFeed()} else if (failedCount > 0) {dismissLoading()}
}