【Android P】 JobScheduler服務原始碼解析(二) ——服務端啟動解析
JoScheduler服務框架分析
App端從建立一個job 到排程一個Job流程是怎樣的? Job在App端主要比較重要的類有四個:JobInfo,JobScheduler,JobService,JobServiceEngine
public class JobInfo implements Parcelable {
// 優先順序都是內部維護的,APP不可用
// 預設的優先順序
public static final int PRIORITY_DEFAULT = 0;
// 迅速完成過的任務的優先順序
public static final int PRIORITY_SYNC_EXPEDITED = 10;
// 初始化完成以後的優先順序
public static final int PRIORITY_SYNC_INITIALIZATION = 20;
// 前臺任務的優先順序
public static final int PRIORITY_FOREGROUND_APP = 30;
// 正在互動任務的優先順序
public static final int PRIORITY_TOP_APP = 40;
// 一個應用執行的任務超過50%時,它的其他任務的優先順序
public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
// 一個應用執行的任務超過90%時,它的其他任務的優先順序
public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
// 工具類,用於幫助構造JobInfo
public static final class Builder {
// 構造引數分別是jobId和jobService,jobId是區分兩個job的唯一標誌,提交時,如果jobId相同,會做更新操作。
// jobService是任務執行的時候執行的服務,是任務的具體邏輯
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
}
/* 設定任務執行是所需要的網路條件,有三個參加可選:
JobInfo.NETWORK_TYPE_NONE(無網路時執行,預設)
JobInfo.NETWORK_TYPE_ANY(有網路時執行)
JobInfo.NETWORK_TYPE_UNMETERED(網路無需付費時執行)
*/
public Builder setRequiredNetworkType(int networkType) {
mNetworkType = networkType;
return this;
}
// 構建JobInfo,會檢查一些合法性問題,另外如果沒有設定任何條件,也會報錯
public JobInfo build() {...}
}
JobInfo 中主要描述了Job 任務的優先順序,以及觸發條件,以及是否迴圈,系統內部還維護了任務的優先順序,在所有符合條件的任務中,先執行優先順序高的,後執行優先順序低的。
public abstract class JobScheduler {
//job排程失敗的返回值
public static final int RESULT_FAILURE = 0;
// job排程成功的返回值
public static final int RESULT_SUCCESS = 1;
// 排程一個job
public abstract @Result int schedule(@NonNull JobInfo job);
// enqueue 一個job 到JobWorkItem 佇列裡
public abstract @Result int enqueue(@NonNull JobInfo job, @NonNull JobWorkItem work);
// 按照指定包名去排程一個job
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
public abstract @Result int scheduleAsPackage(@NonNull JobInfo job, @NonNull String packageName,
int userId, String tag);
// cancel 一個當前包指定的jobId 的job
public abstract void cancel(int jobId);
// 當前UID 對應的包下的全部Job cancel掉,危險!!!
public abstract void cancelAll();
// 獲取當前已經被排程了的job
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
JobScheduler 中定義了排程,cancel ,cancelAll的介面,並且在。在jobSchedulerImpl中實現
public abstract class JobService extends Service {
private static final String TAG = "JobService";
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
private JobServiceEngine mEngine;
/** @hide */
public final IBinder onBind(Intent intent) {
if (mEngine == null) {
mEngine = new JobServiceEngine(this) {
@Override
public boolean onStartJob(JobParameters params) {
return JobService.this.onStartJob(params);
}
@Override
public boolean onStopJob(JobParameters params) {
return JobService.this.onStopJob(params);
}
};
}
return mEngine.getBinder();
}
// 主動呼叫jobFinished,做最後的Job完成後的清理工作。
public final void jobFinished(JobParameters params, boolean wantsReschedule) {
mEngine.jobFinished(params, wantsReschedule);
}
//在任務開始執行時觸發。返回false表示執行完畢,返回true表示需要開發者自己呼叫jobFinished方法通知系統已執行完成。
public abstract boolean onStartJob(JobParameters params);
//在任務停止執行時觸發。
public abstract boolean onStopJob(JobParameters params);
}
JobService 中定義了App中Jobservice 服務的自身應該實現處理的介面 。
上面開啟分析了一下App 端的jobScheduler 中的一些類的作用以及角色。程式碼量還好,程式碼結構也比較清晰。對於一個App 去Scheuler 一個App 的job 時,各個類的作用以及其角色: 流程圖如下
根據上圖的角色來看:
JobInfo:定義了建立job 的時,指定job 網路type, 各種引數,的條件以及Id ,
JobScheduler:類似於PowerManager 的角色,Job_service的客戶端,它的例項從系統服務Context.JOB_SCHEDULER_SERVICE中獲得。具體的實現是在JobSchedulerImpl.java中
JobService: 客戶端需要去實現的一個抽象類,主要描述自身的job任務中應該做什麼工作
JobServiceEngine:主要的角色是充當SystemServer和APP之間的橋樑,負責真正呼叫onStartJob和onStopJob。JobServiceEngine內部有兩個關鍵成員mBinder和mHandler重要引數。
服務端
JobService服務的啟動流程?
先放一張圖,大概描述一下JobSchedulerService 的啟動流程:
下面來詳細介紹一下JobSchedulerService 的啟動過程中做了哪些工作。
SystemServer.java
traceBeginAndSlog("StartJobScheduler");
mSystemServiceManager.startService(JobSchedulerService.class);
traceEnd()
JobSchedulerService.java
public JobSchedulerService(Context context) {
super(context);
//MIUI MOD:
//mHandler = new JobHandler(context.getMainLooper());
mHandler = new JobHandler(com.android.server.MiuiFgThread.get().getLooper()); // 吧jobHandler 從SystemServer 主執行緒移到MiuiFg執行緒
mConstants = new Constants(mHandler); // 初始化一些常量
mJobSchedulerStub = new JobSchedulerStub(); // Binder服務端的localService
mJobs = JobStore.initAndGet(this); // 初始化JobStore ,Jobstore 是幹嘛的呢,主要是存放Job 任務的一個列表,並記錄Job 的執行情況,時長等詳細資訊到/data/system/job/jobs.xml
// Create the controllers.
mControllers = new ArrayList<StateController>();
mControllers.add(ConnectivityController.get(this));
...
mControllers.add(DeviceIdleJobsController.get(this)); // 初始化各種controller ,每個controller 都對應著JobInfo 裡面的一個set 的Job執行條件,目前共有連線,時間,idle(裝置空閒),充電,儲存,AppIdle,ContentObserver,DeviceIdle(Doze) 的控制器
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
if (!mJobs.jobTimesInflatedValid()) { // 時間不正確,沒有初始化完成,那麼要註冊ACTION_TIME_CHANGED 廣播接收器,來重新設定一下系統中的job 了
Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
}
}
建構函式中就是初始化各種Controller 以及儲存器JobStore
@Override
public void onStart() {
publishLocalService(JobSchedulerInternal.class, new LocalService());
publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
}
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED); // 解除安裝包
filter.addAction(Intent.ACTION_PACKAGE_CHANGED); //升級包
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); //force stop 程序
filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); // 資料變化需要重啟該包程序的廣播,例如包名變化
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null); // 註冊監聽廣播
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); //使用者移除。
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN,
null); // 註冊UID 狀態變化observer
// Remove any jobs that are not associated with any of the current users.
cancelJobsForNonExistentUsers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
// Create the "runners".
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { // 一次最多隻能同時run 16個job
mActiveServices.add(
new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
// MIUI MOD
//getContext().getMainLooper()));
com.android.server.MiuiFgThread.get().getLooper()));
}
// Attach jobs to their controllers.
mJobs.forEachJob(new JobStatusFunctor() { // 將系統中已經設定的所有job 新增controller控制器
@Override
public void process(JobStatus job) {
for (int controller = 0; controller < mControllers.size(); controller++) {
final StateController sc = mControllers.get(controller);
sc.maybeStartTrackingJobLocked(job, null);
}
}
});
}
} else if (phase == PHASE_BOOT_COMPLETED) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
// END
}
}
onStart 的方法裡面監聽了Package 的一些廣播,以及註冊了UID 的observer , 將Job 的Controller 也給新增上。傳送MSG_CHECK_JOB 訊息 大致流程如下:
App 建立一個Job並scheduler 它,服務端流程如何?
當使用JobScheduler使用首先會建立一個jobscheduler的服務物件,當呼叫scheduler.schedule(builder.build());時候 scheduler() 便開始啟動該job 的任務,但是不一定立馬執行。
其實是由JobSchedulerImpl通過Binder呼叫到JobSchedulerService的schedule()中
uid = Binder.getCallingUid();
public int schedule(JobInfo job, int uId) {
.
. // 判斷許可權JobService.PERMISSION_BIND
.
return scheduleAsPackage(job, uId, null, -1, null);
}
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
if (ActivityManager.getService().isAppStartModeDisabled(uId,
job.getService().getPackageName())) { //這裡通過AMS來判斷packageName該應該是否允許啟動該服務,
return JobScheduler.RESULT_FAILURE;
}
synchronized (mLock) {
final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); // 判斷系統中該Uid 的App對應的Jobid 是否已經存在系統中
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); // 建立一個對應的JobStatus,並且指定相關的條件!!!!
if (ENFORCE_MAX_JOBS && packageName == null) {
if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { // 每個UID 對應的Job 最多不能同時存在系統中100個
Slog.w(TAG, "Too many jobs for uid " + uId);
throw new IllegalStateException("Apps may not schedule more than "
+ MAX_JOBS_PER_APP + " distinct jobs");
}
}
// This may throw a SecurityException.
jobStatus.prepareLocked(ActivityManager.getService()); //準備
if (toCancel != null) {
cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app"); // 如果系統中已經存在了同一個uid 裡面的同一個jobId 的job ,那麼先cancle 這個job
}
if (work != null) {
// If work has been supplied, enqueue it into the new job.
jobStatus.enqueueWorkLocked(ActivityManager.getService(), work); //如果執行了work佇列那麼 將jobStatus 放入指定的work佇列裡
}
startTrackingJobLocked(jobStatus, toCancel); // 開始將App 的所有的Job的放到mJobs裡表裡,如果並且對每個job 指定對應的不同Controller
if (isReadyToBeExecutedLocked(jobStatus)) { 如果一個job 滿足一定條件需要立即執行,那麼會將其放在pending 列表中,並且在後面馬上處理
// This is a new job, we can just immediately put it on the pending
// list and try to run it.
mJobPackageTracker.notePending(jobStatus);
addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
maybeRunPendingJobsLocked();
}
}
return JobScheduler.RESULT_SUCCESS;
}
當建立一個job任務的時候,會先判斷該package的啟動服務許可權,並且JobScheduler中維持了一個mJobs的列表儲存這系統中所有的Job任務,當新建立一個job任務的時候會先判斷當前系統中是否存在一個已有的job,如果存在的話,先將其cancel。 然後將該開始tracking 該job ,為其指定對應JobController
private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (!jobStatus.isPreparedLocked()) {
Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
}
jobStatus.enqueueTime = SystemClock.elapsedRealtime();
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); // 開始Tracking 該Job,並指定對應的Controller,JobStatus 中也初始化對應的條件
}
}
}
當開始scheduler 一個job 的時候,會呼叫startTrackingJobLocked 方法,將當前的時間賦值給enqueueTime,並且會先判斷系統mJobs列表中是否已經存在這個job ,如果存在,那麼會先刪掉這個job 然後重新新增上,繼而呼叫controller 的 maybeStartTrackingJobLocked方法開始Tracking 該job。
從原始碼裡看到scheduler 一個job 流程其實並不難,呼叫的互動類也很少也不多,JSS,JSI,JSC,JobStore等。但是該流程僅僅是將Job加到一個mJobs列表中,以及指定對應的StateController
其大概流程圖如下(圖片來自Gityuan):
Job設定好了之後如何去觸發它呢
首先我們知道JobScheduler 中維持了8個Controller,分別是:AppIdleController.java(App Idle 狀態控制器),BatteryController.java(電池狀態控制器),ConnectivityController.java (網路連線狀態控制器),ContentObserverController.java(content監聽狀態控制器),DeviceIdleJobsController.java(Doze狀態控制器),StorageController.java(儲存狀態控制器),TimeController.java(時間控制器),IdleController.java(裝置空閒狀態控制器)。這些controller 控制器主要是控制對應Job 需要滿足的條件是否滿足,是否所有條件都滿足,才允許執行。比如說:我一個App 如下圖設定一個Job
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService)
.setRequiresDeviceIdle(true) // 需要Device idle 狀態下才能執行
.setMinimumLatency(15* 60 * 1000); //設定延遲15min,在N 上有限制,最小延遲時間不得小小於15分鐘,如果小於15分鐘,則會主動將你的 MinimumLatency修改為15分鐘。
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 需要網路非計費型別
.setRequiresCharging(true); // 需要充電狀態
js.schedule(builder.build());
這裡能看到,這個job 設定了四個條件,那麼對應到服務端模組,從介面上來看就會有TimeController.java(時間控制器),ConnectivityController.java (網路連線狀態控制器),BatteryController.java(電池狀態控制器),IdleController.java(裝置空閒狀態控制器)四個控制器來監控這個裝置狀態,當四個狀態都滿足了,也就是:裝置處於空閒狀態,從開始排程到現在已經過了超過15分鐘,手機網路處於非計費網路,手機處於充電狀態。那麼job 任務就可以執行了。
但是反饋到system server 它是怎麼執行的呢?
首先我們要需要介紹一下Job 建立時,如何和Controller 繫結起來的。我們在2.1 中有提到,在 scheduleAsPackage 方法中會通過JobInfo 建立對應JobStatus物件。
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()) { //如果job 是迴圈的
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); // 最晚執行時間 = 當前時間+ 迴圈間隔
earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); //最早執行時間 = 最晚觸發時間 - Flex(靈活視窗)時間 , Flex時間是三個時間(5min,5 * interval / 100, 設定的flexMillis時間)中最大值
} else {
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? // job 有最早執行限制(預設有),則 最早執行時間=當前時間+最小延遲
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = job.hasLateConstraint() ? // job 有最晚執行限制(預設有),則最晚執行時間=當前時間+最大延遲
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, // 通過JobInfo,以及執行時間引數建立JobStatus
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0