阅读 465

Android Jetpack架构组件之WorkManager入门

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

前言

——最近抽空又学了一个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());
    }复制代码

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

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

五、内容推荐

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