WorkManager _Android新架构组件

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容