JobScheduler之源碼分析

文中的源代碼版本爲api23java

JobScheduler工做流程

接下來咱們從源碼角度去深刻理解JobScheduler的運行機制。 客戶端調用JobScheduler.schedule方法以後,經過Binder通訊會進入到JobSchedulerStub.schedule方法api

1 JobScheduler工做流程

1.1 JobSchedulerStub.schedule方法

final class JobSchedulerStub extends IJobScheduler.Stub {
    public int schedule(JobInfo job) throws RemoteException {
        //log..
        final int pid = Binder.getCallingPid();
        final int uid = Binder.getCallingUid();

        //主要是校驗一下Service是否存在,是否擁有BIND_JOB_SERVICE權限
        enforceValidJobRequest(uid, job);
        if (job.isPersisted()) {
            //若是Job須要持久化,那須要校驗是否有RECEIVE_BOOT_COMPLETED權限
            if (!canPersistJobs(pid, uid)) {
                throw new IllegalArgumentException("Error: requested job be persisted without"
                        + " holding RECEIVE_BOOT_COMPLETED permission.");
            }
        }

        long ident = Binder.clearCallingIdentity();
        try {
            //JobSchedulerStub是JobSchedulerService的內部類,後續流程
            //直接拋給JobSchedulerService來完成
            return JobSchedulerService.this.schedule(job, uid);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}
複製代碼

JobSchedulerStub.schedule方法的邏輯比較簡單,主要乾了兩件事:網絡

  1. 作一些權限校驗
  2. 調用JobSchedulerService.schedule方法繼續後續流程

1.2 JobSchedulerService.schedule方法

public int schedule(JobInfo job, int uId) {
    JobStatus jobStatus = new JobStatus(job, uId);
    cancelJob(uId, job.getId());
    startTrackingJob(jobStatus);
    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    return JobScheduler.RESULT_SUCCESS;
}
複製代碼

該方法的邏輯仍是很是清晰的:app

  1. 封裝JobInfoJobStatus
  2. 取消具備相同jobId的老任務 3 調用startTrackingJob開始進行狀態跟蹤(重點)
  3. 發送一個MSG_CHECK_JOB消息

其中二、4步咱們放到後面講,先關注核心方法startTrackingJob異步

1.3 JobSchedulerService.startTrackingJob方法

private void startTrackingJob(JobStatus jobStatus) {
    boolean update;
    boolean rocking;
    synchronized (mJobs) {
        //mJobs是Job的管理者,類型爲JobStore
        //mJobs內部使用ArraySet來保存JobStatus
        //JobStore.add方法涉及到ArraySet的remove和add操做
        //update就是ArraySet.remove方法的返回值
        //因爲當前的JobStatus是全新的
        //所以此處的update爲false
        update = mJobs.add(jobStatus);
        //mReadyToRock字段用來跟蹤系統啓動狀態
        //通常狀況下mReadyToRock都爲true
        rocking = mReadyToRock;
    }
    if (rocking) {
        for (int i=0; i<mControllers.size(); i++) {
            //StateController
            StateController controller = mControllers.get(i);
            //...
            controller.maybeStartTrackingJob(jobStatus);
        }
    }
}
複製代碼

該方法的核心邏輯是遍歷全部的StateController並執行其maybeStartTrackingJob方法。 JobSchedulerService使用一個名爲mControllers的變量保存StateController,其類型爲List,在JobSchedulerService的構造函數中被初始化。ide

public JobSchedulerService(Context context) {
    super(context);
    // Create the controllers.
    mControllers = new ArrayList<StateController>();
    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));

    //...
}
複製代碼

能夠看到,StateController的派生類有不少,有ConnectivityControllerTimeControllerIdleControllerBatteryControllerAppIdleControllerJobSchedulerService正是經過這些StateController實現了對網絡鏈接狀態、時間、設備空閒狀態、電池電量、應用空閒狀態等的監聽。 爲了便於分析,咱們以相對簡單的AppIdleController爲例,繼續流程分析。函數

1.4 AppIdleController.maybeStartTrackingJob方法

public void maybeStartTrackingJob(JobStatus jobStatus) {
    synchronized (mTrackedTasks) {
        //mTrackedTasks爲ArrayList<JobStatus>類型
        //AppIdleController使用該字段保存JobStatus
        mTrackedTasks.add(jobStatus);
        String packageName = jobStatus.job.getService().getPackageName();
        //經過UsageStatsService獲取當前應用是否處於空閒狀態
        final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
                jobStatus.uId, jobStatus.getUserId());
        //debug log...
        //根據當前應用狀態設置JobStatus的對應字段
        //appNotIdleConstraintSatisfied爲AtomicBoolean類型
        //用來記錄當前app的空閒狀態
        jobStatus.appNotIdleConstraintSatisfied.set(!appIdle);
    }
}
複製代碼

maybeStartTrackingJob邏輯比較簡單,幹了兩件事情ui

  1. 保存JobStatus
  2. 根據當前應用狀態設置JobStatus.appNotIdleConstraintSatisfied字段的值

那麼流程到這裏就斷了麼?顯然不會,AppIdleController內部會一直跟蹤應用狀態,當應用狀態發生變化時會通知JobSchedulerServicethis

咱們先來看看AppIdleController的構造函數spa

private AppIdleController(StateChangedListener stateChangedListener, Context context) {
    super(stateChangedListener, context);
    mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
    mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
    mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
}
複製代碼

構造函數的邏輯很是簡單,首先是獲取UsageStatsService服務,而後註冊了一個應用狀態變動監聽。

AppIdleStateChangeListenerAppIdleController的一個內部類,當應用狀態發生變化時會回調其onAppIdleStateChanged方法,咱們直接上代碼

@Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
    boolean changed = false;
    synchronized (mTrackedTasks) {
        //這個return邏輯先無論它
        if (mAppIdleParoleOn) {
            return;
        }
        for (JobStatus task : mTrackedTasks) {
            if (task.job.getService().getPackageName().equals(packageName)
                    && task.getUserId() == userId) {
                if (task.appNotIdleConstraintSatisfied.get() != !idle) {
                    //debug log...
                    //應用狀態發生改變,更新對應的字段
                    task.appNotIdleConstraintSatisfied.set(!idle);
                    changed = true;
                }
            }
        }
    }
    if (changed) {
        //只要有Job的狀態發生了變化就會觸發回調
        mStateChangedListener.onControllerStateChanged();
    }
}
複製代碼

邏輯很是簡單,就是遍歷全部的JobStatus看狀態是否發生變化,若是是,則更新appNotIdleConstraintSatisfied字段。同時,只要有一個JobStatus的狀態被更新,就會觸發一個回調。

mStateChangedListener的實現類就是JobSchedulerService,因爲只有一句代碼,就不貼出來了。該方法就是經過JobHandler(JobSchedulerService的一個內部類,派生自Handler)發送了一個MSG_CHECK_JOB消息,接着就會依次觸發maybeQueueReadyJobsForExecutionLockedHmaybeRunPendingJobsH

1.5 JobHandler.maybeQueueReadyJobsForExecutionLockedH方法

private void maybeQueueReadyJobsForExecutionLockedH() {
    int chargingCount = 0;
    int idleCount =  0;
    int backoffCount = 0;
    int connectivityCount = 0;
    List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
    //經過JobStore獲取當前全部的Job
    ArraySet<JobStatus> jobs = mJobs.getJobs();
    for (int i=0; i<jobs.size(); i++) {
        JobStatus job = jobs.valueAt(i);
        //使用isReadyToBeExecutedLocked方法判斷
        //當前的Job因此來的約束條件是否已經知足
        if (isReadyToBeExecutedLocked(job)) {
            if (job.getNumFailures() > 0) {
                //計算重試的Job數量
                backoffCount++;
            }
            if (job.hasIdleConstraint()) {
                //計算依賴設備空閒狀態的Job數量
                idleCount++;
            }
            if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
                //計算依賴網絡類型的Job數量
                connectivityCount++;
            }
            if (job.hasChargingConstraint()) {
                //計算依賴充電狀態的Job數量
                chargingCount++;
            }
            runnableJobs.add(job);
        } else if (isReadyToBeCancelledLocked(job)) {
            //Job的stop流程,先無論它
            stopJobOnServiceContextLocked(job);
        }
    }
    
    //特殊機制
    if (backoffCount > 0 ||
            idleCount >= MIN_IDLE_COUNT ||
            connectivityCount >= MIN_CONNECTIVITY_COUNT ||
            chargingCount >= MIN_CHARGING_COUNT ||
            runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
        //debug log...
        for (int i=0; i<runnableJobs.size(); i++) {
            mPendingJobs.add(runnableJobs.get(i));
        }
    } else {
        //debug log...
    }
    //debug log...
}
複製代碼

maybeQueueReadyJobsForExecutionLockedH方法會遍歷當前列表中的全部JobStatus尋找已經知足執行條件的添加到runnableJobs列表中。 並非知足了執行條件就會執行,JobSchedulerService有一個機制,它會以約束條件爲維度進行計數,併爲各個約束條件設置了一個閾值,只有超過閾值的Job纔會添加到待執行列表mPendingJobs中。

1.6 JobHandler.maybeRunPendingJobsH方法

private void maybeRunPendingJobsH() {
    synchronized (mJobs) {
        if (mDeviceIdleMode) {
            //設備空閒狀態禁止執行任何任務
            return;
        }
        Iterator<JobStatus> it = mPendingJobs.iterator();
        //debug log...
        while (it.hasNext()) {
            JobStatus nextPending = it.next();
            JobServiceContext availableContext = null;
            //這個for循環的工做時尋找可用的JobServiceContext
            for (int i=0; i<mActiveServices.size(); i++) {
                JobServiceContext jsc = mActiveServices.get(i);
                final JobStatus running = jsc.getRunningJob();
                if (running != null && running.matches(nextPending.getUid(),
                        nextPending.getJobId())) {
                    // Job已經在運行,則跳過
                    availableContext = null;
                    break;
                }
                
                if (jsc.isAvailable()) {
                    //找到了可用的JobServiceContext
                    availableContext = jsc;
                }
            }
            if (availableContext != null) {
                //debug log...
                //調用JobServiceContext.executeRunnableJob方法執行任務
                if (!availableContext.executeRunnableJob(nextPending)) {
                    //debug log...
                    //JobServiceContext執行任務失敗,則將Job從JobStore中移除
                    mJobs.remove(nextPending);
                }
                //從mPendingJobs中移除對應的JobStatus
                it.remove();
            }
        }
    }
}
複製代碼

該方法的主要邏輯很簡單:爲全部待啓動任務尋找到可用的JobServiceContext,並執行任務(經過JobServiceContext.executeRunnableJob方法)。 那麼JobServiceContext又是什麼呢?咱們知道一個Job就至關於一個Service,而JobServiceContext則負責與Service的對接工做。

1.7 JobServiceContext.enecuteRunnableJob

boolean executeRunnableJob(JobStatus job) {
    synchronized (mLock) {
        //...

        mRunningJob = job;
        final boolean isDeadlineExpired =
                job.hasDeadlineConstraint() &&
                        (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
        //建立一個JobParameters
        //JobParameters的第一個參數是一個IBinder對象
        //後續JobService能夠拿到該對象與JobServiceContext
        //進行通訊
        mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired);
        mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
        
        mVerb = VERB_BINDING;
        //發送超時消息
        scheduleOpTimeOut();
        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) {
            //綁定失敗的一些清理工做
            return false;
        }
        //...
        mAvailable = false;
        return true;
    }
}
複製代碼

executeRunnableJob的主要工做就是經過bindServiceAsUser啓動了JobService。因爲JobServiceContext實現了ServiceConnection接口,後續JobServiceContext即可以與JobService進行通訊。

有關服務綁定的細節咱們就不贅述了,服務綁定成功以後會回調ServiceConnection.onServiceConnected方法。JobServiceContext.onServiceConnected方法的實現較簡單,有興趣你們能夠本身分析,它會發送一個MSG_SERVICE_BOUND消息,最終觸發JobServiceHandler.handleServiceBoundH方法(JobServiceHandlerJobServiceContext的內部類)。

1.8 JobServiceHandler.handleServiceBoundH方法

private void handleServiceBoundH() {
    //debug log...

    //異常檢查
    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()) {
        if (DEBUG) {
            Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
                    + mRunningJob);
        }
        closeAndCleanupJobH(true /* reschedule */);
        return;
    }
    try {
        mVerb = VERB_STARTING;
        scheduleOpTimeOut();
        //service即是JobService.onBind方法返回的Binder對象
        //mParams是在executeRunnableJob方法中生成的
        service.startJob(mParams);
    } catch (RemoteException e) {
        Slog.e(TAG, "Error sending onStart message to '" +
                mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
    }
}
複製代碼

handleServiceBoundH的主要邏輯頁很簡單,就是調用了JobService返回的Binder對象的startJob方法。這樣JobService.onStartJob方法便觸發了。

至此,JobScheduler的工做流程已經基本結束了。

1.9 總結

JobScheduler的工做主要由JobSchedulerServiceJobServiceContext以及各類StateController協同完成。 其中StateController負責監聽設備的各類狀態、更新JobStatus中對應字段的值,並通知JobSchedulerService. JobSchedulerService收到StateController的消息後,就開始檢測Job的狀態,若是有知足執行條件的Job則交給JobServiceContext執行。 而JobServiceContext則負責綁定服務等於JobService生命週期相關的事宜。

2 JobService銷燬流程

JobService的銷燬流程在前半部分會有兩個分支:

  1. JobService的工做在onStartJob方法中就完成了,那麼經過返回false就能夠結束了(觸發JobSchedulerContext.acknowledgeStartMessage方法)。(如下簡稱分支1)
  2. JobService執行的是一些耗時任務,那麼則須要異步執行任務,那麼就須要調用JobService.jobFinished方法來結束任務(觸發JobSchedulerContext.jobFinished方法)。而此時onStartJob方法須要返回false。(如下簡稱分支2)

不管是acknowledgeStartMessage仍是jobFinished,最終都會進入到JobServiceHandler.handleMessage方法的MSG_CALLBACK這個case中。

public void handleMessage(Message message) {
    switch (message.what) {
        //...
        case MSG_CALLBACK:
            //...

            if (mVerb == VERB_STARTING) {
                //acknowledgeStartMessage走這個分支
                final boolean workOngoing = message.arg2 == 1;
                handleStartedH(workOngoing);
            } else if (mVerb == VERB_EXECUTING ||
                    mVerb == VERB_STOPPING) {
                //jobFinished走這個分支
                final boolean reschedule = message.arg2 == 1;
                handleFinishedH(reschedule);
            } else {
                //...
            }
            break;
        //...
    }
}
複製代碼

從流程上來說,分支1只會走handleStartedH方法,而分支2會依次走handleStartedHhandleFinishedH方法。

2.1 JobServiceHandler.handleStartedH方法

private void handleStartedH(boolean workOngoing) {
    switch (mVerb) {
        case VERB_STARTING:
            mVerb = VERB_EXECUTING;
            if (!workOngoing) {
                //分支1場景workOngoing爲false,馬上執行handleFinishedH
                handleFinishedH(false);
                return;
            }
            //取消流程
            if (mCancelled.get()) {
                //log...
                handleCancelH();
                return;
            }
            //分支2場景,workOngong爲true,執行一個超時消息
            //後續JobService執行完畢調用jobFinished方法
            //仍是會進入到handleFinishedH中。
            scheduleOpTimeOut();
            break;
        default:
            //log...
            return;
    }
}
複製代碼

2.2 JobServiceHandler.handleFinishedH

private void handleFinishedH(boolean reschedule) {
    switch (mVerb) {
        case VERB_EXECUTING:
        case VERB_STOPPING:
            closeAndCleanupJobH(reschedule);
            break;
        default:
            //log...
    }
}
複製代碼

直接調用了closeAndCleanupJobH方法

private void closeAndCleanupJobH(boolean reschedule) {
    final JobStatus completedJob = mRunningJob;
    synchronized (mLock) {
        //...
        mContext.unbindService(JobServiceContext.this);
        mWakeLock = null;
        mRunningJob = null;
        mParams = null;
        mVerb = -1;
        mCancelled.set(false);
        service = null;
        mAvailable = true;
    }
    removeOpTimeOut();
    removeMessages(MSG_CALLBACK);
    removeMessages(MSG_SERVICE_BOUND);
    removeMessages(MSG_CANCEL);
    removeMessages(MSG_SHUTDOWN_EXECUTION);
    mCompletedListener.onJobCompleted(completedJob, reschedule);
}
複製代碼

主要作了這些事情:

  1. 解綁服務
  2. 重置一些變量的狀態
  3. 移除消息隊列中的全部消息
  4. 執行一個成功回調

mCompletedListener的本體就是JobSchedulerService

2.3 JobSchedulerService.onJobCompleted

public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
    //log...
    //stopTrackingJob的工做主要是講JobStatus從JobStore
    //和各類StateController中移除
    //是與startTrackingJob相反的一個過程
    if (!stopTrackingJob(jobStatus)) {
        //log...
        return;
    }
    if (needsReschedule) {
        JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
        startTrackingJob(rescheduled);
    } else if (jobStatus.getJob().isPeriodic()) {
        JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
        startTrackingJob(rescheduledPeriodic);
    }
    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
複製代碼

主要工做:

  1. JobStatusJobStore和各類StateController中移除
  2. 若是須要從新執行,那麼會生成一個新的JobStatus並調用startTrackingJob方法
  3. 不論是否要從新執行,都會發送MSG_CHECK_JOB消息,檢查是否有知足條件的Job可以被執行。

以上,就是JobService的銷燬流程了。

相關文章
相關標籤/搜索