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 requests = new ArrayList<>();
    WorkContinuation workContinuation = WorkManager.getInstance(getContext()).beginWith(workRequests);
    workContinuation.enqueue();

    workContinuation.getWorkInfosLiveData().observe(getViewLifecycleOwner(), new Observer>() {
        @Override
        public void onChanged(List 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. 执行。

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

你可能感兴趣的:(Android Jetpack WorkManager)