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 相關類的關系:

不好意思,圖我還沒畫完,后面會補上的,請相信我,請打賞我,讓我感受到你們的愛。。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容