JobScheduler之原始碼分析
接下來我們從原始碼角度去深入理解JobScheduler
的執行機制。
客戶端呼叫JobScheduler.schedule
方法之後,通過Binder
通訊會進入到JobSchedulerStub.schedule
方法
1JobScheduler工作流程
1.1JobSchedulerStub.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
方法的邏輯比較簡單,主要乾了兩件事:
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; } 複製程式碼
該方法的邏輯還是非常清晰的:
-
封裝
JobInfo
為JobStatus
-
取消具有相同jobId的老任務
3呼叫
startTrackingJob
開始進行狀態跟蹤(重點) -
傳送一個
MSG_CHECK_JOB
訊息
其中2、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
的建構函式中被初始化。
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
為例,繼續流程分析。
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
邏輯比較簡單,幹了兩件事情
JobStatus JobStatus.appNotIdleConstraintSatisfied
那麼流程到這裡就斷了麼?顯然不會,AppIdleController
內部會一直跟蹤應用狀態,當應用狀態發生變化時會通知JobSchedulerService
。
我們先來看看AppIdleController
的建構函式
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
。
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
方法(JobServiceHandler
是JobServiceContext
的內部類)。
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
的工作主要由JobSchedulerService
、JobServiceContext
以及各種StateController
協同完成。
其中StateController
負責監聽裝置的各種狀態、更新JobStatus
中對應欄位的值,並通知JobSchedulerService
.JobSchedulerService
收到StateController
的訊息後,就開始檢測Job
的狀態,如果有滿足執行條件的Job
則交給JobServiceContext
執行。
而JobServiceContext
則負責繫結服務等於JobService
生命週期相關的事宜。
2JobService銷燬流程
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
方法。
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); } 複製程式碼
主要做了這些事情:
- 解綁服務
- 重置一些變數的狀態
- 移除訊息佇列中的所有訊息
- 執行一個成功回撥
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(); } 複製程式碼
主要工作:
-
將
JobStatus
從JobStore
和各種StateController
中移除 -
如果需要重新執行,那麼會生成一個新的
JobStatus
並呼叫startTrackingJob
方法 -
不管是否要重新執行,都會發送
MSG_CHECK_JOB
訊息,檢查是否有滿足條件的Job
能夠被執行。
以上,就是JobService
的銷燬流程了。