——你可以失望,但不能绝望。累的时候可以慢一点,千万不要后退,你还没有拼劲全力。怎么知道没有奇迹。
前言
——最近抽空又学了一个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());
}
更详细的代码就不贴了,大家要是脑补不了。在按流程仔细看一遍源码会了解的更深。
本想画个图加深一下印象,结果发现是个手残党 ,对不住大家 。
五、内容推荐
- 《CSDN》《简书》
- 《Android Jetpack架构组件之Navigation入门》
- 《Android Jetpack架构组件之Room入门及源码分析》
- 《Android Jetpack架构组件之LiveData》
- 《Android Jetpack架构组件之Lifecycle源码分析》
若您发现文章中存在错误或不足的地方,希望您能指出!
网友评论