5月8号, I/O大会上推出了Architeture新组件WorkManager。 由于Android版本的不断更新,后台任务的处理变得越来越复杂。 因此,Google发布了 WorkManager(作为JetPack的一部分)来帮助开发者解决这一难题。
一. 背景
首先介绍几个常见的Android场景:
Service:当你的应用退到后台,但是它需要继续执行任务时,你就必须使用Service。
举个例子:更新提醒,推送sdk
做Android开发的很是熟悉,想要用户及时更新,需要给用户推送新版本,即客户端监听服务端下发的推送信息。一般情况下,应用使用service常驻后台进程,不乏一些为了保证及时更新而采用一系列的进程保活手法,例如双service守护等,总之,service在后台肆无忌惮的运行着。(内存消耗,电量消耗....)
后台网络请求任务:
网络请求在遇到失败后一般会进行重试逻辑,但在重试的过程中应用进入了后台,比如支付过程中,输入完密码没信号了,用户给进程杀死了。关键性信息不稳定会给用户带来痛不欲生的效果(我的钱到底扣了没有)。这个过程肯定不能够使用Service了。Google给我们提供了JobScheduler>=21,但是JobScheduler在API>=23的系统上使用,在API 21&22的系统里JobScheduler还存在一个重大bug(https://github.com/yigit/android-priority-jobqueue/issues/202):
友情提醒:Android Oreo (API 26) 之后,Android 8.0在某些不被允许创建后台服务的场景下,调用了Service的startService()方法,该方法会抛出IllegalStateException。(可以理解为Service时代)
另外请大家再次注意:2019年起: 在每一次发布新版本的Android系统之后,所有新开发以及待更新的应用都必须在一年内将target API level调整至对应的系统版本甚至更高。
二.WorkManager详解
1 WorkManager简介
使用场景总结::当应用完全退出后,需要管理要在后台工作的任务
On the last Google I/O Android framework, the team announced WorkManager:
WorkManager aims to simplify the developer experience by providing a first-class API for system-driven background processing. It is intended for background jobs that should run even if the app is no longer in the foreground. Where possible, it uses JobScheduler or Firebase JobDispatcher to do the work; if your app is in the foreground, it will even try to do the work directly in your process.
注意[翻译]:WorkManager适用于那些即使应用程序退出,系统也能够保证这个任务正常运行的场景,比如将应用程序数据上传到服务器。它不适用于应用进程内的后台工作,如果应用进程消失,就可以安全地终止,对于这种情况,推荐你使用线程池
2 WorkManager库的架构
从图中可以看出,WorkManager执行队列中包含JobScheduler,JobDispatcher,Executor,AlarmManager。
WorkManager在底层会根据你的设备情况进行有选择的调度。但这跟AsyncTask, ThreadPool, RxJava这调度管理工具不同的是,WorkManager能帮助你在应用中在后台线程干活,及时进程被杀死活或关闭。但上述这些工具在进程结束后及结束所有任务,其实Google自己也说了:”WorkManager并不是为了那种在应用内的后台线程而设计出来的. 这种需求你应该使用ThreadPool”。
3 WorkManager API的特点
根据官方文档显示为以下几点:
1. 易于调度
WorkManager API可以轻松创建可延迟的异步任务,并允许您指定应该何时执行。
你可以创建任务并将该任务交给WorkManager,以便立即或在设备处于特定条件下运行该任务。
WorkManager提供了保证,即使您的应用程序强制退出或设备重新启动,你的任务仍会在特定条件匹配时执行。
2. 易于取消
WorkManager给每个任务分配了UUID,使用这个唯一的ID你就可以随时取消任务。
3.易于查询
你可以使用分配给每个任务的唯一标识来询问任务的状态,无论是正在运行,挂起还是已完成。
WorkManager API超越了任务的当前状态,允许任务一键值对格式返回数据。
WorkManager使用LiveData来干会任务的数据和状态,所以,你的Activity可以观察这个LiveData,并且每当任务完成时都会得到通知。
4.支持Android所有版本
WorkManager支持Android API 14及以上
WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行你的任务。
如果应用程序正在运行,WorkManager将创建新的线程来运行任务。
如果应用程序没有运行,那么他将使用JobScheduler API或Firebase Job APIs调度者或Alarm manager API运行调度任务。
4 WorkManager使用方法
Work manager APIs建立在几个类上,你必须继承一些抽象类来安排任务。
Worker:在WorkManager世界中,Worker等同于需要在后台执行的任务或作业。这是一个抽象类。你需要继承它。您的Worker类包含有关如何执行该任务的信息,但它没有关于何时运行的信息。
WorkRequest:它代表了工作调度请求。每个工作必须在安排工作之前创建工作请求。 WorkRequest将包含工作的唯一标识,约束条件说明应在哪种情况下执行任务。这是一个抽象类。该库提供了这个类的两个直接子类:OneTimeWorkRequest和PeriodicWorkRequest。
WorkRequest.Builder:用于创建WorkRequest对象的辅助类,同样,我们要使用它的一个子类,OneTimeWorkRequest.Builder 和PeriodicWorkRequest.Builder 。
Constraints:指定任务在何时运行(例如,“仅在连接到网络时”)。我们可以通过Constraints.Builder 来创建Constraints对象,并在创建WorkRequest之前,将 Constraints 对象传递给 WorkRequest.Builder。
WorkManager:它是基于WorkRequest中定义的约束来管理和调度任务的类。
WorkStatus:这个类包装了任何work请求的状态,你可以通过唯一的id来查询任何work的状态。
基本工作流程如图所示:
gradle依赖:
https://developer.android.com/topic/libraries/architecture/adding-components
WorkManager类已经在 androidx.work 包中,但目前依赖于 Support Library 27.1 以及相关的 Arch组件版本,将来会发布带有 AndroidX 依赖项的WorkManager版本。
dependencies {
def work_version = "1.0.0-alpha01"
implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
// optional - Firebase JobDispatcher support
implementation "android.arch.work:work-firebase:$work_version"
// optional - Test helpers
androidTestImplementation "android.arch.work:work-testing:$work_version"}
基本工作流程:
首先,我们需要定义自己的Worker类,然后重写此类的 doWork() 方法,我们需要指定Worker类如何执行这个操作,但是不应该出现任何关于任务在何时运行的信息。
class CompressWorker : Worker() {
override fun doWork(): WorkerResult { // Do the work here--in this case, compress the stored images.
// In this example no parameters are passed; the task is
// assumed to be "compress the whole library."
myCompress() // Indicate success or failure with your return value:
return WorkerResult.SUCCESS // (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
}
接下来,我们要创建一个基于此Worker 的OneTimeWorkRequest对象,然后使用WorkManager让这个任务入队:
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build()
WorkManager.getInstance().enqueue(compressionWork)
WorkManager会选择适当的时间运行这个任务,平衡诸如系统负载,设备是否插入等考虑因素。在多数情况下,如果我们没有指定任何约束条件,WorkManager会立即运行我们的任务。如果我们需要检查任务的状态,我们可以通过获取合适的LiveData <WorkStatus>的句柄来获取WorkStatus对象。例如,如果我们想检查任务是否完成,可以使用如下代码:
WorkManager.getInstance().getStatusById(compressionWork.id)
.observe(lifecycleOwner, Observer { workStatus -> // Do something with the status
if (workStatus != null && workStatus.state.isFinished) { // ...
}
})
任务约束条件:
如果我们愿意,我们还可以限制任务运行的时间。例如,我们可能想要指定该任务只在设备闲置并接通电源时运行。在这种情况下,我们需要创建一个OneTimeWorkRequest.Builder对象,并使用这个构造器创建实际的OneTimeWorkRequest:
// Create a Constraints that defines when the task should runval myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true) // Many other constraints are available, see the
// Constraints.Builder reference
.build()
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
.setConstraints(myConstraints)
.build()
然后像之前代码一样将新的OneTimeWorkRequest对象传递给WorkManager.enqueue(), WorkManager在查找运行任务的时间时会考虑我们的约束条件。
取消任务:
当我们将任务入列后,我们还可以取消这个任务。要取消任务,我们需要这个任务的Work ID,当然Work ID可以从WorkRequest对象中获取。例如,以下代码将取消上一节中的compressionWork请求:
UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelByWorkId(compressionWorkId);
WorkManager 会尽最大努力取消任务,但实质上这是不确定的 - 当我们尝试取消任务时,任务可能已经运行或完成。
WorkManager还提供方法来取消 唯一工作序列(在高级用法中会有所涉及)中的所有任务,或尽最大努力的取消具有指定标记的所有任务。
详细使用方法请参考:官网https://developer.android.com/topic/libraries/architecture/workmanager
googlecodelabs关于WorkManager应用的使用方法Demo https://github.com/googlecodelabs/android-workmanager
特别鸣谢:
https://juejin.im/post/5b04d064f265da0b80711759
https://android.jlelse.eu/exploring-jetpack-scheduling-tasks-with-work-manager-fba20d7c69bf