文中的源代碼版本爲api23java
接下來咱們從源碼角度去深刻理解JobScheduler
的運行機制。 客戶端調用JobScheduler.schedule
方法以後,經過Binder
通訊會進入到JobSchedulerStub.schedule
方法api
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
方法的邏輯比較簡單,主要乾了兩件事:網絡
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
JobInfo
爲JobStatus
startTrackingJob
開始進行狀態跟蹤(重點)MSG_CHECK_JOB
消息其中二、4步咱們放到後面講,先關注核心方法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
的派生類有不少,有ConnectivityController
、TimeController
、IdleController
、BatteryController
、AppIdleController
,JobSchedulerService
正是經過這些StateController
實現了對網絡鏈接狀態、時間、設備空閒狀態、電池電量、應用空閒狀態等的監聽。 爲了便於分析,咱們以相對簡單的AppIdleController
爲例,繼續流程分析。函數
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
JobStatus
JobStatus.appNotIdleConstraintSatisfied
字段的值那麼流程到這裏就斷了麼?顯然不會,AppIdleController
內部會一直跟蹤應用狀態,當應用狀態發生變化時會通知JobSchedulerService
。this
咱們先來看看AppIdleController
的構造函數spa
private AppIdleController(StateChangedListener stateChangedListener, Context context) {
super(stateChangedListener, context);
mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
}
複製代碼
構造函數的邏輯很是簡單,首先是獲取UsageStatsService
服務,而後註冊了一個應用狀態變動監聽。
AppIdleStateChangeListener
是AppIdleController
的一個內部類,當應用狀態發生變化時會回調其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
消息,接着就會依次觸發maybeQueueReadyJobsForExecutionLockedH
和maybeRunPendingJobsH
。
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
中。
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
的對接工做。
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
方法(JobServiceHandler
是JobServiceContext
的內部類)。
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
的工做流程已經基本結束了。
JobScheduler
的工做主要由JobSchedulerService
、JobServiceContext
以及各類StateController
協同完成。 其中StateController
負責監聽設備的各類狀態、更新JobStatus
中對應字段的值,並通知JobSchedulerService
. JobSchedulerService
收到StateController
的消息後,就開始檢測Job
的狀態,若是有知足執行條件的Job
則交給JobServiceContext
執行。 而JobServiceContext
則負責綁定服務等於JobService
生命週期相關的事宜。
JobService
的銷燬流程在前半部分會有兩個分支:
JobService
的工做在onStartJob
方法中就完成了,那麼經過返回false
就能夠結束了(觸發JobSchedulerContext.acknowledgeStartMessage
方法)。(如下簡稱分支1)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會依次走handleStartedH
、handleFinishedH
方法。
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;
}
}
複製代碼
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);
}
複製代碼
主要作了這些事情:
mCompletedListener
的本體就是JobSchedulerService
。
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();
}
複製代碼
主要工做:
JobStatus
從JobStore
和各類StateController
中移除JobStatus
並調用startTrackingJob
方法MSG_CHECK_JOB
消息,檢查是否有知足條件的Job
可以被執行。以上,就是JobService
的銷燬流程了。