JoScheduler服務(wù)框架分析
前面(一)中基本已經(jīng)App端如何使用Job 服務(wù)做了一個(gè)比較詳細(xì)的介紹,這里將會(huì)對(duì)客戶端幾個(gè)重要類解析;其承擔(dān)的角色;App 從scheduler 一個(gè)job 到執(zhí)行這個(gè)job ,其中服務(wù)端的調(diào)用流程;以及服務(wù)端如何管理系統(tǒng)中所有的job 等等問(wèn)題,將在此做一個(gè)全面的解答。
客戶端
App端從創(chuàng)建一個(gè)job 到調(diào)度一個(gè)Job流程是怎樣的?
Job在客戶端主要比較重要的類有四個(gè):JobInfo,JobScheduler,JobService,JobServiceEngine
public class JobInfo implements Parcelable {
// 優(yōu)先級(jí)都是內(nèi)部維護(hù)的,APP不可用
// 默認(rèn)的優(yōu)先級(jí)
public static final int PRIORITY_DEFAULT = 0;
// 迅速完成過(guò)的任務(wù)的優(yōu)先級(jí)
public static final int PRIORITY_SYNC_EXPEDITED = 10;
// 初始化完成以后的優(yōu)先級(jí)
public static final int PRIORITY_SYNC_INITIALIZATION = 20;
// 前臺(tái)任務(wù)的優(yōu)先級(jí)
public static final int PRIORITY_FOREGROUND_APP = 30;
// 正在交互任務(wù)的優(yōu)先級(jí)
public static final int PRIORITY_TOP_APP = 40;
// 一個(gè)應(yīng)用運(yùn)行的任務(wù)超過(guò)50%時(shí),它的其他任務(wù)的優(yōu)先級(jí)
public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
// 一個(gè)應(yīng)用運(yùn)行的任務(wù)超過(guò)90%時(shí),它的其他任務(wù)的優(yōu)先級(jí)
public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
// 工具類,用于幫助構(gòu)造JobInfo
public static final class Builder {
// 構(gòu)造參數(shù)分別是jobId和jobService,jobId是區(qū)分兩個(gè)job的唯一標(biāo)志,提交時(shí),如果jobId相同,會(huì)做更新操作。
// jobService是任務(wù)運(yùn)行的時(shí)候執(zhí)行的服務(wù),是任務(wù)的具體邏輯
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
}
/* 設(shè)置任務(wù)執(zhí)行是所需要的網(wǎng)絡(luò)條件,有三個(gè)參加可選:
JobInfo.NETWORK_TYPE_NONE(無(wú)網(wǎng)絡(luò)時(shí)執(zhí)行,默認(rèn))
JobInfo.NETWORK_TYPE_ANY(有網(wǎng)絡(luò)時(shí)執(zhí)行)
JobInfo.NETWORK_TYPE_UNMETERED(網(wǎng)絡(luò)無(wú)需付費(fèi)時(shí)執(zhí)行)
*/
public Builder setRequiredNetworkType(int networkType) {
mNetworkType = networkType;
return this;
}
// 構(gòu)建JobInfo,會(huì)檢查一些合法性問(wèn)題,另外如果沒(méi)有設(shè)置任何條件,也會(huì)報(bào)錯(cuò)
public JobInfo build() {...}
}
JobInfo 中主要描述了Job 任務(wù)的優(yōu)先級(jí),以及觸發(fā)條件,以及是否循環(huán),系統(tǒng)內(nèi)部還維護(hù)了任務(wù)的優(yōu)先級(jí),在所有符合條件的任務(wù)中,先執(zhí)行優(yōu)先級(jí)高的,后執(zhí)行優(yōu)先級(jí)低的。
public abstract class JobScheduler {
//job調(diào)度失敗的返回值
public static final int RESULT_FAILURE = 0;
// job調(diào)度成功的返回值
public static final int RESULT_SUCCESS = 1;
// 調(diào)度一個(gè)job
public abstract @Result int schedule(@NonNull JobInfo job);
// enqueue 一個(gè)job 到JobWorkItem 隊(duì)列里
public abstract @Result int enqueue(@NonNull JobInfo job, @NonNull JobWorkItem work);
// 按照指定包名去調(diào)度一個(gè)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 一個(gè)當(dāng)前包指定的jobId 的job
public abstract void cancel(int jobId);
// 當(dāng)前UID 對(duì)應(yīng)的包下的全部Job cancel掉,危險(xiǎn)!!!
public abstract void cancelAll();
// 獲取當(dāng)前已經(jīng)被調(diào)度了的job
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
JobScheduler 中定義了調(diào)度,cancel ,cancelAll的接口,并且在。在jobSchedulerImpl中實(shí)現(xiàn)
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();
}
// 主動(dòng)調(diào)用jobFinished,做最后的Job完成后的清理工作。
public final void jobFinished(JobParameters params, boolean wantsReschedule) {
mEngine.jobFinished(params, wantsReschedule);
}
//在任務(wù)開(kāi)始執(zhí)行時(shí)觸發(fā)。返回false表示執(zhí)行完畢,返回true表示需要開(kāi)發(fā)者自己調(diào)用jobFinished方法通知系統(tǒng)已執(zhí)行完成。
public abstract boolean onStartJob(JobParameters params);
//在任務(wù)停止執(zhí)行時(shí)觸發(fā)。
public abstract boolean onStopJob(JobParameters params);
}
JobService 中定義了App中Jobservice 服務(wù)的自身應(yīng)該實(shí)現(xiàn)處理的接口 。
上面打開(kāi)分析了一下App 端的jobScheduler 中的一些類的作用以及角色。代碼量還好,代碼結(jié)構(gòu)也比較清晰。對(duì)于一個(gè)App 去Scheuler 一個(gè)App 的job 時(shí),各個(gè)類的作用以及其角色: 流程圖如下
根據(jù)上圖的角色來(lái)看:
JobInfo:定義了創(chuàng)建job 的時(shí),指定job 網(wǎng)絡(luò)type, 各種參數(shù),的條件以及Id ,
JobScheduler:類似于PowerManager 的角色,Job_service的客戶端,它的實(shí)例從系統(tǒng)服務(wù)Context.JOB_SCHEDULER_SERVICE中獲得。具體的實(shí)現(xiàn)是在JobSchedulerImpl.java中
JobService: 客戶端需要去實(shí)現(xiàn)的一個(gè)抽象類,主要描述自身的job任務(wù)中應(yīng)該做什么工作
JobServiceEngine:主要的角色是充當(dāng)SystemServer和APP之間的橋梁,負(fù)責(zé)真正調(diào)用onStartJob和onStopJob。JobServiceEngine內(nèi)部有兩個(gè)關(guān)鍵成員mBinder和mHandler重要參數(shù)。
服務(wù)端
JobService服務(wù)的啟動(dòng)流程?
先放一張圖,大概描述一下JobSchedulerService 的啟動(dòng)流程:
下面來(lái)詳細(xì)介紹一下JobSchedulerService 的啟動(dòng)過(guò)程中做了哪些工作。
SystemServer.java
traceBeginAndSlog("StartJobScheduler");
mSystemServiceManager.startService(JobSchedulerService.class);
traceEnd()
JobSchedulerService.java
public JobSchedulerService(Context context) {
super(context);
//mHandler = new JobHandler(context.getMainLooper()) // 吧jobHandler 放到SystemServer 主線程移運(yùn)行
mConstants = new Constants(mHandler); // 初始化一些常量
mJobSchedulerStub = new JobSchedulerStub(); // Binder服務(wù)端的localService
mJobs = JobStore.initAndGet(this); // 初始化JobStore ,Jobstore 是干嘛的呢,主要是存放Job 任務(wù)的一個(gè)列表,并記錄Job 的運(yùn)行情況,時(shí)長(zhǎng)等詳細(xì)信息到/data/system/job/jobs.xml
// Create the controllers.
mControllers = new ArrayList<StateController>();
mControllers.add(ConnectivityController.get(this));
...
mControllers.add(DeviceIdleJobsController.get(this)); // 初始化各種controller ,每個(gè)controller 都對(duì)應(yīng)著JobInfo 里面的一個(gè)set 的Job運(yùn)行條件,目前共有連接,時(shí)間,idle(設(shè)備空閑),充電,存儲(chǔ),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()) { // 時(shí)間不正確,沒(méi)有初始化完成,那么要注冊(cè)ACTION_TIME_CHANGED 廣播接收器,來(lái)重新設(shè)定一下系統(tǒng)中的job 了
Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
}
}
構(gòu)造函數(shù)中就是初始化各種Controller 以及存儲(chǔ)器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); //升級(jí)包
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); //force stop 進(jìn)程
filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); // 數(shù)據(jù)變化需要重啟該包進(jìn)程的廣播,例如包名變化
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null); // 注冊(cè)監(jiān)聽(tīng)廣播
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); // 注冊(cè)UID 狀態(tài)變化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++) { // 一次最多只能同時(shí)run 16個(gè)job
mActiveServices.add(
new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
//getContext().getMainLooper()));
}
// Attach jobs to their controllers.
mJobs.forEachJob(new JobStatusFunctor() { // 將系統(tǒng)中已經(jīng)設(shè)置的所有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 的方法里面監(jiān)聽(tīng)了Package 的一些廣播,以及注冊(cè)了UID 的observer , 將Job 的Controller 也給添加上。發(fā)送MSG_CHECK_JOB 消息
大致工作如下:
App 創(chuàng)建一個(gè)Job并scheduler 它,服務(wù)端流程如何?
當(dāng)使用JobScheduler使用首先會(huì)創(chuàng)建一個(gè)jobscheduler的服務(wù)對(duì)象,當(dāng)調(diào)用scheduler.schedule(builder.build());時(shí)候 scheduler() 便開(kāi)始啟動(dòng)該job 的任務(wù),但是不一定立馬執(zhí)行。
其實(shí)是由JobSchedulerImpl通過(guò)Binder調(diào)用到JobSchedulerService的schedule()中
uid = Binder.getCallingUid();
public int schedule(JobInfo job, int uId) {
.
. // 判斷權(quán)限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())) { //這里通過(guò)AMS來(lái)判斷packageName該應(yīng)該是否允許啟動(dòng)該服務(wù),
return JobScheduler.RESULT_FAILURE;
}
synchronized (mLock) {
final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); // 判斷系統(tǒng)中該Uid 的App對(duì)應(yīng)的Jobid 是否已經(jīng)存在系統(tǒng)中
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); // 創(chuàng)建一個(gè)對(duì)應(yīng)的JobStatus,并且指定相關(guān)的條件!!!!
if (ENFORCE_MAX_JOBS && packageName == null) {
if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { // 每個(gè)UID 對(duì)應(yīng)的Job 最多不能同時(shí)存在系統(tǒng)中100個(gè)
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()); //準(zhǔn)備
if (toCancel != null) {
cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app"); // 如果系統(tǒng)中已經(jīng)存在了同一個(gè)uid 里面的同一個(gè)jobId 的job ,那么先cancle 這個(gè)job
}
if (work != null) {
// If work has been supplied, enqueue it into the new job.
jobStatus.enqueueWorkLocked(ActivityManager.getService(), work); //如果執(zhí)行了work隊(duì)列那么 將jobStatus 放入指定的work隊(duì)列里
}
startTrackingJobLocked(jobStatus, toCancel); // 開(kāi)始將App 的所有的Job的放到mJobs里表里,如果并且對(duì)每個(gè)job 指定對(duì)應(yīng)的不同Controller
if (isReadyToBeExecutedLocked(jobStatus)) { 如果一個(gè)job 滿足一定條件需要立即執(zhí)行,那么會(huì)將其放在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;
}
當(dāng)創(chuàng)建一個(gè)job任務(wù)的時(shí)候,會(huì)先判斷該package的啟動(dòng)服務(wù)權(quán)限,并且JobScheduler中維持了一個(gè)mJobs的列表保存這系統(tǒng)中所有的Job任務(wù),當(dāng)新創(chuàng)建一個(gè)job任務(wù)的時(shí)候會(huì)先判斷當(dāng)前系統(tǒng)中是否存在一個(gè)已有的job,如果存在的話,先將其cancel。 然后將該開(kāi)始tracking 該job ,為其指定對(duì)應(yīng)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); // 開(kāi)始Tracking 該Job,并指定對(duì)應(yīng)的Controller,JobStatus 中也初始化對(duì)應(yīng)的條件
}
}
}
當(dāng)開(kāi)始scheduler 一個(gè)job 的時(shí)候,會(huì)調(diào)用startTrackingJobLocked 方法,將當(dāng)前的時(shí)間賦值給enqueueTime,并且會(huì)先判斷系統(tǒng)mJobs列表中是否已經(jīng)存在這個(gè)job ,如果存在,那么會(huì)先刪掉這個(gè)job 然后重新添加上,繼而調(diào)用controller 的
maybeStartTrackingJobLocked方法開(kāi)始Tracking 該job。
從源碼里看到scheduler 一個(gè)job 流程其實(shí)并不難,調(diào)用的交互類也很少也不多,JSS,JSI,JSC,JobStore等。但是該流程僅僅是將Job加到一個(gè)mJobs列表中,以及指定對(duì)應(yīng)的StateController
其大概流程圖如下(圖片來(lái)自Gityuan):
Job設(shè)定好了之后如何去觸發(fā)它呢
首先我們知道JobScheduler 中維持了8個(gè)Controller,分別是:AppIdleController.java(App Idle 狀態(tài)控制器),BatteryController.java(電池狀態(tài)控制器),ConnectivityController.java (網(wǎng)絡(luò)連接狀態(tài)控制器),ContentObserverController.java(content監(jiān)聽(tīng)狀態(tài)控制器),DeviceIdleJobsController.java(Doze狀態(tài)控制器),StorageController.java(存儲(chǔ)狀態(tài)控制器),TimeController.java(時(shí)間控制器),IdleController.java(設(shè)備空閑狀態(tài)控制器)。這些controller 控制器主要是控制對(duì)應(yīng)Job 需要滿足的條件是否滿足,是否所有條件都滿足,才允許執(zhí)行。比如說(shuō):我一個(gè)App 如下圖設(shè)置一個(gè)Job
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService)
.setRequiresDeviceIdle(true) // 需要Device idle 狀態(tài)下才能執(zhí)行
.setMinimumLatency(15* 60 * 1000); //設(shè)置延遲15min,在N 上有限制,最小延遲時(shí)間不得小小于15分鐘,如果小于15分鐘,則會(huì)主動(dòng)將你的 MinimumLatency修改為15分鐘。
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 需要網(wǎng)絡(luò)非計(jì)費(fèi)類型
.setRequiresCharging(true); // 需要充電狀態(tài)
js.schedule(builder.build());
這里能看到,這個(gè)job 設(shè)置了四個(gè)條件,那么對(duì)應(yīng)到服務(wù)端模塊,從接口上來(lái)看就會(huì)有TimeController.java(時(shí)間控制器),ConnectivityController.java (網(wǎng)絡(luò)連接狀態(tài)控制器),BatteryController.java(電池狀態(tài)控制器),IdleController.java(設(shè)備空閑狀態(tài)控制器)四個(gè)控制器來(lái)監(jiān)控這個(gè)設(shè)備狀態(tài),當(dāng)四個(gè)狀態(tài)都滿足了,也就是:設(shè)備處于空閑狀態(tài),從開(kāi)始調(diào)度到現(xiàn)在已經(jīng)過(guò)了超過(guò)15分鐘,手機(jī)網(wǎng)絡(luò)處于非計(jì)費(fèi)網(wǎng)絡(luò),手機(jī)處于充電狀態(tài)。那么job 任務(wù)就可以執(zhí)行了。
但是反饋到system server 它是怎么運(yùn)行的呢?
首先我們要需要介紹一下Job 創(chuàng)建時(shí),如何和Controller 綁定起來(lái)的。我們?cè)?.1 中有提到,在 scheduleAsPackage 方法中會(huì)通過(guò)JobInfo 創(chuàng)建對(duì)應(yīng)JobStatus對(duì)象。
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 是循環(huán)的
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); // 最晚執(zhí)行時(shí)間 = 當(dāng)前時(shí)間+ 循環(huán)間隔
earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); //最早執(zhí)行時(shí)間 = 最晚觸發(fā)時(shí)間 - Flex(靈活窗口)時(shí)間 , Flex時(shí)間是三個(gè)時(shí)間(5min,5 * interval / 100, 設(shè)置的flexMillis時(shí)間)中最大值
} else {
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? // job 有最早執(zhí)行限制(默認(rèn)有),則 最早執(zhí)行時(shí)間=當(dāng)前時(shí)間+最小延遲
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = job.hasLateConstraint() ? // job 有最晚執(zhí)行限制(默認(rèn)有),則最晚執(zhí)行時(shí)間=當(dāng)前時(shí)間+最大延遲
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, // 通過(guò)JobInfo,以及執(zhí)行時(shí)間參數(shù)創(chuàng)建JobStatus
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
}
在創(chuàng)建用JobInfo builder 一個(gè)job 的時(shí)候,用constraintFlags 變量去使用置位的方式標(biāo)記該job 的一個(gè)條件限制,當(dāng)設(shè)置一個(gè)限制條件,則在相應(yīng)的位上置1。最終形成的JobInfo 對(duì)象中包含了該參數(shù),在創(chuàng)建JobStatus 的時(shí)機(jī)作為Controller 的綁定判斷條件。
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) {
this.job = job;
...
int requiredConstraints = job.getConstraintFlags(); //從JobInfo 中獲取constraintFlags 變量來(lái)作為requiredConstraints 賦值給JobStatus 中的requiredConstraints參數(shù)
switch (job.getNetworkType()) { //對(duì)于網(wǎng)絡(luò)type限制 ,指定為不同的CONSTRAINT 位
case JobInfo.NETWORK_TYPE_NONE:
// No constraint.
break;
case JobInfo.NETWORK_TYPE_ANY:
requiredConstraints |= CONSTRAINT_CONNECTIVITY;
break;
case JobInfo.NETWORK_TYPE_UNMETERED:
requiredConstraints |= CONSTRAINT_UNMETERED;
break;
case JobInfo.NETWORK_TYPE_NOT_ROAMING:
requiredConstraints |= CONSTRAINT_NOT_ROAMING;
break;
case JobInfo.NETWORK_TYPE_METERED:
requiredConstraints |= CONSTRAINT_METERED;
break;
default:
Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType());
break;
}
//對(duì)于不同時(shí)間type限制 ,指定為不同的CONSTRAINT 位
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_TIMING_DELAY;
}
if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_DEADLINE;
}
if (job.getTriggerContentUris() != null) {
requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
}
this.requiredConstraints = requiredConstraints; // 將包裝后的requiredConstraints 賦值給JobStatus的變量requiredConstraints。
...
}
JobScheduler 服務(wù)這里總共有8個(gè)Controller ,比較特殊的有:AppIdleController.java 和 DeviceIdleJobsController.java,這兩個(gè)controller 和其他6個(gè)不一樣,他們是只要設(shè)置了job ,便都受這兩個(gè)Controller 的控制。這個(gè)后面會(huì)詳細(xì)說(shuō)明。大部分都是類似的,以其中一個(gè)Controller 為例
BatteryController.java,來(lái)理解其他的Controller
當(dāng)設(shè)置一個(gè)Job 的時(shí)候,會(huì)循環(huán)所有的Controller 來(lái)為其指定限制該Job 的Controller
@Override
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasPowerConstraint()) {//該Job 是否有設(shè)置Power限制,
mTrackedTasks.add(taskStatus); // 將該Job 加入到mTrackedJobs 列表中
taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); // 該jobStatus開(kāi)始tracking 電池狀態(tài)
taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower()); // 設(shè)置當(dāng)前是否滿足充電狀態(tài)
taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow()); // 設(shè)置當(dāng)前是否滿足低電狀態(tài)
}
}
// 注冊(cè)監(jiān)聽(tīng)Battery 的一些廣播,在此接受廣播
@VisibleForTesting
public void onReceiveInternal(Intent intent) {
synchronized (mLock) {
final String action = intent.getAction();
...
if (BatteryManager.ACTION_CHARGING.equals(action)) { // 接受到廣播會(huì)將mCharging 置為對(duì)應(yīng)的狀態(tài),并重新檢查一下當(dāng)前狀態(tài)是否滿足job 條件
if (DEBUG) {
Slog.d(TAG, "Received charging intent, fired @ "
+ SystemClock.elapsedRealtime());
}
mCharging = true;
maybeReportNewChargingStateLocked();
} else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Disconnected from power.");
}
mCharging = false;
maybeReportNewChargingStateLocked();
}
}
private void maybeReportNewChargingStateLocked() {
...
if (stablePower || batteryNotLow) {
// If one of our conditions has been satisfied, always schedule any newly ready jobs.
mStateChangedListener.onRunJobNow(null); // 條件滿足立即執(zhí)行job
} else if (reportChange) {
// Otherwise, just let the job scheduler know the state has changed and take care of it
// as it thinks is best.
mStateChangedListener.onControllerStateChanged(); //開(kāi)始狀態(tài)已經(jīng)發(fā)生改變,重新檢查job 是否需要調(diào)度
}
}
從BatteryController 這個(gè)Controller 大概能窺見(jiàn)JobScheduler 的控制器具體實(shí)現(xiàn)邏輯,控制器的邏輯大概如此:
1.在JobSchedulerService schduler一個(gè)job 的時(shí)候在startTrackingJobLocked()方法中,輪詢了所有的Controller調(diào)用maybeStartTrackingJobLocked。
maybeStartTrackingJobLocked 方法內(nèi)會(huì)首先如BatteryController(hasPowerConstraint)判斷是否 對(duì)應(yīng)Controller 的限制條件
在對(duì)應(yīng)Controller 創(chuàng)建的時(shí)候注冊(cè)對(duì)應(yīng)設(shè)備狀態(tài)發(fā)生改變的廣播或者Listener(比BatteryController,便是監(jiān)聽(tīng)Battery相關(guān)的廣播更新?tīng)顟B(tài)參數(shù),ConnectivityController便是在注冊(cè)ConnectivityManager.OnNetworkActiveListener,當(dāng)連接狀態(tài)發(fā)生改變時(shí)候,更新?tīng)顟B(tài)參數(shù))
-
在當(dāng)對(duì)應(yīng)Controller 接收到設(shè)備狀態(tài)發(fā)生改變會(huì)執(zhí)行:
1). 判斷當(dāng)前mTackingJobs列表中是否有ready的job,如果有執(zhí)行 mStateChangedListener.onRunJobNow(js) 來(lái)立即執(zhí)行這個(gè)job(Battery,Connectivity,Time), 2). 判斷當(dāng)前狀態(tài)已經(jīng)發(fā)生改變,那么調(diào)用到mStateChangedListener.onControllerStateChanged() 來(lái)回調(diào)到JobschedulerService中。
JobschedulerService收到回調(diào),發(fā)送消息MSG_CHECK_JOB 來(lái)來(lái)檢查當(dāng)前手機(jī)是否處于Idle狀態(tài),來(lái)執(zhí)行對(duì)應(yīng)可以執(zhí)行的Job。
服務(wù)端如何管理Job
我們知道無(wú)論是Job服務(wù),還是Alarm,或者是廣播,Service 等在系統(tǒng)中是怎么管理的,那么我們首先是需要找準(zhǔn)一條主線去一一捋清他的關(guān)系。而在JobSchedulerService 這個(gè)服務(wù)中,我們選擇的這個(gè)主線還是從設(shè)置一個(gè)Job 到觸發(fā)job 的時(shí)刻,服務(wù)端的一個(gè)流程。
該流程其實(shí)也是JobScheduler 服務(wù)的主要流程,但是這里我重點(diǎn)從job 管理這一塊去理清各個(gè)關(guān)鍵類的角色關(guān)系,以及關(guān)鍵參數(shù)的變化。
-
開(kāi)機(jī)會(huì)做三件事:
1). 將8個(gè)controller 全部添加到mControllers 列表中,并開(kāi)始監(jiān)控系統(tǒng)中的狀態(tài)改變,
2). 從/data/system/job/job.xml 中讀取持久化保存的job 并將其加入到系統(tǒng)中mJobs中。
3). 在jobScheduler服務(wù)啟動(dòng)完畢后,發(fā)送消息MSG_CHECK_JOB 開(kāi)始檢查 從App 端設(shè)置調(diào)用jobscheduler.scheduler(job) 服務(wù),當(dāng)服務(wù)滿足各種條件之后,在startTrackingJobLocked 方法中開(kāi)始對(duì)該Job 進(jìn)行跟蹤tracking , 并將其分配對(duì)應(yīng)的Controller控制器。 添加到 mJobs 這個(gè)保存系統(tǒng)中所有Job 的列表,如果該Job 設(shè)置了persist ,那么將其異步寫(xiě)到文件中
-
JobSchedulerService 中維持了6個(gè)重要變量:
mActiveService: 維持了一個(gè)JobServiceContext 的列表 ,表示當(dāng)前正在運(yùn)行的Job 的Context,管理著job 的生命周期
mPendingJob: 一個(gè)JobStatus 的列表,保存當(dāng)前pending掛起的job ,這些job都是一些準(zhǔn)備執(zhí)行的job 或者已經(jīng)滿足條件即將被執(zhí)行的job
mJobPackageTracker:對(duì)一個(gè)package 包下的所有Job 的追蹤類,主要追蹤job 的運(yùn)行各種時(shí)間。
mJobs : JobStore 的對(duì)象,JobStore 是一個(gè)單例模式類,有兩個(gè)主要作用:1. 保存著系統(tǒng)中所有進(jìn)程設(shè)置的job到變量mJobSet,
mJobSet 是一個(gè)以UID 為key的List列表, 2. 持久化到本地/data/system/job/jobs.xml (需要setPersist(true))
mMaybeQueueFunctor:mJobs.forEachJob 會(huì)調(diào)用的到JobStore中的 mJobSet 在forEachJob()方法,在此方法中,會(huì)針對(duì)uid對(duì)應(yīng)的Job遍歷執(zhí)行Functor的process 方法,是否有ready的job ,如果有則滿足條件符合其一種加入到mPendingJob,(針對(duì)手機(jī)處于當(dāng)前沒(méi)有Active的Job)
mReadyQueueFuntor: 功能和mMaybeQueueFunctor 類似,當(dāng)有的ready 的job,則直接加入到mPendingJob 中。(針對(duì)手機(jī)處于當(dāng)前有Active的Job的狀態(tài)。) 在兩個(gè)QueueFuntor 中,都會(huì)通過(guò)mPakcageTracker 將對(duì)應(yīng)的Job 標(biāo)記nopending 和pending的時(shí)間。
其 服務(wù)端關(guān)系類圖大概如下:
對(duì)上面圖做大概解釋:
- App 進(jìn)程通過(guò)Jobscheduler 調(diào)用到代理類JobschedulerImpl 中binder call 去調(diào)度一個(gè)job 開(kāi)始
- 便會(huì)將Job 加入到mJobs中,mJobs是JobStore的一個(gè)對(duì)象。JobStore 中還包裝了兩個(gè)變量mJobSet(用來(lái)存放所有Job的),和mJobsFile用來(lái)持久化一些persist的job
- JobSet 中保存有mJobs變量,在每次調(diào)度一個(gè)job 的時(shí)候,會(huì)判斷該job 是否為persist的job,按照如上圖所示結(jié)構(gòu)存儲(chǔ)
- 服務(wù)端還有一個(gè)變量較為關(guān)鍵是mPendingJobs 列表,為JobServiceContext 的一個(gè)Array,里面存放的都是已經(jīng)ready 的或者即將要被執(zhí)行的job
- JobPackageTracker 主要是追蹤一個(gè)包中job 的時(shí)間參數(shù),用以在計(jì)算該job 優(yōu)先級(jí)的時(shí)候,會(huì)以該job 的活躍時(shí)間占整個(gè)rebatch (30min)時(shí)間段的時(shí)間比重來(lái)重新分配優(yōu)先級(jí)
- 保存job 中所有詳細(xì)信息,包含:jobinfo ,job限制條件,job 的當(dāng)前狀態(tài)已滿足條件。 7. JobPackageTracker 中有個(gè)DataSet 的數(shù)據(jù)結(jié)構(gòu)。如圖所示
Controller——條件控制器
JobScheduler 服務(wù)這里總共有8個(gè)Controller ,比較特殊的有:AppIdleController.java 和 DeviceIdleJobsController.java,這兩個(gè)controller 和其他6個(gè)不一樣,他們是只要設(shè)置了job ,便都受這兩個(gè)Controller 的控制。這個(gè)后面會(huì)詳細(xì)說(shuō)明。大部分都是類似的,以其中一個(gè)Controller 為例
TimeController.java,來(lái)理解其他的Controller
當(dāng)設(shè)置一個(gè)Job 的時(shí)候,會(huì)循環(huán)所有的Controller 來(lái)為其指定限制該Job 的Controller
@Override
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
maybeStopTrackingJobLocked(job, null, false);
…………
it.add(job);
job.setTrackingController(JobStatus.TRACKING_TIME); // 開(kāi)始tracking 時(shí)間控制器
maybeUpdateAlarmsLocked(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
deriveWorkSource(job.getSourceUid(), job.getSourcePackageName())); // 設(shè)置delay 或者dead line 的alarm的時(shí)間
}
}
// 設(shè)置delay 和deadline 的的一些alarm
private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
mNextDelayExpiredElapsedMillis, ws);
}
private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
mNextJobExpiredElapsedMillis, ws);
}
private void checkExpiredDelaysAndResetAlarm() {
…………
if (ready) {
mStateChangedListener.onControllerStateChanged();
}
setDelayExpiredAlarmLocked(nextDelayTime,
deriveWorkSource(nextDelayUid, nextDelayPackageName));
}
}
TimeController 的大概流程如下:
- JobSchedulerService 在循環(huán)設(shè)置controller 的時(shí)候,會(huì)先調(diào)用到每個(gè)controller 的aybeStartTrackingJobLocked()方法
- 判斷該job 是否有延遲和戒指時(shí)間限制。如果沒(méi)有,那么不為此job 設(shè)置該controller控制器
- 如果滿足時(shí)間限制,那么將該job 經(jīng)過(guò)一些合理性判斷后,加入到mTrackedJobs表中(滿足延遲? 延遲時(shí)間:無(wú)窮大時(shí)間; 滿足截止?截止時(shí)間:無(wú)窮大時(shí)間)
- 調(diào)用到 maybeUpdateAlarmsLocked 中,判斷當(dāng)前job最早觸發(fā)時(shí)間和,最遲觸發(fā)時(shí)間是否滿足條件。如果滿足,則設(shè)置其對(duì)用的Alarm,并指定對(duì)應(yīng)的Listener
- 當(dāng)Alarm 觸發(fā)時(shí)間到了,則會(huì)調(diào)用到對(duì)應(yīng)方法中,檢查當(dāng)前mTrackedJobs中是否有滿足條件的
-
如果滿足delay 時(shí)間的,則通知JobSchedulerService中去且檢查job 是否應(yīng)該觸發(fā),如果滿足deadline的,則去直接run該job
TimeController.png
除了TimeoController 與我們聯(lián)系比較多,還有兩個(gè)controller 非常特殊,那就是DeviceIdleJobController & AppIdleController 。他們的特殊主要有三點(diǎn):
所有的Job 都必須受到這兩個(gè)controller 的控制
DeviceIdleJobController 監(jiān)控設(shè)備是否進(jìn)入Doze 模式,如果進(jìn)入doze 模式,則所有的job都不能被執(zhí)行,即使正在被執(zhí)行的job 也會(huì)停掉
AppIdleController 控制App 狀態(tài),如果一個(gè)app 處于了Idle狀態(tài),則會(huì)從UsageStateService.reportEvent()回調(diào)到AppIdleController。然后判斷當(dāng)前App是否為idle狀態(tài),如果是則該job即使其他條件都滿足也不會(huì)被執(zhí)行。
(注: 如何為idle狀態(tài)呢:1. 非deviceAdmin app; 2. 不是當(dāng)前給網(wǎng)絡(luò)打分的app (NETWORK_SCORE_SERVICE);3.桌面上沒(méi)有綁定小部件; 4.不是開(kāi)機(jī)引導(dǎo); 5.不是idle(距離上次亮屏大于12小時(shí)且2天沒(méi)有用過(guò)該app);6.不是運(yùn)營(yíng)商app。)
Android P 關(guān)于Job的新特性
Android 9引入了新的電池管理功能App Standby Buckets。 App Standby Buckets可根據(jù)應(yīng)用程序的最近使用頻率和頻率,幫助系統(tǒng)確定應(yīng)用程序資源請(qǐng)求的優(yōu)先級(jí)。 根據(jù)應(yīng)用程序使用模式,每個(gè)應(yīng)用程序都放在五個(gè)優(yōu)先級(jí)存儲(chǔ)區(qū)之一。 系統(tǒng)會(huì)根據(jù)應(yīng)用所在的存儲(chǔ)區(qū)限制每個(gè)應(yīng)用可用的設(shè)備資源。
共有五個(gè)standby buckets:
Active: 用戶正在使用該app,包括:lunched activity, fg service, provider used by fg app, clicks app's notification
Working set:經(jīng)常運(yùn)行,但當(dāng)前并不是active狀態(tài),例如:社交app,media app等
Frequent:經(jīng)常使用類,但是并不一定是每天使用,例如:健身 app,團(tuán)購(gòu)app,出行app等
Rare:很少使用類,例如:訂票app,工具類app,購(gòu)物類app等
Never:安裝了但是從未運(yùn)行的app,系統(tǒng)對(duì)該類app 做了非常嚴(yán)格的限制
android 官網(wǎng)上一張圖能說(shuō)明各個(gè)不同的bucket 對(duì)于不同的行為的限制
具體是怎么操作的呢一張簡(jiǎn)圖來(lái)說(shuō)明一下
Android P 上針對(duì)UsageStatsService 服務(wù)添加了兩個(gè)接口,setAppStandbyBucket 和getAppStandbyBucket 方法
getAppStandbyBucket(): 從系統(tǒng)中獲取,各個(gè)App 所在的Bucket 。
setAppStandbyBucket: 將機(jī)器學(xué)習(xí)訓(xùn)練好的模型,預(yù)測(cè)出的App 屬于哪個(gè)bucket 設(shè)置到系統(tǒng)中。
其P 上機(jī)器學(xué)習(xí)的流程大致如下:
- 具有機(jī)器學(xué)習(xí)模型的App 調(diào)用方法queryEvents 獲取某一段時(shí)間內(nèi)的一些,用戶使用App的活動(dòng)數(shù)據(jù)
- 并且調(diào)用getAppStandbyBucket 獲取系統(tǒng)中所有App 所處的bucket(usageStatsService 根據(jù)使用頻率計(jì)算出來(lái)的結(jié)果)。
- 結(jié)合1,2 中數(shù)據(jù),導(dǎo)入機(jī)器學(xué)習(xí)預(yù)測(cè)框架中,預(yù)測(cè)某一時(shí)間斷,app 應(yīng)該處于哪個(gè)bucket中。
- 應(yīng)用處于不同的bucket 中,對(duì)于App 的NetWo(hù)rk,Alarm,Job 都有不同的限制。
那么他是怎么限制Job 的
- mHeartbeat :在HeartbeatAlarmListener 每次觸發(fā)一次(11分觸發(fā)一次),就會(huì)將已經(jīng)觸發(fā)的job數(shù)目累加起來(lái)
- 每次觸發(fā)alarm 的時(shí)候會(huì)判斷mHeartbeat > appLastRan? appLastRan 是上一次該job的觸發(fā)時(shí)的心跳數(shù)目
- 但job 要觸發(fā)的時(shí)候,便會(huì)經(jīng)歷以上判斷。 其實(shí)是通過(guò)heartbeat 的數(shù)目來(lái)觸發(fā)的。一次觸發(fā)多個(gè)job
綜述
jobScheduler 是一個(gè)提供給App 端設(shè)定一個(gè)滿足條件執(zhí)行的任務(wù),從App 設(shè)定一個(gè)Job到服務(wù)端接受這個(gè)job ,到服務(wù)端管理這個(gè)job,到最終觸發(fā)這個(gè)job 以及重系統(tǒng)中移除,整個(gè)流程整體并不太復(fù)雜,而是細(xì)節(jié)計(jì)算上有很多點(diǎn)比較讓人煩惱,我這里大概將其流程梳理一遍:
- App 創(chuàng)建一個(gè)Job,并scheduler該job
- JobSchedulerImpl通過(guò)Binder調(diào)用,調(diào)用到服務(wù)端JobSchedulerService端的scheduleAsPackage
- 在添加到mJobs集合中之前先搜索系統(tǒng)中是否有相同的Job已經(jīng)存在,如果存在則先canel掉并,再將其加入到mJobs中
- 判斷該Job是否有對(duì)應(yīng)的限制條件為其Job 分配對(duì)應(yīng)的Controller 。
- Controllers 中所有的控制器要么是監(jiān)聽(tīng)廣播,要么是注冊(cè)listener 去監(jiān)聽(tīng)系統(tǒng)狀態(tài)改變,當(dāng)系統(tǒng)狀態(tài)發(fā)生改變則都會(huì)去通過(guò)回調(diào)到JobSchedulerService
- onStateChanged中在發(fā)送MSG_CHECK_JOB 消息,處理對(duì)應(yīng)消息,首先判斷mReportedActive 是否為true
- 將ready好的Job加入到mPendingsJob列表中,然后調(diào)用到assignJobsToContextsLocked 核心方法
- 在assignJobsToContextsLocked 完成各種計(jì)算后,將mActiveService的可以運(yùn)行的Job,調(diào)用executeRunnableJob 方法。
- 調(diào)用到JobServiceContext 中,開(kāi)始啟動(dòng)服務(wù)bindService。在當(dāng)服務(wù)binder上App端的JobService 服務(wù),回調(diào)回onServiceConnected()
- 在doServiceBoundLocked() 中調(diào)用到handleServiceBoundLocked中通過(guò)service.startJob() ,繼而通過(guò)BinderCall 回調(diào)到JobServiceEnginee中,發(fā)送消息MSG_EXECUTE_JOB到main線程中
- 調(diào)用到App 實(shí)現(xiàn)的JobService 中onStartjob中,執(zhí)行App JobService 的具體事物
- App端調(diào)用JobFinish,binder call 調(diào)用到JobServiceContext ,清理該Job的一些資源和變量,并將其從mJobStore 中的刪掉。
下面是終極方法調(diào)用流程圖: