Android Jetpack架构组件之WorkManager入

作者: 斌林诚上 | 来源:发表于2020-03-03 20:44 被阅读0次

    ——你可以失望,但不能绝望。累的时候可以慢一点,千万不要后退,你还没有拼劲全力。怎么知道没有奇迹。

    前言

    ——最近抽空又学了一个Jetpack组件 —— WorkManager,由于工作繁忙,要学的东西还有很多,任务重,时间紧。虽然只学到了点皮毛,但是还是要花点时间做个总结。因为人们常说:学而不思则罔,思而不学则殆。不思不学则网贷。所以要想致富,好的学习方法是必要的。也跟大家分享一下所学的知识。少走的点弯路。

    一、简介

    (1)是什么

    —— WorkManager是Android Jetpack 中管理后台任务的组件。
    —— 常见的使用场景:1.向后端服务发送日志或分析数据 2.定期将应用数据与服务器同步

    (2)有什么用

    —— 使用 WorkManager API 可以轻松地调度后台任务。可延迟运行(即不需要立即运行)并且在应用退出(进程未关闭)或应用重启时能够可靠运行的任务。

    (3)有什么优点

    • 1.兼容JobScheduler与BroadcastReceiver 和 AlarmManager
    • 2.工作约束满足多种情况
    • 3.可使用一次性或周期性执行的任务
    • 4.监控和管理计划任务
    • 5.提供API将任务链接起来
    • 6.遵循低电耗模式等省电功能

    二、基本使用

    (1)添加依赖

     implementation android.arch.work:work-runtime:1.0.1
    

    (2)创建后台任务(自定义类 继承 Worker 并重写doWork())

    public static class MyWorker extends Worker {
    
        public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
            super(context, params);
        }
    
        @Override
        public Result doWork() {
            return Result.success();//返回成功
    //      return Result.failure();//返回失败
    //      return Result.retry();//重试
        }
    }
    

    (3)创建请求

    // 对于一次性 WorkRequest,请使用 OneTimeWorkRequest,对于周期性工作,请使用 PeriodicWorkRequest.
    // 构建一次性请求
    // OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class).build();
    // 构建周期性请求
    // PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class,1, TimeUnit.HOURS).build();
    

    (4)执行请求(如果没有设置约束条件则会立即执行)

    WorkManager.getInstance().enqueue(request);
    

    (5)取消和停止工作

    WorkManager.getInstance().cancelWorkById(request.getId());
    

    总结:1.创建任务——2.配置请求——3.执行请求

    三、进阶

    (1)进阶1:构建约束条件:

    Uri uri = Uri.parse("xxxxx");
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED) //指定需要在有网的情况下
            .setRequiresBatteryNotLow(true)//指定电量在可接受范围内运行
            .setRequiresStorageNotLow(true)//指定在存储量在可接受范围内运行
            .addContentUriTrigger(uri,true)//当Uri发生变化的时候运行
            .setRequiresDeviceIdle(true)//当设备处于空闲状态时运行
            .setRequiresCharging(true)//当设备处于充电状态时运行
            .build();
    //在请求
    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
                    .setConstraints(constraints)//添加约束
                    .build();
    //当满足约束条件后才会执行该任务
    WorkManager.getInstance().enqueue(request);
    

    (2)进阶2:延迟执行

    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
                    .setInitialDelay(1,TimeUnit.HOURS)//延迟1小时执行
                    .build();
    

    (3)进阶3:设置回退/重试的策略 当doWork()返回 Result.retry()时启用 指定重试间隔时长

    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
                    //第一个参数:设置策略模式。
                    //第二个参数:设置第一次重试时长
                    //第三个参数:设置时间单位
                    .setBackoffCriteria(BackoffPolicy.LINEAR,
                            OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                            TimeUnit.MILLISECONDS)
                    .build();
    

    (4)进阶4:传入参数/标记请求任务

    Data imageData = new Data.Builder()
            .putString(DateKey, "开始执行")
            .build();
    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
                    //传入参数
                    .setInputData(imageData)
                    .build();
    @Override
    public Result doWork() {
        //获取传入的参数
        String data = getInputData().getString(DateKey);
        LogUtils.e("data:"+data);
        //创建输出结果
        Data outputData = new Data.Builder()
                .putString(DateKey,"已经开始充电")
                .build();
        return Result.success(outputData);
    }
    

    (5)进阶5:标记请求任务

    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
                    .addTag(TAG)
                    .build();
                    //取消使用特定标记的所有任务
    //        WorkManager.getInstance().cancelAllWorkByTag(TAG);
    //会返回 LiveData 和具有该标记的所有任务的状态列表
    //        WorkManager.getInstance().getWorkInfosByTagLiveData(TAG); 
    

    (6)进阶6:监听工作状态

    WorkManager.getInstance().getWorkInfoByIdLiveData(request1.getId())
                    .observe(this, new Observer<WorkInfo>() {
                        @Override
                        public void onChanged(@Nullable WorkInfo workInfo) {
                            if (workInfo != null && (workInfo.getState() == WorkInfo.State.SUCCEEDED)){
                                //获取成功返回的结果
                                tvText.setText(workInfo.getOutputData().getString(DateKey));
                            }
                        }
                    });
    

    (7)进阶7:链接工作:用于指定多个关联任务并定义这些任务的运行顺序(可以执行多个任务)

    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class).build();
    OneTimeWorkRequest request1 = new OneTimeWorkRequest.Builder(MyWorker.class).build();
    OneTimeWorkRequest request2 = new OneTimeWorkRequest.Builder(MyWorker.class).build();
    OneTimeWorkRequest request3 = new OneTimeWorkRequest.Builder(MyWorker.class).setInputMerger(OverwritingInputMerger.class).build();
    OneTimeWorkRequest request4 = new OneTimeWorkRequest.Builder(MyWorker.class).build();
    //        为了管理来自多个父级 OneTimeWorkRequest 的输入,WorkManager 使用 InputMerger。
    //        WorkManager 提供两种不同类型的 InputMerger:
    //        OverwritingInputMerger 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
    //        ArrayCreatingInputMerger 会尝试合并输入,并在必要时创建数组。
    
    WorkManager.getInstance()
            //使用beginWith()可以并行执行request、request1、request2 
            .beginWith(Arrays.asList(request, request1, request2)).
            //使用then()可以按顺序执行任务
            .then(request3)//在执行request3
            .then(request4)//在执行request4
            .enqueue();
    

    四、源码分析

    大体流程:
    1.初始化时创建了WorkManager任务执行器管理线程:里面创建了一个单线程池管理后台任务与拿到主线程的handle执行UI更新
    2.在Worker封装了一个线程,通过继承方式把我们的后台任务交给该线程
    3.使用WorkRequest配置该任务线程的执行条件
    4.最终将WorkManager与WorkRequest绑定在一起。实际是把任务线程及配置信息交给WorkManager处理。
    5.也就是调用了WorkManager任务执行器来运行线程与更新UI。

    @ 基于依赖implementation android.arch.work:work-runtime:1.0.1 源码分析

    (1)组件的初始化

    WorkManager的初始化在ContentProvider中,不需要手动添加。WorkManager是一个抽象类,它的大部分方法都是交给他的子类WorkManagerImpl实现的。

    /**
    * @Function workManager初始化
    */
    public class WorkManagerInitializer extends ContentProvider {
        @Override
        public boolean onCreate() {
            // Initialize WorkManager with the default configuration.
            WorkManager.initialize(getContext(), new Configuration.Builder().build());
            return true;
        }
    ......
    }
    
    /**
    * @Function WorkManager.initialize()最终使用单例模式创建WorkManagerImpl对象。
    */
    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        WorkManagerImpl.initialize(context, configuration);
    }
    
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            ...
            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) {
            //创建了WorkManagerImpl
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                //创建了WorkManagerTaskExecutor
                            new WorkManagerTaskExecutor());
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }
    

    核心类:WorkManagerTaskExecutor :主要是管理后台线程与UI线程的执行。

    //通过该类 我们可以执行UI线程上的任务与后台任务
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public class WorkManagerTaskExecutor implements TaskExecutor {
    
        //获取达到UI线程的handler 
        private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
        //创建一个Executor 绑定到UI线程上 再通过调用该Executor可以在UI线程上进行操作
        private final Executor mMainThreadExecutor = new Executor() {
            @Override
            public void execute(@NonNull Runnable command) {
                postToMainThread(command);
            }
        };
    
        @Override
        public void postToMainThread(Runnable r) {
            mMainThreadHandler.post(r);
        }
        ...
    
        //创建了一个单线程池 管理workManager的后台线程
        private final ExecutorService mBackgroundExecutor =
                Executors.newSingleThreadExecutor(mBackgroundThreadFactory);
    
        ... //省略部分调用方法
    }
    

    接下去我们看下 核心类 :WorkManagerImpl

       //按照执行顺序,我们先看下它的构造函数 做了哪些准备工作。
       public WorkManagerImpl(
                @NonNull Context context,
                @NonNull Configuration configuration,
                @NonNull TaskExecutor workTaskExecutor,
                boolean useTestDatabase) {
    
            Context applicationContext = context.getApplicationContext();
            // 创建了一个room 数据库用于保存 任务线程的配置信息
            WorkDatabase database = WorkDatabase.create(applicationContext, useTestDatabase);
            Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
            // 创建Scheduler根据返回一个List<Scheduler>, 
            //里面包含两个Scheduler:GreedyScheduler,SystemJobScheduler/SystemAlarmScheduler
            List<Scheduler> schedulers = createSchedulers(applicationContext);
            //建Processor,Scheduler最后都调用Processor.startWork()去执行Worker中的逻辑,也就是我们重写的doWork()。
            Processor processor = new Processor(
                    context,
                    configuration,
                    workTaskExecutor,
                    database,
                    schedulers);
            //启动APP时检查APP是之前否强制停止退出或有未执行完的任务,是的话重启WorkManager,保证任务可以继续执行。
            internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
        }
    

    由于源码太多这里就不一一摘录了,小弟不才,文采有限。写不出通俗易懂的句子。大家将就看看大体过程就好。初始化阶段就介绍到这里。

    回顾一下初始化过程:

    1.首先创建了WorkManagerImpl类,并持有WorkManagerTaskExecutor类,该类是后台线程与UI线程的主要执行者。

    2.在WorkManagerImpl构造方法中创建了数据库保存任务线程的信息,主要用于App重启时保证任务可以继续执行。

    3.又创建了Schedulers,用来满足不同条件的情况下执行特定的任务。

    4.启动APP时从数据库中获取任务列表判断是否由未执行的任务,并启动 。保证在满足条件的情况下可以继续执行。

    分析到了这里。我们就回发现这里还缺少一个主要的组成部分。那就是我们的任务。如何把我们的后台任务交给workManager处理呢。这就是我们需要收到操作的部分。也就是我们使用WorkManger的过程。

    (2)创建后台任务:Worker

    //这是一个抽象类,所以需要自定义一个类来继承该类并重写 doWork()方法来编写后台任务
    public abstract class Worker extends ListenableWorker {
        ...
        //从该方法中可以看出dowork()在一个线程中执行。getBackgroundExecutor()则是调用了单线程池来管理该线程。
        @Override
        public final @NonNull ListenableFuture<Result> startWork() {
            mFuture = SettableFuture.create();
            getBackgroundExecutor().execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Result result = doWork();
                        mFuture.set(result);
                    } catch (Throwable throwable) {
                        mFuture.setException(throwable);
                    }
    
                }
            });
            return mFuture;
        }
    }
    
    

    (3)配置后台任务的执行条件:WorkRequest

    ——WorkRequest配置后台任务的执行条件,该类是一个抽象类,有WorkManager有两种具体的实现OneTimeWorkRequest/PeriodicWorkRequest。

    new OneTimeWorkRequest.Builder(MyWorker.class)
                    .setConstraints(constraints)//添加约束
                    .setInitialDelay(1,TimeUnit.HOURS)//进阶2:延迟执行
                    .setBackoffCriteria(BackoffPolicy.LINEAR,
                            OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                            TimeUnit.MILLISECONDS)//进阶3:退避政策:当doWork()返回 Result.retry()时 启用
                    .setInputData(imageData)//进阶4:传入参数
                    .addTag(TAG)//进阶4:标记请求任务
                    .build();
    
    //创建了配置信息类WorkSpec ,将执行条件和参数都保存到WorkSpec中
    public abstract static class Builder<B extends Builder, W extends WorkRequest> {
       ...
    
       Builder(@NonNull Class<? extends ListenableWorker> workerClass) {
             mId = UUID.randomUUID();
             mWorkSpec = new WorkSpec(mId.toString(), workerClass.getName());
             addTag(workerClass.getName());
       }
    
       public final @NonNull B setBackoffCriteria(
                @NonNull BackoffPolicy backoffPolicy,
                long backoffDelay,
                @NonNull TimeUnit timeUnit) {
            mBackoffCriteriaSet = true;
            mWorkSpec.backoffPolicy = backoffPolicy;
            mWorkSpec.setBackoffDelayDuration(timeUnit.toMillis(backoffDelay));
            return getThis();
        }
        ...
        public final @NonNull B setConstraints(@NonNull Constraints constraints) {
              mWorkSpec.constraints = constraints;
              return getThis();
        }
        ...
    }
    

    (4)执行任务

        // WorkManager.getInstance().enqueue(request1)
    
        @Override
        @NonNull
        public Operation enqueue(
                @NonNull List<? extends WorkRequest> workRequests) {
            ...
            return new WorkContinuationImpl(this, workRequests).enqueue();
        }
    
        @Override
        public @NonNull Operation enqueue() {
            if (!mEnqueued) {
                //调用单线程池执行EnqueueRunnable   后面详细分析下EnqueueRunnable
                EnqueueRunnable runnable = new EnqueueRunnable(this);
                mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
                mOperation = runnable.getOperation();
            } else {
                Logger.get().warning(TAG,
                        String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
            }
            return mOperation;
        }
    
     //该线程会执行run()方法 并执行两个重要的方法addToDatabase(), scheduleWorkInBackground();
     public class EnqueueRunnable implements Runnable {
         ...
    
        @Override
        public void run() {
            try {
                if (mWorkContinuation.hasCycles()) {
                    throw new IllegalStateException(
                            String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
                }
                //将后台任务及配置信息存到数据库 并返回是否需要执行任务
                boolean needsScheduling = addToDatabase();
                if (needsScheduling) {
                    // Enable RescheduleReceiver, only when there are Worker's that need scheduling.
                    final Context context =
                            mWorkContinuation.getWorkManagerImpl().getApplicationContext();
                    PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
                    scheduleWorkInBackground();
                }
                mOperation.setState(Operation.SUCCESS);
            } catch (Throwable exception) {
                mOperation.setState(new Operation.State.FAILURE(exception));
            }
    
       }
    
        //最后会启用 初始化时创建的GreedyScheduler,SystemJobScheduler/SystemAlarmScheduler等调度类来执行工作.
        @VisibleForTesting
        public void scheduleWorkInBackground() {
            WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
            Schedulers.schedule(
                    workManager.getConfiguration(),
                    workManager.getWorkDatabase(),
                    workManager.getSchedulers());
        }
    

    更详细的代码就不贴了,大家要是脑补不了。在按流程仔细看一遍源码会了解的更深。

    本想画个图加深一下印象,结果发现是个手残党 ,对不住大家 。

    五、内容推荐

    若您发现文章中存在错误或不足的地方,希望您能指出!

    相关文章

      网友评论

        本文标题:Android Jetpack架构组件之WorkManager入

        本文链接:https://www.haomeiwen.com/subject/wpqhlhtx.html