Android源码阅读WorkMangaer - 5

前言

由于笔者目前水平限制,表达能力有限,尽请见谅。

WorkManager 是 Android Jetpack 库的一部分,提供了一种向后兼容的方式来安排可延迟的异步任务,这些任务即使在应用退出或设备重启后也应该继续执行,它是 Android 推荐的解决方案,用于处理需要保证执行的后台任务。WorkManager 适合用于那些不需要立即执行的任务,但最终需要完成的任务。

上文主要深挖到了最佳调度器BestAvailableBackgroundScheduler的选择方式,接下来先将继续深入调度器的原理。

正文

SystemJobScheduler调度器

系统最推荐的调度器(设备支持下的最佳调度器)。

类的参数如下:

 对于最关键的schedule函数,代码如下:

具体来看他做了这些事

  1. IdGenerator生成新的作业ID生成器。
  2. 方法接收一个或多个WorkSpec对象,方法遍历这些WorkSpec对象,准备将它们转换为JobScheduler作业。
  3. 开启数据库事务,然后根据WorkSpec的Id获取WorkSpec,如果WorkSpec不再数据库中,或者其状态不是已入队(ENQUEUED),则跳过调度,并将当前事务标记为成功。
  4. 接下来首先检查是否已经为这个WorkSpec分配了一个系统作业ID(SystemIdInfo),如果没有生成一个新的,新ID在mConfiguration定义的最小和最大作业ID范围内。
  5. 如果infonull,则创建一个新的SystemIdInfo对象,其中包含了WorkSpec的生成ID和新分配的作业ID。
  6. 接着,将这个新的SystemIdInfo对象插入到数据库中,在WorkSpecJobScheduler作业之间建立持久的关联。

scheduleInternal是实际执行调度逻辑的方法,将WorkSpec转换为JobInfo对象,并通过JobScheduler安排执行。

同时针对API23进行特殊处理(Android6.0)

在Android API 23(Android 6.0)上,JobScheduler只有在队列中至少有两个作业时才会启动作业,为了兼容,SystemJobScheduler会在API 23上为每个作业进行双重调度,方法通过调用getPendingJobIds来获取当前已调度但尚未执行的作业ID列表,然后为当前WorkSpec选择一个新的作业ID进行第二次调度,emmm。

getPendingJobIds函数如下

@Nullable
    private 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 WorkSpec
        List<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可识别的任务并进行调度。

@VisibleForTesting
    public 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的相应属性上,如设置任务的执行条件(比如网络状态、设备充电状态等)。

接着使用JobSchedulerschedule方法尝试调度任务。schedule方法返回一个结果码,如果调度失败(JobScheduler.RESULT_FAILURE),则记录警告日志,如果一个加急的WorkSpecworkSpec.expeditedtrue)无法调度,且其outOfQuotaPolicy策略设置为RUN_AS_NON_EXPEDITED_WORK_REQUEST,则会尝试将其作为非加急任务重新调度。

catch就捕获各种异常,如有定义异常处理器,就传给异常处理器,如果没有就打印。

GreedyScheduler调度器

GreedyScheduler 是 一个为了尽快执行任务而设计的调度器,不依赖于系统的 JobScheduler,而是直接在应用进程中尝试执行任务。

其参数如下

  • Configuration :WorkManager的配置信息,如最小延迟时间、任务重试策略。
  • Processors:管理和执行后台任务。
  • WorkLauncher:实际启动执行任务。
  • TaskExecutor :执行任务的线程池管理器。
  • WorkConstraintsTracker :监视任务约束条件。
  • DelayedWorkTracker:跟踪和调度需要延迟执行的任务。

关键的调度方法如下

 

  • 首先通过checkDefaultProcess方法检查当前进程是否为应用的默认进程。如果不是则记录日志并退出方法。
  • registerExecutionListenerIfNeeded方法确保GreedyScheduler注册了对工作执行的监听,允许调度器在工作任务执行完成后接收回调。
  • 对于每一个传入的WorkSpec对象,检查这项工作是否已经有一个对应的启动令牌,有则跳过此任务。

计算下一次执行任务的时间

  • 如果工作已计划在未来执行,并且存在延迟工作跟踪器,则安排该工作在未来执行。
  • 如果工作有特定的约束条件,且当前环境不满足这些约束,则跳过该工作。
  • 如果工作无需延迟且无特殊约束或当前环境满足约束条件,则准备立即执行该工作。
  1. 接下来synchronized (mLock)确保对mConstrainedWorkSpecs访问和修改的线程安全。
  2. if (!constrainedWorkSpecs.isEmpty())确保只有在存在至少一个有约束条件的WorkSpec时,才进行后续操作。
  3. 如果mConstrainedWorkSpecs包括了约束Id,创建一个新的Job来监听这个WorkSpec的约束条件,并把Job和WorkSpec关联。

这个Job会持续监听与WorkSpec相关的约束条件是否被满足,采用协程作业。

@Override
    public 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 system
            if (!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的启动令牌,如果不存在对应的启动令牌,说明这项工作尚未开始执行,可以继续启动过程。

系列文章未完待续。

  • 28
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏目艾拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值