JSS 第 3 篇 - JobSchedulerService - schedule

基于 Android 7.1.1 源码分析,本文为原创,转载请注明出处,谢谢~~~爱你们的 Coolqi!

前言

我们先从基本的方法开始,也就是 schedule 方法,方法参数传递:

  • JobInfo job:需要 schedule 的任务!
  • int uId:调用方的 uid!
    public int schedule(JobInfo job, int uId) {
        return scheduleAsPackage(job, uId, null, -1, null);
    }
    public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId, String tag) {
        // 创建新的 jobStatus
        JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
        try {
            if (ActivityManagerNative.getDefault().getAppStartMode(uId,
                    job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
                        + " -- package not allowed to start");
                return JobScheduler.RESULT_FAILURE;
            }
        } catch (RemoteException e) {
        }

        if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
        JobStatus toCancel;
        synchronized (mLock) {
            // Jobs on behalf of others don't apply to the per-app job cap
            // 判断应用设置的任务数量是否超过上限:100
            if (ENFORCE_MAX_JOBS && packageName == null) {
                if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
                    Slog.w(TAG, "Too many jobs for uid " + uId);
                    throw new IllegalStateException("Apps may not schedule more than "
                                + MAX_JOBS_PER_APP + " distinct jobs");
                }
            }
            // 如果同一个id,之前已经注册了一个任务,取消上一个任务
            toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
            if (toCancel != null) {
                cancelJobImpl(toCancel, jobStatus);
            }
            // 开始追踪该任务
            startTrackingJob(jobStatus, toCancel);
        }
        // 向 system_server 进程的主线程发送 message:MSG_CHECK_JOB
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
        return JobScheduler.RESULT_SUCCESS;
    }

我们是使用 schedule 方法将我们需要的任务注册到系统中的,传入的参数主要是:

  1. JobInfo:封装任务的基本信息!
  2. uId:调用者的 uid!

我们接着来看,先调用 JobStatus 的 createFromJobInfo ,创建 JobStatus:

1 JobStatus.createFromJobInfo

参数传递:job, uId, null, -1, null,

   /**
     * Create a newly scheduled job.
     * @param callingUid Uid of the package that scheduled this job.
     * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates
     *                          the calling package is the source.
     * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
     */
    public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
            int sourceUserId, String tag) {
        final long elapsedNow = SystemClock.elapsedRealtime(); // 从开机到现在的时间
        final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
        // 判断这个任务是否是周期性的
        if (job.isPeriodic()) {
            latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
            earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
        } else {
            earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
                    elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
            latestRunTimeElapsedMillis = job.hasLateConstraint() ?
                    elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
        }
        return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0,
                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
    }

这里通过 JobInfo 创建了对应的 JobStatus 对象,参数传递:
job, uId, null, -1, null,0,earliestRunTimeElapsedMillis,latestRunTimeElapsedMillis。

    private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
            int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
            long latestRunTimeElapsedMillis) {
        this.job = job;
        this.callingUid = callingUid;
        int tempSourceUid = -1;
        if (sourceUserId != -1 && sourcePackageName != null) {
            try {
                tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
                        sourceUserId);
            } catch (RemoteException ex) {
                // Can't happen, PackageManager runs in the same process.
            }
        }
    
        if (tempSourceUid == -1) {
            this.sourceUid = callingUid;
            this.sourceUserId = UserHandle.getUserId(callingUid);
            this.sourcePackageName = job.getService().getPackageName();
            this.sourceTag = null;
        } else {
            this.sourceUid = tempSourceUid;
            this.sourceUserId = sourceUserId;
            this.sourcePackageName = sourcePackageName;
            this.sourceTag = tag;
        }
        this.batteryName = this.sourceTag != null
                ? this.sourceTag + ":" + job.getService().getPackageName()
                : job.getService().flattenToShortString();
        this.tag = "*job*/" + this.batteryName;
        this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
        this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
        this.numFailures = numFailures;
        int requiredConstraints = 0;
        if (job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY) {
            requiredConstraints |= CONSTRAINT_CONNECTIVITY;
        }
        if (job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED) {
            requiredConstraints |= CONSTRAINT_UNMETERED;
        }
        if (job.getNetworkType() == JobInfo.NETWORK_TYPE_NOT_ROAMING) {
            requiredConstraints |= CONSTRAINT_NOT_ROAMING;
        }
        if (job.isRequireCharging()) {
            requiredConstraints |= CONSTRAINT_CHARGING;
        }
        if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
            requiredConstraints |= CONSTRAINT_TIMING_DELAY;
        }
        if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
            requiredConstraints |= CONSTRAINT_DEADLINE;
        }
        if (job.isRequireDeviceIdle()) {
            requiredConstraints |= CONSTRAINT_IDLE;
        }
        if (job.getTriggerContentUris() != null) {
            requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
        }
        this.requiredConstraints = requiredConstraints;
    }

这里很简单的,就是通过 JobInfo 来创建对应的 JobStatus,我们先看到这里!

2 JobStore.countJobsForUid

这里是统计 uid 对用的应用有多少个任务:

    public int countJobsForUid(int uid) {
        return mJobSet.countJobsForUid(uid);
    }

JobStore 有一个集合,用来存放所有的 Job!

final JobSet mJobSet;

我们来看看这个 JobSet

    static class JobSet {
        // Key is the getUid() originator of the jobs in each sheaf
        // mJobs 的 key 是应用的 uid!
        private SparseArray<ArraySet<JobStatus>> mJobs;
        public JobSet() {
            mJobs = new SparseArray<ArraySet<JobStatus>>();
        }
        ... ... ... ...
        // We only want to count the jobs that this uid has scheduled on its own
        // behalf, not those that the app has scheduled on someone else's behalf.
        // 统计 uid 对应的应用注册的任务数!
        public int countJobsForUid(int uid) {
            int total = 0;
            ArraySet<JobStatus> jobs = mJobs.get(uid);
            if (jobs != null) {
                for (int i = jobs.size() - 1; i >= 0; i--) {
                    JobStatus job = jobs.valueAt(i);
                    if (job.getUid() == job.getSourceUid()) {
                        total++;
                    }
                }
            }
            return total;
        }
        ... ... ... ...
    }

JobSet 有很多的其他方法:getJobsByXXX,add,remove,getXXX,等等的方法,这里我们先不看!

3 JobStore.getJobByUidAndJobId

接着是根据 uid 和 jobId,来获得一个任务,这里的目的是判断是否之前已经注册过了一个相同的任务:

    /**
     * @param uid Uid of the requesting app.
     * @param jobId Job id, specified at schedule-time.
     * @return the JobStatus that matches the provided uId and jobId, or null if none found.
     */
    public JobStatus getJobByUidAndJobId(int uid, int jobId) {
        return mJobSet.get(uid, jobId);
    }

还是调用的是 JobSet.get 方法:

        public JobStatus get(int uid, int jobId) {
            ArraySet<JobStatus> jobs = mJobs.get(uid);
            if (jobs != null) {
                for (int i = jobs.size() - 1; i >= 0; i--) {
                    JobStatus job = jobs.valueAt(i);
                    if (job.getJobId() == jobId) {
                        return job;
                    }
                }
            }
            return null;
        }

这个很简单,不详细说了!

4 JSS.cancelJobImpl

如果之前已经注册过一个任务了,需要先取消掉之前的任务!

    private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
        if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
        // 停止追踪任务!
        stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
        synchronized (mLock) {
            // Remove from pending queue.
            // 如果这个任务在等待队列中,移除它。
            if (mPendingJobs.remove(cancelled)) {
                mJobPackageTracker.noteNonpending(cancelled);
            }
            // Cancel if running.
            // 如果正在运行,取消这个任务!
            stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
            reportActive();
        }
    }

这里主要做的是:停止对这个任务的监视,同时,将这个任务移除等待队列,如果这个任务正在运行,那么就要取消它,我们一个一个来看!

4.1 JSS.stopTrackingJob

我们先来看第一个方法,参数传递:

  • JobStatus jobStatus:要被取消的任务;
  • JobStatus incomingJob:本次要注册的任务;
  • boolean writeBack:true;
    /**
     * Called when we want to remove a JobStatus object that we've finished executing. Returns the
     * object removed.
     */
    private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
            boolean writeBack) {
        synchronized (mLock) {
            // Remove from store as well as controllers.
            final boolean removed = mJobs.remove(jobStatus, writeBack);
            if (removed && mReadyToRock) {
                for (int i=0; i<mControllers.size(); i++) {
                    StateController controller = mControllers.get(i);
                    controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
                }
            }
            return removed;
        }
    }

这里的 mJobs 是 JobStore 对象!

4.1.1 JobStore.remove

我们来看看这个移除操作:

    public boolean remove(JobStatus jobStatus, boolean writeBack) {
        // 先从 mJobSet 中移除要删除的 JobStatus!
        boolean removed = mJobSet.remove(jobStatus);
        if (!removed) {
            if (DEBUG) {
                Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
            }
            return false;
        }
        // 如果 writeBack 是 true,并且 jobStatus 是 isPersisted 的,那就要更新 Jobs.xml 文件!
        if (writeBack && jobStatus.isPersisted()) {
            maybeWriteStatusToDiskAsync();
        }
        return removed;
    }

从 JobSet 集合中移除 Job,并且,如果需要写入操作,并且这个 Job 是设备重启后仍然需要保留的,那就要调用 remove 方法,将更新后的 JobSet 写入到 system/job/jobs.xml,因为所有 Persisted 为 true 的 Job ,都是会被写入到这个文件中去的,用于重启恢复!!

我们来看 maybeWriteStatusToDiskAsync 方法:

    /**
     * Every time the state changes we write all the jobs in one swath, instead of trying to
     * track incremental changes.
     * @return Whether the operation was successful. This will only fail for e.g. if the system is
     * low on storage. If this happens, we continue as normal
     */
    private void maybeWriteStatusToDiskAsync() {
        mDirtyOperations++;
        if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) {
            if (DEBUG) {
                Slog.v(TAG, "Writing jobs to disk.");
            }
            mIoHandler.post(new WriteJobsMapToDiskRunnable());
        }
    }
    ... ... ... ... ... ...
 
   /**
     * Runnable that writes {@link #mJobSet} out to xml.
     * NOTE: This Runnable locks on mLock
     */
    private class WriteJobsMapToDiskRunnable implements Runnable {
        @Override
        public void run() {
            final long startElapsed = SystemClock.elapsedRealtime();
            final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
            synchronized (mLock) {
                // Clone the jobs so we can release the lock before writing.
                // 这里是将 mJobSet 拷贝一份到 storeCopy 中。
                mJobSet.forEachJob(new JobStatusFunctor() {
                    @Override
                    public void process(JobStatus job) {
                        if (job.isPersisted()) {
                            storeCopy.add(new JobStatus(job));
                        }
                    }
                });
            }
            // 将更新后的拷贝写入 jobs.xml。
            writeJobsMapImpl(storeCopy);
            if (JobSchedulerService.DEBUG) {
                Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
                        - startElapsed) + "ms");
            }
        }

        ... ... ... ... 
    
    }

我们接着看!

4.1.2 SC.maybeStopTrackingJobLocked

接着,就是遍历控制器集合,让控制器停止对该任务的监视,参数传递:

  • JobStatus jobStatus:要被删除的 Job!
  • JobStatus incomingJob:同一个 uid,同一个 jobId 的要被注册执行的 Job!
  • boolean forUpdate:false
    /**
     * Remove task - this will happen if the task is cancelled, completed, etc.
     */
    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate);
StateController 是一个抽象类,具体的实现,我们在 JobSchedulerService 的构造器中有看到:
        mControllers.add(ConnectivityController.get(this));
        mControllers.add(TimeController.get(this));
        mControllers.add(IdleController.get(this));
        mControllers.add(BatteryController.get(this));
        mControllers.add(AppIdleController.get(this));
        mControllers.add(ContentObserverController.get(this));
        mControllers.add(DeviceIdleJobsController.get(this));

这里有很多的控制器:

4.1.2.1 ConnectivityController

我们先来看看 ConnectivityController 类的方法:

    @Override
    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate) {
        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()
                || jobStatus.hasNotRoamingConstraint()) {
            mTrackedJobs.remove(jobStatus);
        }
    }

其他控制器的方法很类似的哦!

4.2 JobPackageTracker.noteNonpending

这个就是这样的

    public void noteNonpending(JobStatus job) {
        final long now = SystemClock.uptimeMillis();
        mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
        rebatchIfNeeded(now);
    }

这里我们先不看!

4.3 JSS.stopJobOnServiceContextLocked

如果 Job 有在运行,那就停止它,参数传递:cancelled, JobParameters.REASON_CANCELED

    private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
        for (int i=0; i<mActiveServices.size(); i++) {
            JobServiceContext jsc = mActiveServices.get(i);
            final JobStatus executing = jsc.getRunningJob();
            if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
                jsc.cancelExecutingJob(reason);
                return true;
            }
        }
        return false;
    }

这里调用了 JobServiceContext 的 cancelExecutingJob 这个方法:

4.3.1 JobServiceContext.cancelExecutingJob

我们进入 JobServiceContext 文件中来看看:

    /** Called externally when a job that was scheduled for execution should be cancelled. */
    void cancelExecutingJob(int reason) {
        mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
    }

其中:mCallbackHandler = new JobServiceHandler(looper),这里的 looper 是 system_server 的主线程的 looper,这个在第二篇初始化和启动就可以看到!

这里我们向 JobServiceHandler 发送了一个 MSG_CANCEL 的消息,我们去看看:

   private class JobServiceHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                
                ... ... ... ...

                case MSG_CANCEL: // 收到 MSG_CANCEL 的消息!
                    // 处理出入的参数:reason
                    if (mVerb == VERB_FINISHED) {
                        if (DEBUG) {
                            Slog.d(TAG, "Trying to process cancel for torn-down context, ignoring.");
                        }
                        return;
                    }
                    mParams.setStopReason(message.arg1); // 保存停止的原因!
                    if (message.arg1 == JobParameters.REASON_PREEMPT) {
                        mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
                                NO_PREFERRED_UID;
                    }
                    handleCancelH();
                    break;
                case MSG_TIMEOUT:
                    handleOpTimeoutH();
                    break;
                case MSG_SHUTDOWN_EXECUTION:
                    closeAndCleanupJobH(true /* needsReschedule */);
                    break;
                default:
                    Slog.e(TAG, "Unrecognised message: " + message);
            }
        }

    ... ... ... ...

  }

我们进入到 handleCancelH 方法中来看看:

        /**
         * A job can be in various states when a cancel request comes in:
         * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
         *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
         *     _STARTING   -> Mark as cancelled and wait for
         *                    {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
         *     _EXECUTING  -> call {@link #sendStopMessageH}}, but only if there are no callbacks
         *                      in the message queue.
         *     _ENDING     -> No point in doing anything here, so we ignore.
         */
        private void handleCancelH() {
            if (JobSchedulerService.DEBUG) {
                Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
                        + VERB_STRINGS[mVerb]);
            }
            switch (mVerb) {
                case VERB_BINDING:
                case VERB_STARTING:
                    mCancelled.set(true);
                    break;
                case VERB_EXECUTING:
                    if (hasMessages(MSG_CALLBACK)) {
                        // If the client has called jobFinished, ignore this cancel.
                        return;
                    }
                    sendStopMessageH();
                    break;
                case VERB_STOPPING:
                    // Nada.
                    break;
                default:
                    Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
                    break;
            }
        }

mVerb 使用来保存 Job 的状态的,一个 Job 在 JobServiceContext 中会有如下的几种状态:

  • VERB_BINDING
  • _STARTING
  • _EXECUTING
  • _ENDING

这里我先不看,下面的调用流程会涉及到的!

5 JSS.startTrackingJob

接下来,就是开始 track 任务,参数传递:

  • JobStatus jobStatus:本次注册的 job!
  • JobStatus lastJob:同一个 uid,同一个 jobId,上次注册的已经被取消的任务,可以为 null!
    /**
     * Called when we have a job status object that we need to insert in our
     * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
     * about.
     */
    private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
        synchronized (mLock) {
            // 添加到 JobStore 中!
            final boolean update = mJobs.add(jobStatus);
            if (mReadyToRock) {
                for (int i = 0; i < mControllers.size(); i++) {
                    StateController controller = mControllers.get(i);
                    if (update) {
                        controller.maybeStopTrackingJobLocked(jobStatus, null, true);
                    }
                    controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
                }
            }
        }
    }

第一步,可以看到,先将这一次要注册执行的任务,加入到 JobStore 中:

5.1 JobStore.add

这里是将要好注册执行的 Job 添加到 JobStore 中,这里和上面有些类似:

     * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
     * it will be replaced.
     * @param jobStatus Job to add.
     * @return Whether or not an equivalent JobStatus was replaced by this operation.
     */
    public boolean add(JobStatus jobStatus) {
        boolean replaced = mJobSet.remove(jobStatus);
        mJobSet.add(jobStatus); // 添加到 JobSets 集合中!
        if (jobStatus.isPersisted()) {
            maybeWriteStatusToDiskAsync();
        }
        if (DEBUG) {
            Slog.d(TAG, "Added job status to store: " + jobStatus);
        }
        return replaced;
    }

如果这个 Job 是 isPersisted 的,那就需要更新 Jobs.xml 文件!

5.2 SC.maybeStopTrackingJobLocked

这里先要停止 track 这个任务,参数传递:

  • JobStatus jobStatus:要被注册执行的新的 Job!
  • JobStatus incomingJob:null
  • boolean forUpdate:true
    /**
     * Remove task - this will happen if the task is cancelled, completed, etc.
     */
    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate);

接着看!

5.3 SC.maybeStartTrackingJobLocked

接着,调用这个方法,来开始 track 任务,方法参数:

  • JobStatus jobStatus:这次要注册的任务
  • JobStatus lastJob:同一个 uid,同一个 jobId 已经被取消掉的上一个任务,可以为 null!
    /**
     * Implement the logic here to decide whether a job should be tracked by this controller.
     * This logic is put here so the JobManager can be completely agnostic of Controller logic.
     * Also called when updating a task, so implementing controllers have to be aware of
     * preexisting tasks.
     */
    public abstract void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob);

这里同样是一个抽象接口,具体的实现是子类:

5.3.1 ConnectivityController

让我们来看看 ConnectivityController 对象的这个方法:

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()
                || jobStatus.hasNotRoamingConstraint()) {
            updateConstraintsSatisfied(jobStatus);
            // 将这个 Job 添加到 ConnectivityController 的跟踪列表中!
            mTrackedJobs.add(jobStatus);
        }
    }

首先,判断 Job 是否有设置和 Connectivity 网络相关的属性:

  • jobStatus.hasConnectivityConstraint()
  • jobStatus.hasUnmeteredConstraint()
  • jobStatus.hasNotRoamingConstraint()

这里我们先不看!

6 JSS.JobHandler

接着,就是向 JobHandler 发送了 MSG_CHECK_JOB 的消息,我们来看看:

    private class JobHandler extends Handler {
        public JobHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message message) {
            synchronized (mLock) {
                if (!mReadyToRock) {
                    return;
                }
            }
            switch (message.what) {
                case MSG_JOB_EXPIRED:

                    ... ... ... ...

                    break;
                case MSG_CHECK_JOB: // 接收到 MSG_CHECK_JOB 的消息!
                    synchronized (mLock) {
                        if (mReportedActive) { // 为 true,表示 JSS 通知了设备管理器,自己处于活跃状态!
                            // if jobs are currently being run, queue all ready jobs for execution.
                            queueReadyJobsForExecutionLockedH();
                        } else {
                            // Check the list of jobs and run some of them if we feel inclined.
                            maybeQueueReadyJobsForExecutionLockedH();
                        }
                    }
                    break;
                case MSG_CHECK_JOB_GREEDY:
                    synchronized (mLock) {
                        queueReadyJobsForExecutionLockedH();
                    }
                    break;
                case MSG_STOP_JOB:
                    cancelJobImpl((JobStatus)message.obj, null);
                    break;
            }
            // 处理 mPendingJobs 中的任务!
            maybeRunPendingJobsH();
            // Don't remove JOB_EXPIRED in case one came along while processing the queue.
            removeMessages(MSG_CHECK_JOB);
        }
       
        ... ... ... ... ...
}

接着,收到了 MSG_CHECK_JOB 的消息,开始遍历队列,执行准备好的任务!

6.1 queueReadyJobsForExecutionLockedH

下面是这个方法的代码:

        /**
         * Run through list of jobs and execute all possible - at least one is expired so we do
         * as many as we can.
         */
        private void queueReadyJobsForExecutionLockedH() {
            if (DEBUG) {
                Slog.d(TAG, "queuing all ready jobs for execution:");
            }
            noteJobsNonpending(mPendingJobs);
            mPendingJobs.clear();
            mJobs.forEachJob(mReadyQueueFunctor);
            mReadyQueueFunctor.postProcess();
            if (DEBUG) {
                final int queuedJobs = mPendingJobs.size();
                if (queuedJobs == 0) {
                    Slog.d(TAG, "No jobs pending.");
                } else {
                    Slog.d(TAG, queuedJobs + " jobs queued.");
                }
            }
        }

啦啦啦啦啦啦,天啦撸啊撸!

6.2 maybeQueueReadyJobsForExecutionLockedH

这个方法的代码:

        private void maybeQueueReadyJobsForExecutionLockedH() {
            if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
            noteJobsNonpending(mPendingJobs);
            // 清除 mPendingJobs 集合!
            mPendingJobs.clear();
            // 将准备好的 Job 加入到 JobHandler 内部的 runnableJobs 集合中。
            mJobs.forEachJob(mMaybeQueueFunctor);
            // 将 runnableJobs 加入到 mPendingJobs 集合中!
            mMaybeQueueFunctor.postProcess();
        }

首先清除了:mPendingJobs 集合!
接着,这里传入了 mMaybeQueueFunctor 对象!

    public void forEachJob(JobStatusFunctor functor) {
        mJobSet.forEachJob(functor);
    }

进入了 JobSet:

        public void forEachJob(JobStatusFunctor functor) {
            for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
                ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
                for (int i = jobs.size() - 1; i >= 0; i--) {
                    // 对每个 uid 的应用程序的 Job,执行下面操作:
                    functor.process(jobs.valueAt(i));
                }
            }
        }

可以看出,这对所有的 Job,都调用了 MaybeReadyJobQueueFunctor 的 process 方法:

        class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
            int chargingCount;
            int idleCount;
            int backoffCount;
            int connectivityCount;
            int contentCount;
            List<JobStatus> runnableJobs; // 符合条件的即将运行的 Job
            public MaybeReadyJobQueueFunctor() {
                reset();
            }
            // Functor method invoked for each job via JobStore.forEachJob()
            @Override
            public void process(JobStatus job) {
                // 判断这个 Job 是否是准备了!
                if (isReadyToBeExecutedLocked(job)) {
                    try {
                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
                                job.getJob().getService().getPackageName())
                                == ActivityManager.APP_START_MODE_DISABLED) {
                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
                                    + job.getJob().toString() + " -- package not allowed to start");
                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
                            return;
                        }
                    } catch (RemoteException e) {
                    }
                    if (job.getNumFailures() > 0) {
                        backoffCount++;
                    }
                    if (job.hasIdleConstraint()) {
                        idleCount++;
                    }
                    if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
                            || job.hasNotRoamingConstraint()) {
                        connectivityCount++;
                    }
                    if (job.hasChargingConstraint()) {
                        chargingCount++;
                    }
                    if (job.hasContentTriggerConstraint()) {
                        contentCount++;
                    }
                    if (runnableJobs == null) {
                        runnableJobs = new ArrayList<>();
                    }
                    // 将满足条件的 Job,先加入到 runnableJobs 集合!
                    runnableJobs.add(job);
                } else if (areJobConstraintsNotSatisfiedLocked(job)) {
                    stopJobOnServiceContextLocked(job,
                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                }
            }

这里调用了 isReadyToBeExecutedLocked 方法来判断,这个 job 是否已经准备好了:

        /**
         * Criteria for moving a job into the pending queue:
         *      - It's ready.
         *      - It's not pending.
         *      - It's not already running on a JSC.
         *      - The user that requested the job is running.
         *      - The component is enabled and runnable.
         */
        private boolean isReadyToBeExecutedLocked(JobStatus job) {
            final boolean jobReady = job.isReady();
            final boolean jobPending = mPendingJobs.contains(job);
            final boolean jobActive = isCurrentlyActiveLocked(job);
            final int userId = job.getUserId();
            final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
            final boolean componentPresent;
            try {
                componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
                        job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                        userId) != null);
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            }
            if (DEBUG) {
                Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
                        + " ready=" + jobReady + " pending=" + jobPending
                        + " active=" + jobActive + " userStarted=" + userStarted
                        + " componentPresent=" + componentPresent);
            }
            return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
        }

可以看出,一个准备好的 Job 要满足这些条件:
*- It's ready.
*- It's not pending.
*- It's not already running on a JSC.
*- The user that requested the job is running.
*- The component is enabled and runnable.
(英文很简单,我就不翻译了,哈哈哈哈,逼近记不住键盘!)
最后,调用 MaybeQueueFunctor.postProcess 方法:

            public void postProcess() {
                if (backoffCount > 0 ||
                        idleCount >= mConstants.MIN_IDLE_COUNT ||
                        connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
                        chargingCount >= mConstants.MIN_CHARGING_COUNT ||
                        contentCount >= mConstants.MIN_CONTENT_COUNT ||
                        (runnableJobs != null
                                && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
                    if (DEBUG) {
                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
                    }

                    noteJobsPending(runnableJobs);

                    // 将 runnableJobs 加入到 mPendingJobs 集合中!
                    mPendingJobs.addAll(runnableJobs);
                } else {
                    if (DEBUG) {
                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                    }
                }
                // Be ready for next time
                reset();
            }

该功能:
先将 JobStore 的 JobSet 中满足条件的 job 对应的 JobStatus 加入 runnableJobs 队列;
再将 runnableJobs 中满足触发条件的 JobStatus 加入到 mPendingJobs 队列;

6.3 maybeRunPendingJobsH

接着,就是处理 mPendingJobs 中的 Job:

        /**
         * Reconcile jobs in the pending queue against available execution contexts.
         * A controller can force a job into the pending queue even if it's already running, but
         * here is where we decide whether to actually execute it.
         */
        private void maybeRunPendingJobsH() {
            synchronized (mLock) {
                if (DEBUG) {
                    Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
                }
                assignJobsToContextsLocked();
                reportActive();
            }
        }

结合就是调用 JSS 的 assignJobsToContextLocked 方法:

6.3.1 JSS.assignJobsToContextsLocked

这个方法其实从名字上就可以看出,就是把 Job 分配给 JobServiceContext 方法,我们去那个方法里面看一下:

    /**
     * Takes jobs from pending queue and runs them on available contexts.
     * If no contexts are available, preempts lower priority jobs to
     * run higher priority ones.
     * Lock on mJobs before calling this function.
     */
    private void assignJobsToContextsLocked() {
        if (DEBUG) {
            Slog.d(TAG, printPendingQueue());
        }
        // 根据当前系统的内存级别,设定最大的活跃 Job 数,保存到 mMaxActiveJobs!
        int memLevel;
        try {
            memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
        } catch (RemoteException e) {
            memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
        }
        switch (memLevel) {
            case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
                mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
                break;
            case ProcessStats.ADJ_MEM_FACTOR_LOW:
                mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
                break;
            case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
                mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
                break;
            default:
                mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
                break;
        }

        JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap; // 大小为 16,和 mActiveServices 相对应!
        boolean[] act = mTmpAssignAct;

        int[] preferredUidForContext = mTmpAssignPreferredUidForContext; // 大小为 16,和 mActiveServices 相对应!
        int numActive = 0;
        int numForeground = 0;
        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
            final JobServiceContext js = mActiveServices.get(i);
            final JobStatus status = js.getRunningJob();
            // 初始化 contextIdToJobMap,里面保存当前运行着的 Job,和空闲的用于增加的 Job 位置!
            // 然后,分别计算活跃 job 和前台 job 的个数! 
            if ((contextIdToJobMap[i] = status) != null) {
                numActive++;
                if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
                    numForeground++;
                }
            }
            act[i] = false;
            preferredUidForContext[i] = js.getPreferredUid();
        }
        if (DEBUG) {
            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
        }
        
        // 遍历 mPendingJos 任务集合,开始做准备工作!
        for (int i=0; i<mPendingJobs.size(); i++) {
            JobStatus nextPending = mPendingJobs.get(i);
            // If job is already running, go to next job.
            // 判断当前的 Job 是不是在 contextIdToJobMap 中了,即他是不是已经在运行了!
            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
            if (jobRunningContext != -1) {
                continue;
            }
            // 计算 Job 的优先级!
            final int priority = evaluateJobPriorityLocked(nextPending);
            nextPending.lastEvaluatedPriority = priority;
            // Find a context for nextPending. The context should be available OR
            // it should have lowest priority among all running jobs
            // (sharing the same Uid as nextPending)
            // 遍历 contextIdToJobMap
            // 给这个即将被执行的 Job 找一个合适的 context,至少要满足两个中的一个要求:1、可利用;2、优先级最低!
            int minPriority = Integer.MAX_VALUE;
            int minPriorityContextId = -1;
            for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
                JobStatus job = contextIdToJobMap[j];
                int preferredUid = preferredUidForContext[j];
                if (job == null) { 
                    if ((numActive < mMaxActiveJobs ||
                            (priority >= JobInfo.PRIORITY_TOP_APP &&
                                    numForeground < mConstants.FG_JOB_COUNT)) &&
                            (preferredUid == nextPending.getUid() ||
                                    preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
                        // This slot is free, and we haven't yet hit the limit on
                        // concurrent jobs...  we can just throw the job in to here.
                        // 如果 context 原有的 job 为空,并且满足以下条件:
                        // 活跃 job 数小于最大活跃数,或者即将执行的 job 的优先级不低于 PRIORITY_TOP_APP,并且前台 job 数小于 4,那么这个 context 是合适的!
                        minPriorityContextId = j;
                        break;
                    }
                    // No job on this context, but nextPending can't run here because
                    // the context has a preferred Uid or we have reached the limit on
                    // concurrent jobs.
                    continue;
                }
                if (job.getUid() != nextPending.getUid()) { // 如果 context 原有的 job 不为空,且 uid 和即将执行的 job 不一样,那么这个 context 不合适!
                    continue;
                }
                if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) { // 如果 context 原有的 job 不为空,且 uid 相同,但优先级不低于即将执行的 job,那么这个 context 不合适!
                    continue;
                }
                if (minPriority > nextPending.lastEvaluatedPriority) { // 如果 context 原有的 job 不为空,且 uid 相同,且优先级低于即将执行的 job,那么这个 context 合适!
                    minPriority = nextPending.lastEvaluatedPriority;
                    minPriorityContextId = j;
                }
            }

            if (minPriorityContextId != -1) { // 找到了合适的 context 的下标!
                // 将这个要被执行的 Job 放入合适的 Context 中!
                contextIdToJobMap[minPriorityContextId] = nextPending;
                // 对应的 act 位置为 true,表示可以运行!
                act[minPriorityContextId] = true;
                numActive++; // 
                if (priority >= JobInfo.PRIORITY_TOP_APP) {
                    numForeground++;
                }
            }
        }
        if (DEBUG) {
            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
        }
        mJobPackageTracker.noteConcurrency(numActive, numForeground);
        // 执行任务!
        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
            boolean preservePreferredUid = false;
            if (act[i]) { // art 为 true 的位置,对应的就是上面添加的即将执行的 Job
                JobStatus js = mActiveServices.get(i).getRunningJob();
                if (js != null) {
                    if (DEBUG) {
                        Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
                    }
                    // preferredUid will be set to uid of currently running job.
                    // 如果这个 context 已经在运行了一个优先级低的 job,那就要取消它!
                    mActiveServices.get(i).preemptExecutingJob();
                    preservePreferredUid = true;
                } else {
                    // 从 contextIdToJobMap 中获得即将执行的 Job!
                    final JobStatus pendingJob = contextIdToJobMap[i];
                    if (DEBUG) {
                        Slog.d(TAG, "About to run job on context "
                                + String.valueOf(i) + ", job: " + pendingJob);
                    }
                    // 通知控制器!
                    for (int ic=0; ic<mControllers.size(); ic++) {
                        mControllers.get(ic).prepareForExecutionLocked(pendingJob);
                    }
                    // 使用对应的 JobServiceContext 来执行 Job
                    if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
                        Slog.d(TAG, "Error executing " + pendingJob);
                    }
                    // Job 已经开始执行了,从 mPendingJobs 中移除这个 Job!
                    if (mPendingJobs.remove(pendingJob)) {
                        mJobPackageTracker.noteNonpending(pendingJob);
                    }
                }
            }

            if (!preservePreferredUid) {
                mActiveServices.get(i).clearPreferredUid();
            }
        }
    }

接着我们继续来看:

6.3.1.1 JSS.findJobContextIdFromMap

这个方法的作用很简单,就是判断 job 是否已经在 map 集合中了!

    int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
        for (int i=0; i<map.length; i++) {
            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
                return i;
            }
        }
        return -1;
    }

6.3.1.2 JSS.evaluateJobPriorityLocked

这个方法是为 job 计算优先级:

    private int evaluateJobPriorityLocked(JobStatus job) {
        int priority = job.getPriority();
        if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
            return adjustJobPriority(priority, job);
        }
        int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
        if (override != 0) {
            return adjustJobPriority(override, job);
        }
        return adjustJobPriority(priority, job);
    }

接着:

6.3.1.3 JSC.preemptExecutingJob

这里调用了 JobServiceContext 的 preemptExecutingJob 方法来取消优先级低的任务,之前有说过,如果 Context 已经在执行一个 job,并且这个 job 和即将被执行的 job 属于同一个 apk,那么要取消优先级低的!

    void preemptExecutingJob() {
        Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
        // 参数为 JobParameters.REASON_PREEMPT,表示要取代!
        m.arg1 = JobParameters.REASON_PREEMPT;
        m.sendToTarget();
    }

这个消息会发给 JSC 内部的 JobServiceHandler 来处理:

JobServiceHandler.MSG_CANCEL

我们接着来看:

    private class JobServiceHandler extends Handler {
        JobServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                ... ... ... ...
                case MSG_CANCEL:
                    if (mVerb == VERB_FINISHED) { // 这个 mVerb 表示的是,这个 Context 的 job 的当前状态!
                        if (DEBUG) {
                            Slog.d(TAG,
                                   "Trying to process cancel for torn-down context, ignoring.");
                        }
                        return;
                    }
                    mParams.setStopReason(message.arg1);
                    if (message.arg1 == JobParameters.REASON_PREEMPT) {
                        mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
                                NO_PREFERRED_UID;
                    }
                    // 执行取消操作!
                    handleCancelH();
                    break;
                case MSG_TIMEOUT:
                    handleOpTimeoutH();
                    break;
                case MSG_SHUTDOWN_EXECUTION:
                    closeAndCleanupJobH(true /* needsReschedule */);
                    break;
                default:
                    Slog.e(TAG, "Unrecognised message: " + message);
            }
        }
       
        ... ... ... ... ... ...

        private void handleCancelH() {
            if (JobSchedulerService.DEBUG) {
                Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
                        + VERB_STRINGS[mVerb]);
            }
            switch (mVerb) { // 这里因为是取消的是这个 context 里面正在执行的 job,所以 mVerb 的值为 VERB_EXECUTING!
                case VERB_BINDING:
                case VERB_STARTING:
                    mCancelled.set(true);
                    break;
                case VERB_EXECUTING:
                    if (hasMessages(MSG_CALLBACK)) { // 如果 client 已经调用了 jobFinished 方法结束了 job,那就 return!
                        // If the client has called jobFinished, ignore this cancel.
                        return;
                    }
                    sendStopMessageH(); // 否则,就再次发送停止的消息!
                    break;
                case VERB_STOPPING:
                    // Nada.
                    break;
                default:
                    Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
                    break;
            }
        }
        ... ... ... ...
    }

如果有优先级低的 job 正在运行,那就 stop job,这时 mVerb 会从:VERB_EXECUTING -> VERB_STOPPING.

        /**
         * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
         * VERB_STOPPING.
         */
        private void sendStopMessageH() {
            removeOpTimeOut();
            if (mVerb != VERB_EXECUTING) {
                Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
                closeAndCleanupJobH(false /* reschedule */);
                return;
            }
            try {
                mVerb = VERB_STOPPING; // mVerb 状态变为 VERB_STOPPING!
                scheduleOpTimeOut();
                service.stopJob(mParams); // 这里就调用了 client 的 JobService 的
            } catch (RemoteException e) {
                Slog.e(TAG, "Error sending onStopJob to client.", e);
                closeAndCleanupJobH(false /* reschedule */);
            }
        }

这里的 service 是一个 IJobService 对象,对应的是一个 .aidl 文件,表示的是 Binder 服务端,代码定义位于:JobService 中。

JobService

注意:这里对于 Binder 机制来说:应用程序 client 的 JobService 所在进程是 Binder 服务端,JobServiceContext 所在的进程 system_server 是 Binder 客户端!也就是说,应用程序定义的 JobService 是被 JobSerivceContext 来 bind 的,所以,你会发现,你无法 override JobService 的 onbind 方法!

    static final class JobInterface extends IJobService.Stub {
        final WeakReference<JobService> mService;
        JobInterface(JobService service) {
            mService = new WeakReference<>(service);
        }
        @Override
        public void startJob(JobParameters jobParams) throws RemoteException {
            JobService service = mService.get();
            if (service != null) {
                service.ensureHandler();
                Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams);
                m.sendToTarget();
            }
        }
        @Override
        public void stopJob(JobParameters jobParams) throws RemoteException { // 通过 binder 机制来来调用指定应用的 JobService 的 stopJob 方法!
            JobService service = mService.get();
            if (service != null) {
                service.ensureHandler();
                // 发送到 JobService 内部的 JobHandler 对象中!
                Message m = Message.obtain(service.mHandler, MSG_STOP_JOB, jobParams);
                m.sendToTarget();
            }
        }
    }

这里发送了 MSG_STOP_JOB 消息给 JobService.JobHandler,我们去 JobHandler 内部去看看:

JobHandler

JobHandler 位于应用程序的主线程:

    /**
     * Runs on application's main thread - callbacks are meant to offboard work to some other
     * (app-specified) mechanism.
     * @hide
     */
    class JobHandler extends Handler {
        JobHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            // 这里获得传递过来的 JobParameters 
            final JobParameters params = (JobParameters) msg.obj;
            switch (msg.what) {

                ... ... ...

                case MSG_STOP_JOB:
                    try {
                        // 调用 JobService onStopJob 方法,把参数传递过去,这里是不是很熟悉,就不多说!
                        // onStopJob 会返回 true / false, true 表示还会 reschedule 这个 job!
                        boolean ret = JobService.this.onStopJob(params);
                        ackStopMessage(params, ret);
                    } catch (Exception e) {
                        Log.e(TAG, "Application unable to handle onStopJob.", e);
                        throw new RuntimeException(e);
                    }
                    break;

                ... ... ... ...

                default:
                    Log.e(TAG, "Unrecognised message received.");
                    break;
            }
        }
        ... ... ... ... ...
        private void ackStopMessage(JobParameters params, boolean reschedule) {
            // 这里获得了 IJobCallback 对象,这里显示是 Binder 机制,服务端是 JobServiceContext
            final IJobCallback callback = params.getCallback();
            final int jobId = params.getJobId();
            if (callback != null) {
                try {
                    // 发送消息给 JobServiceContext
                    callback.acknowledgeStopMessage(jobId, reschedule);
                } catch(RemoteException e) {
                    Log.e(TAG, "System unreachable for stopping job.");
                }
            } else {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Attempting to ack a job that has already been processed.");
                }
            }
        }
    }

这里的 IJobCallback 又是使用了 Binder 机制,Binder 客户端是应用程序的 JobService 所在进程,Binder 服务端是 JobServiceContext 所在的进程,最后调用的是 JobServiceContext.acknowledgeStopMessage 方法:

JobServiceContext

通过 Binder 机制,将回调信息发回给 JobServiceContext

    @Override
    public void acknowledgeStopMessage(int jobId, boolean reschedule) {
        if (!verifyCallingUid()) {
            return;
        }
        // 发送 MSG_CALLBACK 给 JobServiceHandler
        mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
                .sendToTarget();
    }

然后又会发送 MSG_CALLBACK 给 JobServiceContext.JobServiceHandler,这里我们只看关键代码:

JobServiceHandler

JobServiceHandler 同样的也是位于主线程:

                case MSG_CALLBACK:
                    if (DEBUG) {
                        Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
                                + " v:" + VERB_STRINGS[mVerb]);
                    }
                    removeOpTimeOut();
                    if (mVerb == VERB_STARTING) {
                        final boolean workOngoing = message.arg2 == 1;
                        handleStartedH(workOngoing);
                    } else if (mVerb == VERB_EXECUTING ||
                            mVerb == VERB_STOPPING) { // 从前面跟代码,可以看出 mVerb 的值为 VERB_STOPPING.
                        final boolean reschedule = message.arg2 == 1;
                        handleFinishedH(reschedule);
                    } else {
                        if (DEBUG) {
                            Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
                        }
                    }
                    break;

接着,调用 JobServiceContext 的 handleFinishedH 方法:

        private void handleFinishedH(boolean reschedule) {
            switch (mVerb) {
                case VERB_EXECUTING:
                case VERB_STOPPING:
                    closeAndCleanupJobH(reschedule); // 调用了 closeAndCleanupJobH,reschedul
                    break;
                default:
                    Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
                            "executed. Was " + VERB_STRINGS[mVerb] + ".");
            }
        }

接着进入 closeAndCleanupJobH 方法:

        /**
         * The provided job has finished, either by calling
         * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
         * or from acknowledging the stop message we sent. Either way, we're done tracking it and
         * we want to clean up internally.
         */
        private void closeAndCleanupJobH(boolean reschedule) {
            final JobStatus completedJob;
            synchronized (mLock) {
                if (mVerb == VERB_FINISHED) {
                    return;
                }
                completedJob = mRunningJob;
                mJobPackageTracker.noteInactive(completedJob);
                try {
                    mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
                            mRunningJob.getSourceUid());
                } catch (RemoteException e) {
                    // Whatever.
                }
                if (mWakeLock != null) {
                    mWakeLock.release();
                }
                mContext.unbindService(JobServiceContext.this); // 取消绑定 JobService
                mWakeLock = null;
                mRunningJob = null;
                mParams = null;
                mVerb = VERB_FINISHED;
                mCancelled.set(false);
                service = null;
                mAvailable = true;
            }
            removeOpTimeOut(); // 移除已经处理的消息
            removeMessages(MSG_CALLBACK);
            removeMessages(MSG_SERVICE_BOUND);
            removeMessages(MSG_CANCEL);
            removeMessages(MSG_SHUTDOWN_EXECUTION);
            // 调用了 mCompletedListener 的 onJobCompleted 方法!
            mCompletedListener.onJobCompleted(completedJob, reschedule);
        }
    }

这里 mCompletedListener 大家去看 JobServiceContext 的初始化,也就是第一篇,其实就是 JobSchedulerService.this:

JobSchedulerService.onJobCompleted
    @Override
    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
        if (DEBUG) {
            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
        }
        // Do not write back immediately if this is a periodic job. The job may get lost if system
        // shuts down before it is added back.
        // 停止 track 这个 job!
        if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
            if (DEBUG) {
                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
            }
            // We still want to check for jobs to execute, because this job may have
            // scheduled a new job under the same job id, and now we can run it.
            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
            return;
        }
        // Note: there is a small window of time in here where, when rescheduling a job,
        // we will stop monitoring its content providers.  This should be fixed by stopping
        // the old job after scheduling the new one, but since we have no lock held here
        // that may cause ordering problems if the app removes jobStatus while in here.
        // 重新 schedule 这个 job
        if (needsReschedule) {
            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
            startTrackingJob(rescheduled, jobStatus);
        } else if (jobStatus.getJob().isPeriodic()) {
            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
            startTrackingJob(rescheduledPeriodic, jobStatus);
        }
        reportActive();
        // 发送 MSG_CHECK_JOB_GREEDY 给 JobSchedulerService.JobHandler
        mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
    }

这里首先调用了 stopTrackingJob 将这个 job 从 JobStore 和 controller 中移除:

    /**
     * Called when we want to remove a JobStatus object that we've finished executing. Returns the
     * object removed.
     */
    private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
            boolean writeBack) {
        synchronized (mLock) {
            // Remove from store as well as controllers.
            // 从 JobStore 中移除这个 job,如果 writeback 为 true,还要更新本地的 job.xml 文件!
            final boolean removed = mJobs.remove(jobStatus, writeBack);
            if (removed && mReadyToRock) {
                for (int i=0; i<mControllers.size(); i++) {
                    StateController controller = mControllers.get(i);
                    // 从 Controller 的跟踪队列中移除!
                    controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
                }
            }
            return removed;
        }
    }

最后是发 MSG_CHECK_JOB_GREEDY 给 JobHandler:

JobHandler
    private class JobHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            synchronized (mLock) {
                if (!mReadyToRock) {
                    return;
                }
            }
            switch (message.what) {
                ... ... ... ...
                case MSG_CHECK_JOB_GREEDY:
                    synchronized (mLock) {
                        // 作用和 maybeQueueReadyJobsForExecutionLockedH 一样的都是更新 mPendingJobs 集合!
                        queueReadyJobsForExecutionLockedH();
                    }
                    break;
                ... ... ... ...
            }
            // 再次执行 mPendingJobs 中的 job
            maybeRunPendingJobsH();
            // Don't remove JOB_EXPIRED in case one came along while processing the queue.
            removeMessages(MSG_CHECK_JOB);
        }
        ... ... ... ...
    }

最后继续 maybeRunPendingJobsH,这里又回到了第 6 节了,就不多说了!

6.3.1.4 SC.prepareForExecutionLocked

这个方法其实很简单,就是一个抽象类的方法:

    /**
     * Optionally implement logic here to prepare the job to be executed.
     */
    public void prepareForExecutionLocked(JobStatus jobStatus) {
    }

表示通知 StateController,做好准备,具体实现是在 Controller 中,我们先看看 ConnectivityController,其他类似:

6.3.1.5 JSC.executeRunnableJob

这里就是调用 JobSchedulerContext 方法来执行 Job:

    /**
     * Give a job to this context for execution. Callers must first check {@link #getRunningJob()}
     * and ensure it is null to make sure this is a valid context.
     * @param job The status of the job that we are going to run.
     * @return True if the job is valid and is running. False if the job cannot be executed.
     */
    boolean executeRunnableJob(JobStatus job) {
        synchronized (mLock) {
            if (!mAvailable) {
                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
                return false;
            }
            mPreferredUid = NO_PREFERRED_UID;
            // 保存到 mRunningJob 中,
            mRunningJob = job;
            final boolean isDeadlineExpired =
                    job.hasDeadlineConstraint() &&
                            (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
            Uri[] triggeredUris = null;
            if (job.changedUris != null) {
                triggeredUris = new Uri[job.changedUris.size()];
                job.changedUris.toArray(triggeredUris);
            }
            String[] triggeredAuthorities = null;
            if (job.changedAuthorities != null) {
                triggeredAuthorities = new String[job.changedAuthorities.size()];
                job.changedAuthorities.toArray(triggeredAuthorities);
            }
            // 创建 job 需要的 JobParamters
            mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired,
                    triggeredUris, triggeredAuthorities);
            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
            // mVerb 的值变为 VERB_BINDING!
            mVerb = VERB_BINDING;
            scheduleOpTimeOut();
            // 这里很关键,bind 应用程序中注册的 JobService!
            final Intent intent = new Intent().setComponent(job.getServiceComponent());
            boolean binding = mContext.bindServiceAsUser(intent, this,
                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
                    new UserHandle(job.getUserId()));
            if (!binding) {
                if (DEBUG) {
                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                }
                mRunningJob = null;
                mParams = null;
                mExecutionStartTimeElapsed = 0L;
                mVerb = VERB_FINISHED;
                removeOpTimeOut();
                return false;
            }
            // 记录信息!
            try {
                mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
            } catch (RemoteException e) {
                // Whatever.
            }
            mJobPackageTracker.noteActive(job);
            mAvailable = false;
            return true;
        }
    }

这便是由 system_server 进程的主线程来执行 bind Service 的方式来拉起的进程,当服务启动后回调到发起端的 onServiceConnected。

关于 bindService 这里不讨论,另开一贴!

6.3.1.5.1 JSC.onServiceConnected

bind 成功后,作为 Binder 机制的客户端,JobServiceContext 的

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        JobStatus runningJob;
        synchronized (mLock) {
            // This isn't strictly necessary b/c the JobServiceHandler is running on the main
            // looper and at this point we can't get any binder callbacks from the client. Better
            // safe than sorry.
            runningJob = mRunningJob;
        }
        // 异常检测
        if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
            mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
            return;
        }
        // 获得了应用程序的 JobService 的代理对象!
        this.service = IJobService.Stub.asInterface(service);
        final PowerManager pm =
                (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                runningJob.getTag());
        wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
        wl.setReferenceCounted(false);
        wl.acquire();
        synchronized (mLock) {
            // We use a new wakelock instance per job.  In rare cases there is a race between
            // teardown following job completion/cancellation and new job service spin-up
            // such that if we simply assign mWakeLock to be the new instance, we orphan
            // the currently-live lock instead of cleanly replacing it.  Watch for this and
            // explicitly fast-forward the release if we're in that situation.
            if (mWakeLock != null) {
                Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
                        + " tag=" + mWakeLock.getTag());
                mWakeLock.release();
            }
            mWakeLock = wl;
        }
        // 发送 MSG_SERVICE_BOUND 给 JobServiceHandler 中!
        mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
    }

可以看到,这里会发送消息到 JobServiceHandler 中:

6.3.1.5.2 JSC.JobServiceHandler

JobServiceHandler 方法也是在主线程中!

    private class JobServiceHandler extends Handler {
        JobServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MSG_SERVICE_BOUND: // 绑定成功的消息!
                    removeOpTimeOut();
                    handleServiceBoundH();
                    break;

                ... ... ... ...

                case MSG_SHUTDOWN_EXECUTION: // 绑定是出现异常的消息!
                    closeAndCleanupJobH(true /* needsReschedule */);
                    break;
                default:
                    Slog.e(TAG, "Unrecognised message: " + message);
            }
        }
     
        ... ... ... ...
    
    }

接着我们来分别看一下:

6.3.1.5.2.1 JSH.MSG_SERVICE_BOUND

收到这个消息后,调用 handleServiceBoundH 方法:

        /** Start the job on the service. */
        private void handleServiceBoundH() {
            if (DEBUG) {
                Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString());
            }
            if (mVerb != VERB_BINDING) { // 状态异常
                Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
                        + VERB_STRINGS[mVerb]);
                closeAndCleanupJobH(false /* reschedule */);
                return;
            }
            if (mCancelled.get()) { // 如果发现 JobService 对应的 job 被取消了,那就 return!
                if (DEBUG) {
                    Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
                            + mRunningJob);
                }
                closeAndCleanupJobH(true /* reschedule */);
                return;
            }
            try {
                // mVerb 的值设置为 VERB_STARTING!
                mVerb = VERB_STARTING;
                scheduleOpTimeOut();
                // 这里调用了 JobService 的 startJob 方法
                service.startJob(mParams);
            } catch (RemoteException e) {
                Slog.e(TAG, "Error sending onStart message to '" +
                        mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
            }
        }

这里就不细看了!

7 总结

通过分析,我们可以看到 JobSchedulerService 相关类的关系:

不好意思,图我还没画完,后面会补上的,请相信我,请打赏我,让我感受到你们的爱。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容