Android Jetpack WorkManager

导语

Jetpack简介及其它组件文章
还在为发布动态流程过于复杂而心烦吗?还在抱怨弱网关注失败吗?还在担心重要任务丢失吗?不用担心,WorkManager帮你解决这一切。

主要内容

  • 什么是WorkManager
  • 简单使用WorkManager
  • WorkManager的使用场景
  • WorkManager的原理解析

具体内容

什么是WorkManager

WorkManager可以自动维护后台任务的执行时机,执行顺序,执行状态。

执行时机(带约束条件的任务)

创建任务的执行时机,立刻执行还是设备空闲的时候执行,或者设备的充电量满足的时候执行,或者在Wifi下才会执行,或者是在设备存储空间足够的情况下才能执行。

执行顺序

任务执行可以有依赖关系,比如执行完任务A才能执行任务B,任务B执行完才能执行任务C。

执行状态

任务执行的每一个状态都会回调给我们,成功,取消,失败。

特点
  • 保证任务一定会被执行。
  • 合理使用设备资源。
与Service对比
  • WorkManager同样是可以应用于异步任务的并发场景的。
  • WorkManager更轻便,更加省电。理论上Service能做的事情,WorkManager都可以做。Android8.0以后,Android对于后台service管理的更加严格,应用在后台启动的服务必须是前台服务,否则会导致应用crash。
  • WorkManager是支持多任务的链式调用,支持多任务的串型依赖,还支持和LiveData相搭配使用,所以很容易观察任务执行的如何。
任务场景
任务场景
WorkContinuation left, right; 
left = workManager.beginWith(A).then(B); 
right = workManager.beginWith(C).then(D); 
WorkContinuation.combine(Arrays.asList(left, right)).then(E).Enqueue(); 
任务状态
任务状态
  1. BLOCKED:表示当前有尚未完成的前提性工作
  2. ENQUEUED:表示工作能够在满足约束和时机条件后可立即执行
  3. RUNNING:表示工作器在活跃的执行
  4. SUCCEEDED:工作期返回成功的状态,此时是一种终止的状态,且只有OneTimeWorkRequest可以进入这种状态
  5. CANCELLED:当明确取消尚未终止的WorkRequest时,它会进入CANCELLED状态,所有依赖工作也会被标记为CANCELLED状态,且不会运行
  6. FAILED:工作期返回失败的状态,此时是一种终止的状态,所有依赖的工作也会被标记为FAILED状态,且只有OneTimeWorkRequest可以进入这种状态
任务控制
  • cancelWorkById(UUID)——通过ID取消单个任务。
  • cancelAllWorkByTag(String)——通过Tag取消所有任务。
  • cancelUniqueWork(String)——通过名字取消唯一任务。
类关系
  • Worker:任务的执行者,是一个抽象类,需要继承实现要执行的任务。
  • WorkRequest:指定让哪个Woker执行任务,指定执行的环境,执行的顺序等,要使用它的子类OneTimeWorkRequest或PeriodicWorkRequest。
  • WorkManager:管理任务请求和任务队列,发起WorkRequest会进入它的任务队列。
  • WorkStatus:包含有任务的状态和任务信息,以LiveData的形式提供给观察者。

简单使用WorkManager

创建FollowWorker类,doWork() 方法是具体要执行的任务。

public class FollowWorker extends Worker {
    public FollowWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        Log.d("WorkManager", "开始关注");

        Data inputData = getInputData();
        String uid = inputData.getString("uid");
        if (TextUtils.isEmpty(uid)) {
            return Result.failure();
        } else {
            //假装这里是Http同步请求
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String relationship = CommonConstants.RELATIONSHIP.FOLLOWED_HIM;
            Data outputData = new Data.Builder()
                    .putString("relationship", relationship)
                    .build();

            Log.d("WorkManager", "关注成功");

            return Result.success(outputData);
        }
    }
}

在需要关注的地方使用FollowWork。

private UUID followUUID;

private void toFollow(String uid) {
    OneTimeWorkRequest request = getOneTimeWorkRequest(uid);
    followUUID = request.getId();

    enqueue(request);
}

private OneTimeWorkRequest getOneTimeWorkRequest(String uid) {
    Data inputData = new Data.Builder()
            .putString("uid", uid)
            .build();

//        @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就是指 我们设置的setContentUriTriggers uri对应的内容
//        constraints.setTriggerMaxContentDelay(0);
    OneTimeWorkRequest request = new OneTimeWorkRequest
            .Builder(FollowWorker.class)
            .setInputData(inputData)
//                .setConstraints(constraints)
//                //设置一个拦截器,在任务执行之前 可以做一次拦截,去修改入参的数据然后返回新的数据交由worker使用
//                .setInputMerger(null)
//                //任务被调度执行的延迟时间
//                .setInitialDelay(10, TimeUnit.SECONDS)
//                //当一个任务被调度失败后,所要采取的重试策略,可以通过BackoffPolicy来执行具体的策略   线性、指数性
//                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
//                //设置该任务尝试执行的最大次数
//                .setInitialRunAttemptCount(2)
//                //设置这个任务开始执行的时间
//                //System.currentTimeMillis()
//                .setPeriodStartTime(0, TimeUnit.SECONDS)
//                //指定该任务被调度的时间  同上
//                .setScheduleRequestedAt(0, TimeUnit.SECONDS)
//                //当一个任务执行状态变成finish时,又没有后续的观察者来消费这个结果,那么workamnager会在
//                //内存中保留一段时间的该任务的结果。超过这个时间,这个结果就会被存储到数据库中
//                //下次想要查询该任务的结果时,会触发workmanager的数据库查询操作,可以通过uuid来查询任务的状态
//                .keepResultsForAtLeast(10, TimeUnit.SECONDS)
            .build();
    return request;
}

private void enqueue(OneTimeWorkRequest workRequests) {
//        List<OneTimeWorkRequest> requests = new ArrayList<>();
    WorkContinuation workContinuation = WorkManager.getInstance(getContext()).beginWith(workRequests);
    workContinuation.enqueue();

    workContinuation.getWorkInfosLiveData().observe(getViewLifecycleOwner(), 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==coverUploadUUID)是错的
                    if (uuid.equals(followUUID)) {
                        Log.d("WorkManager", "回调关注失败");
                    }
                } else if (state == WorkInfo.State.SUCCEEDED) {
                    Log.d("WorkManager", "回调关注成功");

                    String relationship = outputData.getString("relationship");
                    if (uuid.equals(followUUID)) {
                        Log.d("WorkManager", "回调关注结果 = " + relationship);
                    }
                }
            }
        }
    });
}

WorkManager的使用场景

  • 关注用户,加入基地等用户重要操作,不想被丢失。
  • 发布动态,发布帖子等串行并行操作。
  • 抛埋点,提升埋点准确率。
  • 开机视频广告在wifi下预加载。
  • 开机启动优化。
  • 心跳请求。

WorkManager的原理解析

WorkManager的初始化
  1. WorkManager的初始化是在app冷启动后,由WorkManagerInitializer这个ContentProvider执行的。
  2. 初始化过程包含了ConfigurationWorkManagerTaskExecutorWorkDatabaseSchedulersProcessor等的初始化过程。
  3. Schedulers有两个。
    • (1) GreedyScheduler:执行没有任何约束的非周期性的任务。
    • (2) SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler:执行周期性或者有约束性的任务。优先返回SystemJobScheduler,在build version小于23的情况下先尝试返回GcmBasedScheduler,若返回为空再返回SystemAlarmScheduler
  4. 初始化的最后,会根据情况找到需要被执行的任务进行调度执行。
WorkRequest的创建

WorkRequest的创建是为了持有三个重要的成员变量。分别是:

  1. mId:由UUID生成的任务id。
  2. mWorkSpec:每个任务的属性。
  3. mTags:每个任务的标签。
非约束条件任务的执行过程
  1. WorkManager执行了enqueue()后,创建WorkContinuationImpl对象执行enqueue()方法。
  2. WorkContinuationImpl持有的EnqueueRunnable对象将任务添加到db,并交给Schedulers去调度。
  3. Schedulers将任务交给每一个Scheduler去处理。在我们的示例中,GreedyScheduler会先处理这个任务。
  4. GreedyScheduler经过一系列判断后,调用WorkManagerstartWork()方法执行这种一次性,非延迟,无约束的任务。
  5. WorkManager持有的StartWorkRunnable对象会将任务交给Processor去处理,执行startWork()方法。
  6. Processor创建一个WorkerWrapper对象,由它去调用Worker的startWork()方法,执行我们自定义worker的任务,并返回相应的result
  7. 任务完成后,WorkerWrapper会根据result对任务状态,db等进行更新,然后schedule下一个任务。
带约束的任务的执行过程
  1. 创建JobService。
  2. 配置JobInfo。
  3. 执行。

更多内容戳这里(整理好的各种文集)

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

推荐阅读更多精彩内容