前言
由于笔者目前水平限制,表达能力有限,尽请见谅。
WorkManager 是 Android Jetpack 库的一部分,提供了一种向后兼容的方式来安排可延迟的异步任务,这些任务即使在应用退出或设备重启后也应该继续执行,它是 Android 推荐的解决方案,用于处理需要保证执行的后台任务。WorkManager 适合用于那些不需要立即执行的任务,但最终需要完成的任务。
上文主要深挖到了最佳调度器BestAvailableBackgroundScheduler的选择方式,接下来先将继续深入调度器的原理。
正文
SystemJobScheduler调度器
系统最推荐的调度器(设备支持下的最佳调度器)。
类的参数如下:
对于最关键的schedule函数,代码如下:
具体来看他做了这些事
IdGenerator
生成新的作业ID生成器。- 方法接收一个或多个
WorkSpec
对象,方法遍历这些WorkSpec
对象,准备将它们转换为JobScheduler
作业。 - 开启数据库事务,然后根据WorkSpec的Id获取WorkSpec,如果
WorkSpec
不再数据库中,或者其状态不是已入队(ENQUEUED),则跳过调度,并将当前事务标记为成功。 - 接下来首先检查是否已经为这个
WorkSpec
分配了一个系统作业ID(SystemIdInfo),如果没有生成一个新的,
新ID在mConfiguration
定义的最小和最大作业ID范围内。 如果info
是null
,则创建一个新的SystemIdInfo
对象,其中包含了WorkSpec
的生成ID和新分配的作业ID。- 接着,将这个新的
SystemIdInfo
对象插入到数据库中,在WorkSpec
和JobScheduler
作业之间建立持久的关联。
scheduleInternal
是实际执行调度逻辑的方法,将WorkSpec
转换为JobInfo
对象,并通过JobScheduler
安排执行。
同时针对API23进行特殊处理(Android6.0)
在Android API 23(Android 6.0)上,JobScheduler
只有在队列中至少有两个作业时才会启动作业,为了兼容,SystemJobScheduler
会在API 23上为每个作业进行双重调度,方法通过调用getPendingJobIds
来获取当前已调度但尚未执行的作业ID列表,然后为当前WorkSpec
选择一个新的作业ID进行第二次调度,emmm。
getPendingJobIds函数如下
@Nullableprivate static List<Integer> getPendingJobIds(@NonNull Context context,@NonNull JobScheduler jobScheduler,@NonNull String workSpecId) {List<JobInfo> jobs = getPendingJobs(context, jobScheduler);if (jobs == null) {return null;}// We have at most 2 jobs per WorkSpecList<Integer> jobIds = new ArrayList<>(2);for (JobInfo jobInfo : jobs) {WorkGenerationalId id = getWorkGenerationalIdFromJobInfo(jobInfo);if (id != null && workSpecId.equals(id.getWorkSpecId())) {jobIds.add(jobInfo.getId());}}return jobIds;}
本函数不再多研究了,刚刚遇到的有意思的函数是另外一个:
将一个特定的WorkSpec
转换为JobScheduler
可识别的任务并进行调度。
@VisibleForTestingpublic void scheduleInternal(@NonNull WorkSpec workSpec, int jobId) {JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec, jobId);Logger.get().debug(TAG,"Scheduling work ID " + workSpec.id + "Job ID " + jobId);try {int result = mJobScheduler.schedule(jobInfo);if (result == JobScheduler.RESULT_FAILURE) {Logger.get().warning(TAG, "Unable to schedule work ID " + workSpec.id);if (workSpec.expedited&& workSpec.outOfQuotaPolicy == RUN_AS_NON_EXPEDITED_WORK_REQUEST) {// Falling back to a non-expedited job.workSpec.expedited = false;String message = String.format("Scheduling a non-expedited job (work ID %s)", workSpec.id);Logger.get().debug(TAG, message);scheduleInternal(workSpec, jobId);}}} catch (IllegalStateException e) {// This only gets thrown if we exceed 100 jobs. Let's figure out if WorkManager is// responsible for all these jobs.List<JobInfo> jobs = getPendingJobs(mContext, mJobScheduler);int numWorkManagerJobs = jobs != null ? jobs.size() : 0;String message = String.format(Locale.getDefault(),"JobScheduler 100 job limit exceeded. We count %d WorkManager "+ "jobs in JobScheduler; we have %d tracked jobs in our DB; "+ "our Configuration limit is %d.",numWorkManagerJobs,mWorkDatabase.workSpecDao().getScheduledWork().size(),mConfiguration.getMaxSchedulerLimit());Logger.get().error(TAG, message);IllegalStateException schedulingException = new IllegalStateException(message, e);// If a SchedulingExceptionHandler is defined, let the app handle the scheduling// exception.Consumer<Throwable> handler = mConfiguration.getSchedulingExceptionHandler();if (handler != null) {handler.accept(schedulingException);} else {// Rethrow a more verbose exception.throw schedulingException;}} catch (Throwable throwable) {// OEM implementation bugs in JobScheduler cause the app to crash. Avoid crashing.Logger.get().error(TAG, "Unable to schedule " + workSpec, throwable);}}
这里mSystemJobInfoConverter就发挥作用了,将WorkSpec
对象转换为JobScheduler
需要的JobInfo
对象,对于这个方法也就不多探究了,
具体就是将WorkSpec
定义的约束和配置映射到JobInfo
的相应属性上,如设置任务的执行条件(比如网络状态、设备充电状态等)。
接着使用JobScheduler
的schedule
方法尝试调度任务。schedule
方法返回一个结果码,如果调度失败(JobScheduler.RESULT_FAILURE
),则记录警告日志,如果一个加急的WorkSpec
(workSpec.expedited
为true
)无法调度,且其outOfQuotaPolicy
策略设置为RUN_AS_NON_EXPEDITED_WORK_REQUEST
,则会尝试将其作为非加急任务重新调度。
catch就捕获各种异常,如有定义异常处理器,就传给异常处理器,如果没有就打印。
GreedyScheduler调度器
GreedyScheduler
是 一个为了尽快执行任务而设计的调度器,不依赖于系统的 JobScheduler
,而是直接在应用进程中尝试执行任务。
其参数如下
- Configuration :WorkManager的配置信息,如最小延迟时间、任务重试策略。
- Processors:管理和执行后台任务。
- WorkLauncher:实际启动执行任务。
- TaskExecutor :执行任务的线程池管理器。
- WorkConstraintsTracker :监视任务约束条件。
- DelayedWorkTracker:跟踪和调度需要延迟执行的任务。
关键的调度方法如下
- 首先通过
checkDefaultProcess
方法检查当前进程是否为应用的默认进程。如果不是则记录日志并退出方法。 registerExecutionListenerIfNeeded
方法确保GreedyScheduler
注册了对工作执行的监听,允许调度器在工作任务执行完成后接收回调。- 对于每一个传入的
WorkSpec
对象,检查这项工作是否已经有一个对应的启动令牌,有则跳过此任务。
计算下一次执行任务的时间
- 如果工作已计划在未来执行,并且存在延迟工作跟踪器,则安排该工作在未来执行。
- 如果工作有特定的约束条件,且当前环境不满足这些约束,则跳过该工作。
- 如果工作无需延迟且无特殊约束或当前环境满足约束条件,则准备立即执行该工作。
接下来synchronized (mLock)
确保对mConstrainedWorkSpecs
访问和修改的线程安全。if (!constrainedWorkSpecs.isEmpty())
确保只有在存在至少一个有约束条件的WorkSpec
时,才进行后续操作。- 如果mConstrainedWorkSpecs包括了约束Id,创建一个新的Job来监听这个WorkSpec的约束条件,并把Job和WorkSpec关联。
这个Job
会持续监听与WorkSpec
相关的约束条件是否被满足,采用协程作业。
@Overridepublic void onConstraintsStateChanged(@NonNull WorkSpec workSpec,@NonNull ConstraintsState state) {WorkGenerationalId id = generationalId(workSpec);if (state instanceof ConstraintsState.ConstraintsMet) {// it doesn't help against races, but reduces useless load in the systemif (!mStartStopTokens.contains(id)) {Logger.get().debug(TAG, "Constraints met: Scheduling work ID " + id);StartStopToken token = mStartStopTokens.tokenFor(id);mTimeLimiter.track(token);mWorkLauncher.startWork(token);}} else {Logger.get().debug(TAG, "Constraints not met: Cancelling work ID " + id);StartStopToken runId = mStartStopTokens.remove(id);if (runId != null) {mTimeLimiter.cancel(runId);int reason = ((ConstraintsState.ConstraintsNotMet) state).getReason();mWorkLauncher.stopWorkWithReason(runId, reason);}}}
当WorkSpec
的约束条件满足状态变化时,这个方法会被调用,
外层if检查约束条件是否都满足了,如果未满足,则从mStartStopTokens
集合中移除对应的启动令牌,停止跟踪该工作的执行状态,继续使用stopWorkWithReason(runId, reason)
停止工作任务的执行,reason
参数指明停止执行的原因。
被嵌套的if检查mStartStopTokens
集合中是否已经存在对应WorkSpec
的启动令牌,如果不存在对应的启动令牌,说明这项工作尚未开始执行,可以继续启动过程。
系列文章未完待续。