1. 程式人生 > >【Android P】 JobScheduler服務原始碼解析(二) ——服務端啟動解析

【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