Android Jetpack架构组件-WorkManager使用篇

一、定义:

作为 Android Jetpack 中的新组件,WorkManager 负责用来管理后台任务,说简单点则和异步任务Task或者 Service 作用一样,都可以处理异步任务或后台任务。

核心类介绍

先来介绍下WorkManager中涉及到的想关类

  • Worker
    任务的执行者,是一个抽象类,需要继承它实现要执行的任务。

  • WorkRequest
    指定让哪个 Woker 执行任务,指定执行的环境,执行的顺序等。
    要使用它的子类 OneTimeWorkRequest 或 PeriodicWorkRequest。

  • WorkManager
    管理任务请求和任务队列,发起的 WorkRequest 会进入它的任务队列。

  • WorkStatus
    包含有任务的状态和任务的信息,以 LiveData 的形式提供给观察者,更新相关UI

二、如何使用:

依赖,在 build.gradle 添加如下依赖:

    //workmanager
    api 'androidx.work:work-runtime:2.2.0'
  • 第一步:自定义Work类,继承Worker类,这里以上传文件为示例
public class UploadFileWorker extends Worker {

    public UploadFileWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        Data inputData = getInputData();
        String filePath = inputData.getString("file");
        String fileUrl = FileUploadManager.upload(filePath);
        if (TextUtils.isEmpty(fileUrl)){
            return Result.failure();
        }else{
            Data outputData = new Data.Builder().putString("fileUrl", fileUrl)
                    .build();
            return Result.success(outputData);
        }
    }
}
  • 第二步:定义 WorkRequest
    这里我们使用OneTimeWorkRequest 这个Request,因为我们的request不是轮循的任务,故使用OneTimeWorkRequest,在MainActiivty中创建OneTimeWorkRequest如下所示:
        Data inputData = new Data.Builder()
                .putString("file", filePath)
                .build();

        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .setInputData(inputData)
                .build();

       UUID uploadRequestId= request.getId(); 
  //每一个request对应一个UUID,通过这个ID,可以监听该任务的一些状态及执行结果
  • 第三步:将定义好的WorkRequest加入到队列,如下所示,和Okhttp请求差不多
        WorkContinuation workContinuation = WorkManager.getInstance(PublishActivity.this)
                .beginWith(request);

        workContinuation.enqueue();


//通过返回的LiveData,监听任务的执行结果及UI更新
        workContinuation.getWorkInfosLiveData().observe(PublishActivity.this, new Observer<List<WorkInfo>>() {
            @Override
            public void onChanged(List<WorkInfo> workInfos) {
                //block runing enuqued failed susscess finish
                for (WorkInfo workInfo : workInfos) {
                    WorkInfo.State state = workInfo.getState();
                    Data outputData = workInfo.getOutputData();
                    UUID uuid = workInfo.getId();

                    if (state == WorkInfo.State.FAILED) {  
                        if (uuid.equals(fileUploadUUID)) {
                            showToast(getString(R.string.file_upload_original_message));
                            //TODO
                        }
                    } else if (state == WorkInfo.State.SUCCEEDED) {
                        String fileUrl = outputData.getString("fileUrl");
                        if (uuid.equals(fileUploadUUID)) {
                            fileUploadUrl = fileUrl;
                        }
                       //TODO
                    }
                }
            }
        });

三、数据交互

在实际开发业务中,执行后台任务的时候,都会传递参数,这里来具体讲解下WorkRequest是如何进行数据传递

WorkRequest已经为我们设计好了,在创建WorkRequest的时候,通过构建一个Data对象,传入必要的参数,上述案例中需要上传文件,故需传递文件对象的路径

在UploadFileWorker 中,通过 getInputData(); 方法,将得到传递的参数。类似于Bundle的使用

        Data inputData = new Data.Builder()
                .putString("file", filePath)
                .build();

        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .setInputData(inputData)
                .build();

在UploadFileWorker 中doWork()方法中,执行完异步任务后,需要传递参数的时候,也可以通过cData,将需要的参数返回出去,即Result.success(outputData)。

Data outputData = new Data.Builder().putString("fileUrl", fileUrl)
                    .build();
Result.success(outputData);

如果需要取消一个在队列中的任务,,每一个Request对应一个ID,所以可以通过 id 实现取消任务

UUID uploadRequestId= request.getId(); 
WorkManager.getInstance().cancelWorkById(request.id)

到这里,相信对WorkManager的使用有了基本的了解,接下来看WorkManager的高级用法

四、高级特性

4.1 环境约束

WorkManager 允许我们指定任务执行的环境,比如网络已连接、电量充足时等,在满足条件的情况下任务才会执行。

具体设置方法如下:


@SuppressLint("RestrictedApi") Constraints constraints = new Constraints();
        //设备存储空间充足的时候 才能执行 ,>15%
        constraints.setRequiresStorageNotLow(true);
        //必须在执行的网络条件下才能好执行,不计流量 ,wifi
        constraints.setRequiredNetworkType(NetworkType.UNMETERED);
        //设备的充电量充足的才能执行 >15%
        constraints.setRequiresBatteryNotLow(true);
        //只有设备在充电的情况下 才能允许执行
        constraints.setRequiresCharging(true);
        //只有设备在空闲的情况下才能被执行 比如息屏,cpu利用率不高
        constraints.setRequiresDeviceIdle(true);
        //workmanager利用contentObserver监控传递进来的这个uri对应的内容是否发生变化,当且仅当它发生变化了
        //我们的任务才会被触发执行,以下三个api是关联的
        constraints.setContentUriTriggers(null);
        //设置从content变化到被执行中间的延迟时间,如果在这期间。content发生了变化,延迟时间会被重新计算
//        这个content就是指 我们设置的setContentUriTriggers uri对应的内容
        constraints.setTriggerContentUpdateDelay(0);
        //设置从content变化到被执行中间的最大延迟时间  这个content就是指 我们设置的 
        constraints.setContentUriTriggers uri对应的内容
        constraints.setTriggerMaxContentDelay(0);

通过创建Constraints ,设置具体的约束条件,在创建Request的时候,调用.setConstraints(constraints)即可。

   OneTimeWorkRequest request = new OneTimeWorkRequest
                .Builder(UploadFileWorker.class)
                .setInputData(inputData)
                .setConstraints(constraints)
//                .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时,又没有后续的观察者来消费这个结果,难么workamnager会在
//                //内存中保留一段时间的该任务的结果。超过这个时间,这个结果就会被存储到数据库中
//                //下次想要查询该任务的结果时,会触发workmanager的数据库查询操作,可以通过uuid来查询任务的状态
//                .keepResultsForAtLeast(10, TimeUnit.SECONDS)
                .build();

设置完constraints的Request,则加入到队列中的时候 workContinuation.enqueue();不会立即执行,只有当constraints中的条件满足的时候,才会执行该request 。

4.2 强大的生命力

这是 WorkManager 的另一个特点,一旦发起一个任务,任务是可以保证一定会被执行的,就算退出应用,甚至重启手机都阻止不了他。但可能由于添加了环境约束等原因,它执行的时间是不确定的。

当应用正在运行时,它会在当前的进程中启用一个子线程执行。应用没有运行的情况下启用,它则会自己选择一种合适的方式在后台运行。具体是什么方式和 Android 的版本和依赖环境有关:

image.png

4.3 任务链:

WorkManager 允许我们按照一定的顺序执行任务,比如我想 A、B、C 三个任务按先后顺序执行:


image.png

则可以使用如下代码,即可实现:

         OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        
        WorkManager.getInstance(PublishActivity.this).beginWith(requestA).then(requestB).then(requestC).enqueue();

这样的话,上一个任务的 outputData 会成为下一个任务的 inputData。

再更更复杂一点,如果我想这样:

image.png

        OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        OneTimeWorkRequest requestD = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();
        OneTimeWorkRequest requestE = new OneTimeWorkRequest.Builder(UploadFileWorker.class)
                .build();

        WorkContinuation chainA = WorkManager.getInstance(PublishActivity.this).beginWith(requestA).then(requestB);
        WorkContinuation chainB = WorkManager.getInstance(PublishActivity.this).beginWith(requestC).then(requestD);

        List<WorkContinuation> chains =new ArrayList<>();
        chains.add(chainA);
        chains.add(chainB);
        
     WorkContinuation.combine(chains).then(requestE).enqueue();

4.5 任务唯一性

很多情况下,我们希望在任务队列里,同一个任务只存在一个,避免任务的重复执行,这时候可以用到 beginUniqueWork 这个方法:

WorkManager.getInstance()
        .beginUniqueWork("unique", ExistingWorkPolicy.REPLACE, request)
        .enqueue()

需要传入一个任务的标签,和重复任务的执行方式,可取值如下:


image.png

但这种方式也是只支持 OneTimeWorkRequest。如果是 PeriodicWorkRequest,我想到的办法是每次执行之前,根据标签去取消已有的任务。

4.6 使用场景

很明显,WorkManager 区别于异步任务,它更像是一个 Service。基本上,WorkManager 能做的,Service 也能做,我并没有想到有什么情况是非用 WorkManger 不可的。

但反观 Service,泛滥的 Service 后台任务可能是引起 Android 系统卡顿的主要原因,这几年 Google 也对 Service 也做了一些限制。

到这里,WorkManager的基本使用就介绍完了,文章中的示例代码已上Jetpack

该仓库为演示Jetpack的组件的仓库,分别对Lifecyele、LiveData、ViewModel、Room的介绍和使用

详细介绍文章

项目目录结构为如下

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