WorkManager这个任务管理框架还是非常好用的,有人说可以用它替代Service,额,这个我就不太清楚了,不过个人认为替代AsycnTask和JobScheduler应该是没问题,而WorkManager中的循环任务使用的也正是JobScheduler。这里我不去介绍WorkManager的使用,官网和百度都是学习使用的圣地~,我这里只说源码,通过源码看本质,让我们更加透彻的了解WorkManager的原理。
一、源码分析
对于WorkManager的源码,从三个地方入手分析:WorkManager的初始化,任务(WorkRequest)的加入,任务执行结果
1.WorkManager的初始化
估计有什么蒙了,初始化还有什么好说的吗?不就是WorkManager实例化吗?别着急,我们慢慢看看
我们从WorkManager.getInstance方法进入,然后进入到WorkManagerImpl的getInstance方法中
//WorkManager的getInstance
public static @NonNull WorkManager getInstance(@NonNull Context context) {
return WorkManagerImpl.getInstance(context);
}
//WorkManagerImpl的getInstance
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
synchronized (sLock) {
WorkManagerImpl instance = getInstance();
if (instance == null) {
Context appContext = context.getApplicationContext();
if (appContext instanceof Configuration.Provider) {
initialize(
appContext,
((Configuration.Provider) appContext).getWorkManagerConfiguration());
instance = getInstance(appContext);
} else {
throw new IllegalStateException("太长了,省略~");
}
}
return instance;
}
}
我们再进入无参的getInstance方法中瞧瞧
public static @Nullable WorkManagerImpl getInstance() {
synchronized (sLock) {
if (sDelegatedInstance != null) {
return sDelegatedInstance;
}
return sDefaultInstance;
}
}
很明显,sDelegatedInstance和sDefaultInstance都为null,我们回到带参的getInstance方法中:从源码中可以看到,如果instance为空的时候,先判断context是否Configuration.Provider的实现类或子类,这个类是干嘛的我不太清楚,但是我基本上上可以肯定Application没有implement这个接口;如果没有实现这个接口或者不是它的子类的话,直接抛出异常。
what?到这里我就蒙了,反反复复看了好几遍,流程的确没问题,有问题的应该是从无参的getInstance中获取的实例上,说明这两个示例有在其他地方初始化。
通过逆向寻找,找到了源头WorkManagerInitializer,上图(所谓的逆向寻找就是从sDelegatedInstance和sDefaultInstance初始化的地方开始向上寻找):
WorkManagerInitializer:从源码上我们可以知道,它继承了ContentProvider。那么清单文件中,一定有它的注册才能执行onCreate方法。我在github上找到了WorkManager的源码(地址),清单文件中的确发现了注册的代码,如下图。
WorkManagerImpl的实例初始化完毕,不过我不太清楚的是为什么要使用这种方式进行初始化,有知道的小伙伴可以留言通知呦~
2.任务(WorkRequest)的加入
如何创建WorkRequest可以在官网上进行学习,创建完成后WorkManager执行enqueue方法进行加入执行队列中,WorkManager中的enqueue是一个抽象方法,而WorkManagerImpl是WorkManager的实现类,我们直接看WorkManagerImpl中的enqueue方法
@Override
@NonNull
public Operation enqueue(
@NonNull List<? extends WorkRequest> workRequests) {
// This error is not being propagated as part of the Operation, as we want the
// app to crash during development. Having no workRequests is always a developer error.
if (workRequests.isEmpty()) {
throw new IllegalArgumentException(
"enqueue needs at least one WorkRequest.");
}
return new WorkContinuationImpl(this, workRequests).enqueue();
}
这里将WorkManagerImpl和WorkRequests集合,加入到了WorkContinuationImpl这类中,然后执行enqueue方法。
@Override
public @NonNull Operation enqueue() {
// Only enqueue if not already enqueued.
if (!mEnqueued) {
// The runnable walks the hierarchy of the continuations
// and marks them enqueued using the markEnqueued() method, parent first.
EnqueueRunnable runnable = new EnqueueRunnable(this);
//将Runnable加入到线程池执行
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
mOperation = runnable.getOperation();
} else {
Logger.get().warning(TAG,
String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
}
return mOperation;
}
EnqueueRunnable实现了Runnable接口,所以我们直接看run方法:
@Override
public void run() {
try {
if (mWorkContinuation.hasCycles()) {
throw new IllegalStateException(
String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
}
//加入到Room数据库
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));
}
}
addToDatabase方法,其实就是把任务(WorkSpec这个类)存储到了Room数据库。经过如下方法的跳转,在enqueueWorkWithPrerequisites中找到了inserWorkSpec方法
workDatabase.workSpecDao().insertWorkSpec(workSpec);
我们继续回到EnqueueRunnable中的run方法,其中scheduleWorkInBackground方法是执行任务,我们进入查看源码
public void scheduleWorkInBackground() {
WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
Schedulers.schedule(
workManager.getConfiguration(),
workManager.getWorkDatabase(),
workManager.getSchedulers());
}
进入Schedulers.schedule方法中
public static void schedule(
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List<Scheduler> schedulers) {
if (schedulers == null || schedulers.size() == 0) {
return;
}
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
List<WorkSpec> eligibleWorkSpecsForLimitedSlots;
List<WorkSpec> allEligibleWorkSpecs;
workDatabase.beginTransaction();
try {
// Enqueued workSpecs when scheduling limits are applicable.
//有限制的任务集合
eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
configuration.getMaxSchedulerLimit());
// Enqueued workSpecs when scheduling limits are NOT applicable.
//所有任务的集合
allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling();
if (eligibleWorkSpecsForLimitedSlots != null
&& eligibleWorkSpecsForLimitedSlots.size() > 0) {
long now = System.currentTimeMillis();
// Mark all the WorkSpecs as scheduled.
// Calls to Scheduler#schedule() could potentially result in more schedules
// on a separate thread. Therefore, this needs to be done first.
for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
workSpecDao.markWorkSpecScheduled(workSpec.id, now);
}
}
workDatabase.setTransactionSuccessful();
} finally {
workDatabase.endTransaction();
}
if (eligibleWorkSpecsForLimitedSlots != null
&& eligibleWorkSpecsForLimitedSlots.size() > 0) {
WorkSpec[] eligibleWorkSpecsArray =
new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
eligibleWorkSpecsArray =
eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);
// Delegate to the underlying schedulers.
for (Scheduler scheduler : schedulers) {
if (scheduler.hasLimitedSchedulingSlots()) {
scheduler.schedule(eligibleWorkSpecsArray);
}
}
}
if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
WorkSpec[] enqueuedWorkSpecsArray = new WorkSpec[allEligibleWorkSpecs.size()];
enqueuedWorkSpecsArray = allEligibleWorkSpecs.toArray(enqueuedWorkSpecsArray);
// Delegate to the underlying schedulers.
for (Scheduler scheduler : schedulers) {
if (!scheduler.hasLimitedSchedulingSlots()) {
scheduler.schedule(enqueuedWorkSpecsArray);
}
}
}
}
其实上述的代码主要是两个功能:第一:从数据库中查找出任务集合 第二:使用具体的Scheduler去执行任务
我们这里从allEligibleWorkSpec(无限制任务)入手,而执行它的Scheduler是GreedyScheduler,我们直接看它的scheduler方法,关键代码如下
for (WorkSpec workSpec : workSpecs) {
long nextRunTime = workSpec.calculateNextRunTime();
long now = System.currentTimeMillis();
if (workSpec.state == WorkInfo.State.ENQUEUED) {
if (now < nextRunTime) {
// Future work
if (mDelayedWorkTracker != null) {
mDelayedWorkTracker.schedule(workSpec);
}
} else if (workSpec.hasConstraints()) {
if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
// Ignore requests that have an idle mode constraint.
Logger.get().debug(TAG,
String.format("Ignoring WorkSpec %s, Requires device idle.",
workSpec));
} else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
// Ignore requests that have content uri triggers.
Logger.get().debug(TAG,
String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
workSpec));
} else {
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
}
} else {
Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
mWorkManagerImpl.startWork(workSpec.id);
}
}
}
我们看到最后一行代码,执行了mWorkManagerImpl的startWork方法,也就是说整个任务又回到了WorkManagerImpl中,那为什么饶了上面的一大圈呢?主要是对任务存储数据库的工作及任务状态改变的工作
我们继续追根溯源,定位到了两个参数的startWork方法
public void startWork(
@NonNull String workSpecId,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkTaskExecutor
.executeOnBackgroundThread(
new StartWorkRunnable(this, workSpecId, runtimeExtras));
}
StartWorkRunnable中的run方法
@Override
public void run() {
mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
}
Processor中的startWork方法
public boolean startWork(
@NonNull String id,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
WorkerWrapper workWrapper;
synchronized (mLock) {
// Work may get triggered multiple times if they have passing constraints
// and new work with those constraints are added.
if (isEnqueued(id)) {
Logger.get().debug(
TAG,
String.format("Work %s is already enqueued for processing", id));
return false;
}
workWrapper =
new WorkerWrapper.Builder(
mAppContext,
mConfiguration,
mWorkTaskExecutor,
this,
mWorkDatabase,
id)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
ListenableFuture<Boolean> future = workWrapper.getFuture();
future.addListener(
new FutureListener(this, id, future),
mWorkTaskExecutor.getMainThreadExecutor());
mEnqueuedWorkMap.put(id, workWrapper);
}
mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
return true;
}
执行了WorkWrapper,这个类实现了Runnable接口,我们看其中的run方法
@WorkerThread
@Override
public void run() {
mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
mWorkDescription = createWorkDescription(mTags);
runWorker();
}
public void runWorker(){
...此处省略...
mWorkTaskExecutor.getMainThreadExecutor()
.execute(new Runnable() {
@Override
public void run() {
try {
Logger.get().debug(TAG, String.format("Starting work for %s",
mWorkSpec.workerClassName));
mInnerFuture = mWorker.startWork();
future.setFuture(mInnerFuture);
} catch (Throwable e) {
future.setException(e);
}
}
});
...此处省略...
}
我们可以看到,执行了mWorker的startWork方法,我们看Work.java的startWork方法
@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;
}
我们看到了doWork这个方法,我擦,终于找了WorkRequest中的doWork方法,使用WorkManager的小伙伴对这个doWork肯定不陌生,到这里为止呢,enqueue的过程就结束了,我画了一张图便于大家理解这个enqueue的过程
WorkManagerTaskExecutor在这个过程的作用是线程的调度
3.任务执行结果
任务执行结果这里,其实没有特别的东西需要介绍的,官网介绍了几种:RxJava,LiveData等,我个人使用的都是LiveData的方式,调用的是WorkManagerImpl中的getWorkInfoByIdLiveData方法,如下
@Override
public @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id) {
WorkSpecDao dao = mWorkDatabase.workSpecDao();
LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString()));
return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData,
new Function<List<WorkSpec.WorkInfoPojo>, WorkInfo>() {
@Override
public WorkInfo apply(List<WorkSpec.WorkInfoPojo> input) {
WorkInfo workInfo = null;
if (input != null && input.size() > 0) {
workInfo = input.get(0).toWorkInfo();
}
return workInfo;
}
},
mWorkTaskExecutor);
}
我们可以看到是使用了Room数据库,Room数据库是支持LiveData的,数据改变,自然会更新到Observer的onChanged方法
二、遇到的问题
看了上面的源码,估计同学们还是有很多问题,的确,WorkManager涉及的内容比较广,上面的源码只是一个普通流程的分析,比如传值,约束,循环任务等都没有说到,所以我这里抛出几个我自己在撸源码时给自己提出的问题
1.WorkManager的约束条件是如何执行的?
查看约束条件的源码,我们要从ConstrantProxy这里开始,估计有的同学会问,你怎么找到这个类的?因为约束条件是电量,网络等,所以我猜测WorkManager应该使用的广播,所以我直接查看了WorkManger的清单文件,发现了如下的代码:
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
tools:targetApi="n">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
tools:targetApi="n">
<intent-filter>
<action android:name="android.intent.action.BATTERY_OKAY"/>
<action android:name="android.intent.action.BATTERY_LOW"/>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
tools:targetApi="n">
<intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_LOW"/>
<action android:name="android.intent.action.DEVICE_STORAGE_OK"/>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
tools:targetApi="n">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
电量,网络改变等的广播都是ConstraintProxy的内部类,所以我就找到了ConstraintProxy。我这里就不直接介绍源码的流程了,因为这个过程比较绕,用语言显得比较乱,我直接用流程图表示,感兴趣的同学完全可以根据流程图来找源码,我这里以网络变化为例
最后一步,startWork方法应该不陌生,在上面源码解析中,也有startWork这个方法,之后的流程和源码解析中的流程是一样的了
2.WorkManager中的约束任务和非约束任务在执行上有什么不同?
上面第一个问题所讲述的是这样的一个场景:任务执行时,条件不满足,此时任务挂起。当条件满足时,执行上述流程。
那么当任务执行时就满足条件,任务是如何执行的呢?我们先看一下第2题这个问题,约束任务和非约束任务在执行上的不同。其实不同的地方我们在源码探索时也简单说过,在Schedulers.schedule方法中。非约束条件使用的是GreedyScheduler进行任务的执行,那么约束条件呢?我们先从WorkManagerImpl的构造方法找起:
public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
@NonNull WorkDatabase database) {
Context applicationContext = context.getApplicationContext();
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
//创建任务执行器集合
List<Scheduler> schedulers =
createSchedulers(applicationContext, configuration, workTaskExecutor);
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}
再进入createSchedulers方法
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
public List<Scheduler> createSchedulers(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor taskExecutor) {
return Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(context, this),
// Specify the task executor directly here as this happens before internalInit.
// GreedyScheduler creates ConstraintTrackers and controllers eagerly.
new GreedyScheduler(context, configuration, taskExecutor, this));
}
进入Schedulers的createBestAvailableBackgroundScheduler方法中
@NonNull
static Scheduler createBestAvailableBackgroundScheduler(
@NonNull Context context,
@NonNull WorkManagerImpl workManager) {
Scheduler scheduler;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
} else {
scheduler = tryCreateGcmBasedScheduler(context);
if (scheduler == null) {
scheduler = new SystemAlarmScheduler(context);
setComponentEnabled(context, SystemAlarmService.class, true);
Logger.get().debug(TAG, "Created SystemAlarmScheduler");
}
}
return scheduler;
}
我们发现,不同的版本使用的Scheduler不一样,我们先说一下if中的代码,也就是版本号大于等于23时所使用的的Scheduler,如上述代码所写,使用是SystemJobScheduler,我们进入SystemJobScheduler内部可以看到JobScheduler这个类
private final JobScheduler mJobScheduler;
当执行scheduler()方法时,会进入scheduleInternal()方法,然后执行mSystemJobInfoConverter.convert()方法,我们就不进入源码查看了,感兴趣的同学可以自己看~。我们从convert()方法中可以看见,设置了低电量,低存储等约束条件,因为JobShceduler本身是支持这些约束条件的。所以大于等于23版本,直接使用了JobScheduler执行了约束条件任务。
我们再说一下else中的方法:tryCreateGcmBasedScheduler方法上有一个gcm,是不是很眼熟啊,是的我们在gradle中进行依赖库的时候依赖过下面的代码:
implementation "androidx.work:work-gcm:$work_version"
也就是说依赖了work-gcm的,使用的是GcmScheduler(可以进如tryCreateGcmBasedScheduler方法中查看),GcmScheduler其实和JobScheduler原理一样,算是一个支持低版本的JobScheduler,也是支持约束条件任务的执行。
如果没有依赖work-gcm的话,使用的是SystemAlarmScheduler,我们从SystemAlarmScheduler的scheduler方法中进入,然后到schedulerWorkSpec方法中
private void scheduleWorkSpec(@NonNull WorkSpec workSpec) {
Logger.get().debug(TAG, String.format("Scheduling work with workSpecId %s", workSpec.id));
Intent scheduleIntent = CommandHandler.createScheduleWorkIntent(mContext, workSpec.id);
mContext.startService(scheduleIntent);
}
CommandHandler很眼熟对吧,第一个问题中已经说过了,流程和第一个问题中的基本一样
3.WorkManager中的一次性任务和循环任务是如何执行的?
其实这个问题比较好描述,我们可以从PeriodicWorkRequest中发现,直接把时间间隔赋值了WorkSpec中的inervalDuration和flexDuration;而一次性任务这两个字段肯定是初始值。根据这两个值来决定任务是否循环。
4.WorkManagerImpl中的实例是如何初始化的?为什么要采用这种方式?
这个问题我也不知道为啥?有懂的朋友,希望不吝赐教~(抱拳)
5.如果WorkRequest在enqueue时约束条件不符合,没有执行,之后约束条件符合后会执行吗?从源码上说为什么?
如果上面的问题都理解了的话,我相信这个问题应该很好回答了,我这里就不赘述了
WorkManager终于撸完了,有问题的小伙伴可以留言讨论~