上一次咱们对Paging的应用进行了一次全面的分析,这一次咱们来聊聊WorkManager。java
若是你对Paging还未了解,推荐阅读这篇文章:android
Paging在RecyclerView中的应用,有这一篇就够了git
原本这一篇文章上周就可以发布出来,但我写文章有一个特色,都会结合具体的Demo来进行阐述,而WorkManager的Demo早就完成了,只是要结合文章一块儿阐述实在须要时间,上周自身缘由也就延期了,想一想仍是写代码容易啊...😿😿github
哎呀很少说了,进入正题!数据库
WorkManager是什么?官方给的解释是:它对可延期任务操做很是简单,同时稳定性很是强,对于异步任务,即便App退出运行或者设备重启,它都可以很好的保证任务的顺利执行。api
因此关键点是简单与稳定性。bash
对于日常的使用,若是一个后台任务在执行的过程当中,app忽然退出或者手机断网,这时后台任务将直接终止。网络
典型的场景是:App的关注功能。若是用户在弱网的状况下点击关注按钮,此时用户因为某种缘由立刻退出了App,但关注的请求并无成功发送给服务端,那么下次用户再进入时,拿到的仍是以前未关注的状态信息。这就产生了操做上的bug,下降了用户的体验,增长了用户没必要要的操做。app
那么该如何解决呢?很简单,看WorkManager的定义,使用WorkManager就能够轻松解决。这里就再也不拓展实现代码了,只要你继续看完这篇文章,你就能轻松实现。框架
固然你不使用WorkManager也能实现,这就涉及到它的另外一个好处:简单。若是你不使用WorkManager,你就要对不一样API版本进行区分。
val service = ComponentName(this, MyJobService::class.java)
val mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val builder = JobInfo.Builder(jobId, serviceComponent)
.setRequiredNetworkType(jobInfoNetworkType)
.setRequiresCharging(false)
.setRequiresDeviceIdle(false)
.setExtras(extras).build()
mJobScheduler.schedule(jobInfo)
复制代码
经过JobScheduler来建立一个Job,一旦所设的条件达到,就会执行该Job。但JobScheduler是在API21加入的,同时在API21&22有一个系统Bug
这就意味着它只能用在API23及以上的版本
if (Build.VERSION.SDK_INT >= 23) {
// use JobScheduler
}
复制代码
既然只能API23及以上才能使用JobScheduler,那么在API23如下又该如何呢?
这时对于API23如下,可使用AlarmManager来进行任务的执行,同时结合BoradcastReceiver来进行任务的条件监听,例如网络的链接状态、设备的启动等。
看到这里是否是开始头大了呢,咱们开始的目的只是想作一个稳定性的后台任务,最后发现竟然还要进行版本兼容。兼容性与实现性进一步加大。
那么有没有统一的实现方式呢?固然有,它就是WorkManager,它的核心原理使用的就是上面所分析的结合体。
他会结合版本自动使用最佳的实现方式,同时还会提供额外的便利操做,例如状态监听、链式请求等等。
WorkManager的使用,我将其分为如下几步:
下面咱们来经过Demo逐步了解。
WorkManager每个任务都是由Work构成,因此Work是任务具体执行的核心所在。既然是核心所在,你可能会认为它会很是难实现,但偏偏相反,它的实现很是简单,你只需实现它的doWork方法便可。例如咱们来实现一个清除相关目录下的.png图片的Work
class CleanUpWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val outputDir = File(applicationContext.filesDir, Constants.OUTPUT_PATH)
if (outputDir.exists()) {
val fileLists = outputDir.listFiles()
for (file in fileLists) {
val fileName = file.name
if (!TextUtils.isEmpty(fileName) && fileName.endsWith(".png")) {
file.delete()
}
}
}
return Result.success()
}
}
复制代码
全部代码都在doWork中,实现逻辑也很是简单:找到相关目录,而后逐一判断目录中的文件是否为.png图片,若是是就删除。
以上是逻辑代码,关键点是返回值Result.success(),它是一个Result类型,可用值有三个
对于success与failure,它还支持传递Data类型的值,Data内部是一个Map来管理的,因此对于kotlin能够直接使用workDataOf
return Result.success(workDataOf(Constants.KEY_IMAGE_URI to outputFileUri.toString()))
复制代码
它传递的值将放入OutputData中,能够在链式请求中传递,与最终的响应结果获取。其实本质是WorkManager结合了Room,将数据保存在数据库中。
这一步要点就是这么多,下面进入下一步。
WorkManager主要是经过WorkRequest来配置任务的,而它的WorkRequest种类包括:
首先OneTimeWorkRequest是做用于一次性任务,即任务只执行一次,一旦执行完就自动结束。它的构建也很是简单:
val cleanUpRequest = OneTimeWorkRequestBuilder<CleanUpWorker>().build()
复制代码
这样就配置了与CleanUpWorker相关的WorkRequest,并且是一次性的。
在配置WorkRequest的过程当中咱们还能够对其添加别的配置,例如添加tag、传入inputData与添加constraint约束条件等等。
val constraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val blurRequest = OneTimeWorkRequestBuilder<BlurImageWorker>()
.setInputData(workDataOf(Constants.KEY_IMAGE_RES_ID to R.drawable.yaodaoji))
.addTag(Constants.TAG_BLUR_IMAGE)
.setConstraints(constraint)
.build()
复制代码
添加tag是为了打上标签,以便后续获取结果;传入的inputData能够在BlurImageWork中获取传入的值;添加网络链接constraint约束条件,表明只有在网络链接的状态下才会触发该WorkRequest。
而BlurImageWork的核心代码以下:
override suspend fun doWork(): Result {
val resId = inputData.getInt(Constants.KEY_IMAGE_RES_ID, -1)
if (resId != -1) {
val bitmap = BitmapFactory.decodeResource(applicationContext.resources, resId)
val outputBitmap = apply(bitmap)
val outputFileUri = writeToFile(outputBitmap)
return Result.success(workDataOf(Constants.KEY_IMAGE_URI to outputFileUri.toString()))
}
return Result.failure()
}
复制代码
在doWork中,经过InputData来获取上述blurRequest中传入的InputData数据。而后经过apply来处理图片,最后使用writeToFile写入到本地文件中,并返回路径。
因为篇幅有限,这里就不一一展开,感兴趣的能够查看源码
PeriodicWorkRequest是能够周期性的执行任务,它的使用方式与配置和OneTimeWorkRequest一致。
val constraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
// at least 15 minutes
mPeriodicRequest = PeriodicWorkRequestBuilder<DataSourceWorker>(15, TimeUnit.MINUTES)
.setConstraints(constraint)
.addTag(Constants.TAG_DATA_SOURCE)
.build()
复制代码
不过须要注意的是:它的周期间隔最少为15分钟。
上面咱们已经将WorkRequest配置好了,剩下要作的是将其加入到work工做链中进行执行。
对于单个的WorkRequest,能够直接经过WorkManager的enqueue方法
private val mWorkManager: WorkManager = WorkManager.getInstance(application)
mWorkManager.enqueue(cleanUpRequest)
复制代码
若是你想使用链式工做,只需调用beginWith或者beginUniqueWork方法便可。其实它们本质都是实例化了一个WorkContinuationImpl,只是调用了不一样的构造方法。而最终的构造方法为:
WorkContinuationImpl(@NonNull WorkManagerImpl workManagerImpl,
String name,
ExistingWorkPolicy existingWorkPolicy,
@NonNull List<? extends WorkRequest> work,
@Nullable List<WorkContinuationImpl> parents) { }
复制代码
其中beginWith方法只需传入WorkRequest
val workContinuation = mWorkManager.beginWith(cleanUpWork)
复制代码
beginUniqueWork容许咱们建立一个独一无二的链式请求。使用也很简单:
val workContinuation = mWorkManager.beginUniqueWork(Constants.IMAGE_UNIQUE_WORK, ExistingWorkPolicy.REPLACE, cleanUpWork)
复制代码
其中第一个参数是设置该链式请求的name;第二个参数ExistingWorkPolicy是设置name相同时的表现,它三个值,分别为:
而不论是beginWith仍是beginUniqueWork,它都会返回WorkContinuation对象,经过该对象咱们能够将后续任务加入到链式请求中。例如将上面的cleanUpRequest(清除)、blurRequest(图片模糊处理)与saveRequest(保存)串行起来执行,实现以下:
val cleanUpRequest = OneTimeWorkRequestBuilder<CleanUpWorker>().build()
val workContinuation = mWorkManager.beginUniqueWork(Constants.IMAGE_UNIQUE_WORK, ExistingWorkPolicy.REPLACE, cleanUpRequest)
val blurRequest = OneTimeWorkRequestBuilder<BlurImageWorker>()
.setInputData(workDataOf(Constants.KEY_IMAGE_RES_ID to R.drawable.yaodaoji))
.addTag(Constants.TAG_BLUR_IMAGE)
.build()
val saveRequest = OneTimeWorkRequestBuilder<SaveImageToMediaWorker>()
.addTag(Constants.TAG_SAVE_IMAGE)
.build()
workContinuation.then(blurRequest)
.then(saveRequest)
.enqueue()
复制代码
除了串行执行,还支持并行。例如将cleanUpRequest与blurRequest并行处理,完成以后再与saveRequest串行
val left = mWorkManager.beginWith(cleanUpRequest)
val right = mWorkManager.beginWith(blurRequest)
WorkContinuation.combine(arrayListOf(left, right))
.then(saveRequest)
.enqueue()
复制代码
须要注意的是:若是你的WorkRequest是PeriodicWorkRequest类型,那么它不支持创建链式请求,这一点须要注意了。简单的理解,周期性的任务原则上是没有终止的,是个闭环,也就不存在所谓的链了。
这就到最后一步了,获取响应结果WorkInfo。WorkManager支持两种方式来获取响应结果
同时返回的WorkInfo还支持LiveData数据格式。
例如,如今咱们要监听上述blurRequest与saveRequest的状态,使用tag来获取:
// ViewModel
internal val blurWorkInfo: LiveData<List<WorkInfo>>
get() = mWorkManager.getWorkInfosByTagLiveData(Constants.TAG_BLUR_IMAGE)
internal val saveWorkInfo: LiveData<List<WorkInfo>>
get() = mWorkManager.getWorkInfosByTagLiveData(Constants.TAG_SAVE_IMAGE)
// Activity
private fun addObserver() {
vm.blurWorkInfo.observe(this, Observer {
if (it == null || it.isEmpty()) return@Observer
with(it[0]) {
if (!state.isFinished) {
vm.processEnable.value = false
} else {
vm.processEnable.value = true
val uri = outputData.getString(Constants.KEY_IMAGE_URI)
if (!TextUtils.isEmpty(uri)) {
vm.blurUri.value = Uri.parse(uri)
}
}
}
})
vm.saveWorkInfo.observe(this, Observer {
saveImageUri = ""
if (it == null || it.isEmpty()) return@Observer
with(it[0]) {
saveImageUri = outputData.getString(Constants.KEY_SHOW_IMAGE_URI)
vm.showImageEnable.value = state.isFinished && !TextUtils.isEmpty(saveImageUri)
}
})
......
......
}
复制代码
再来看一个经过id获取的:
// ViewModel
internal val dataSourceInfo: MediatorLiveData<WorkInfo> = MediatorLiveData()
private fun addSource() {
val periodicWorkInfo = mWorkManager.getWorkInfoByIdLiveData(mPeriodicRequest.id)
dataSourceInfo.addSource(periodicWorkInfo) {
dataSourceInfo.value = it
}
}
// Activity
private fun addObserver() {
vm.dataSourceInfo.observe(this, Observer {
if (it == null) return@Observer
with(it) {
if (state == WorkInfo.State.ENQUEUED) {
val result = outputData.getString(Constants.KEY_DATA_SOURCE)
if (!TextUtils.isEmpty(result)) {
Toast.makeText(this@OtherWorkerActivity, result, Toast.LENGTH_LONG).show()
}
}
}
})
}
复制代码
结合LiveData使用是否是很简单呢? WorkInfo获取的本质是经过操做Room数据库来获取。在文章的Work部分已经提到,在执行完Work任务以后传递的数据将会保存到Room数据库中。
因此WorkManager与AAC的结合度很是高,目的也是致力于为咱们开发者提供一套完整的框架,同时也说明Google对AAC框架的重视。
若是你还未了解AAC,推荐你阅读我以前的文章
最后咱们将上面的几个WorkRequest结合起来执行,看下它们的最终效果:
经过这篇文章,但愿你可以熟悉运用WorkManager。若是这篇文章对你有所帮助,你能够顺手点赞、关注一波,这是对我最大的鼓励!
该库的目的是结合详细的Demo来全面解析Android相关的知识点, 帮助读者可以更快的掌握与理解所阐述的要点