美文网首页Android Framework模块
【Android P】 JobScheduler服务源码解析(二

【Android P】 JobScheduler服务源码解析(二

作者: 风雨田 | 来源:发表于2018-10-23 20:31 被阅读0次

    JoScheduler服务框架分析

    前面(一)中基本已经App端如何使用Job 服务做了一个比较详细的介绍,这里将会对客户端几个重要类解析;其承担的角色;App 从scheduler 一个job 到执行这个job ,其中服务端的调用流程;以及服务端如何管理系统中所有的job 等等问题,将在此做一个全面的解答。

    客户端

    App端从创建一个job 到调度一个Job流程是怎样的?
    Job在客户端主要比较重要的类有四个: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 时,各个类的作用以及其角色: 流程图如下

    App端类流程图.png

    根据上图的角色来看:

    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 的启动流程:


    启动流程图.png

    下面来详细介绍一下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 消息
    大致工作如下:

    启动工作.png

    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):


    流程图.png

    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 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
    }
    

    在创建用JobInfo builder 一个job 的时候,用constraintFlags 变量去使用置位的方式标记该job 的一个条件限制,当设置一个限制条件,则在相应的位上置1。最终形成的JobInfo 对象中包含了该参数,在创建JobStatus 的时机作为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 变量来作为requiredConstraints 赋值给JobStatus 中的requiredConstraints参数
            switch (job.getNetworkType()) {  //对于网络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;
            }
     
            //对于不同时间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 服务这里总共有8个Controller ,比较特殊的有:AppIdleController.java 和 DeviceIdleJobsController.java,这两个controller 和其他6个不一样,他们是只要设置了job ,便都受这两个Controller 的控制。这个后面会详细说明。大部分都是类似的,以其中一个Controller 为例

    BatteryController.java,来理解其他的Controller

    当设置一个Job 的时候,会循环所有的Controller 来为其指定限制该Job 的Controller

    @Override
    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
        if (taskStatus.hasPowerConstraint()) {//该Job 是否有设置Power限制,
            mTrackedTasks.add(taskStatus);   // 将该Job 加入到mTrackedJobs 列表中
            taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); // 该jobStatus开始tracking 电池状态
            taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());  // 设置当前是否满足充电状态
            taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow()); // 设置当前是否满足低电状态
        }
    }
      
      
         // 注册监听Battery 的一些广播,在此接受广播
        @VisibleForTesting
        public void onReceiveInternal(Intent intent) {
            synchronized (mLock) {
                final String action = intent.getAction();
      
              ...
              if (BatteryManager.ACTION_CHARGING.equals(action)) { // 接受到广播会将mCharging 置为对应的状态,并重新检查一下当前状态是否满足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);   // 条件满足立即执行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(); //开始状态已经发生改变,重新检查job 是否需要调度
        }
    }
    

    从BatteryController 这个Controller 大概能窥见JobScheduler 的控制器具体实现逻辑,控制器的逻辑大概如此:

    1.在JobSchedulerService schduler一个job 的时候在startTrackingJobLocked()方法中,轮询了所有的Controller调用maybeStartTrackingJobLocked。

    1. maybeStartTrackingJobLocked 方法内会首先如BatteryController(hasPowerConstraint)判断是否 对应Controller 的限制条件

    2. 在对应Controller 创建的时候注册对应设备状态发生改变的广播或者Listener(比BatteryController,便是监听Battery相关的广播更新状态参数,ConnectivityController便是在注册ConnectivityManager.OnNetworkActiveListener,当连接状态发生改变时候,更新状态参数)

    3. 在当对应Controller 接收到设备状态发生改变会执行:

       1). 判断当前mTackingJobs列表中是否有ready的job,如果有执行 mStateChangedListener.onRunJobNow(js) 来立即执行这个job(Battery,Connectivity,Time),
      
       2). 判断当前状态已经发生改变,那么调用到mStateChangedListener.onControllerStateChanged() 来回调到JobschedulerService中。
      
    4. JobschedulerService收到回调,发送消息MSG_CHECK_JOB 来来检查当前手机是否处于Idle状态,来执行对应可以执行的Job。

    服务端如何管理Job

    我们知道无论是Job服务,还是Alarm,或者是广播,Service 等在系统中是怎么管理的,那么我们首先是需要找准一条主线去一一捋清他的关系。而在JobSchedulerService 这个服务中,我们选择的这个主线还是从设置一个Job 到触发job 的时刻,服务端的一个流程。

    该流程其实也是JobScheduler 服务的主要流程,但是这里我重点从job 管理这一块去理清各个关键类的角色关系,以及关键参数的变化。

    1. 开机会做三件事:

      1). 将8个controller 全部添加到mControllers 列表中,并开始监控系统中的状态改变,
      2). 从/data/system/job/job.xml 中读取持久化保存的job 并将其加入到系统中mJobs中。
      3). 在jobScheduler服务启动完毕后,发送消息MSG_CHECK_JOB 开始检查

    2. 从App 端设置调用jobscheduler.scheduler(job) 服务,当服务满足各种条件之后,在startTrackingJobLocked 方法中开始对该Job 进行跟踪tracking , 并将其分配对应的Controller控制器。 添加到 mJobs 这个保存系统中所有Job 的列表,如果该Job 设置了persist ,那么将其异步写到文件中

    3. JobSchedulerService 中维持了6个重要变量:

      mActiveService: 维持了一个JobServiceContext 的列表 ,表示当前正在运行的Job 的Context,管理着job 的生命周期
      mPendingJob: 一个JobStatus 的列表,保存当前pending挂起的job ,这些job都是一些准备执行的job 或者已经满足条件即将被执行的job
      mJobPackageTracker:对一个package 包下的所有Job 的追踪类,主要追踪job 的运行各种时间。
      mJobs : JobStore 的对象,JobStore 是一个单例模式类,有两个主要作用:1. 保存着系统中所有进程设置的job到变量mJobSet,
      mJobSet 是一个以UID 为key的List列表, 2. 持久化到本地/data/system/job/jobs.xml (需要setPersist(true))
      mMaybeQueueFunctor:mJobs.forEachJob 会调用的到JobStore中的 mJobSet 在forEachJob()方法,在此方法中,会针对uid对应的Job遍历执行Functor的process 方法,是否有ready的job ,如果有则满足条件符合其一种加入到mPendingJob,(针对手机处于当前没有Active的Job)
      mReadyQueueFuntor: 功能和mMaybeQueueFunctor 类似,当有的ready 的job,则直接加入到mPendingJob 中。(针对手机处于当前有Active的Job的状态。)

    4. 在两个QueueFuntor 中,都会通过mPakcageTracker 将对应的Job 标记nopending 和pending的时间。

    其 服务端关系类图大概如下:


    服务端关系类图.png

    对上面图做大概解释:

    1. App 进程通过Jobscheduler 调用到代理类JobschedulerImpl 中binder call 去调度一个job 开始
    2. 便会将Job 加入到mJobs中,mJobs是JobStore的一个对象。JobStore 中还包装了两个变量mJobSet(用来存放所有Job的),和mJobsFile用来持久化一些persist的job
    3. JobSet 中保存有mJobs变量,在每次调度一个job 的时候,会判断该job 是否为persist的job,按照如上图所示结构存储
    4. 服务端还有一个变量较为关键是mPendingJobs 列表,为JobServiceContext 的一个Array,里面存放的都是已经ready 的或者即将要被执行的job
    5. JobPackageTracker 主要是追踪一个包中job 的时间参数,用以在计算该job 优先级的时候,会以该job 的活跃时间占整个rebatch (30min)时间段的时间比重来重新分配优先级
    6. 保存job 中所有详细信息,包含:jobinfo ,job限制条件,job 的当前状态已满足条件。 7. JobPackageTracker 中有个DataSet 的数据结构。如图所示

    Controller——条件控制器

    JobScheduler 服务这里总共有8个Controller ,比较特殊的有:AppIdleController.java 和 DeviceIdleJobsController.java,这两个controller 和其他6个不一样,他们是只要设置了job ,便都受这两个Controller 的控制。这个后面会详细说明。大部分都是类似的,以其中一个Controller 为例

    TimeController.java,来理解其他的Controller

    当设置一个Job 的时候,会循环所有的Controller 来为其指定限制该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); // 开始tracking 时间控制器
                maybeUpdateAlarmsLocked(
                        job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
                        job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
                        deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));  // 设置delay 或者dead line 的alarm的时间
            }
    }
      
      
         // 设置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 的大概流程如下:

    1. JobSchedulerService 在循环设置controller 的时候,会先调用到每个controller 的aybeStartTrackingJobLocked()方法
    2. 判断该job 是否有延迟和戒指时间限制。如果没有,那么不为此job 设置该controller控制器
    3. 如果满足时间限制,那么将该job 经过一些合理性判断后,加入到mTrackedJobs表中(满足延迟? 延迟时间:无穷大时间; 满足截止?截止时间:无穷大时间)
    4. 调用到 maybeUpdateAlarmsLocked 中,判断当前job最早触发时间和,最迟触发时间是否满足条件。如果满足,则设置其对用的Alarm,并指定对应的Listener
    5. 当Alarm 触发时间到了,则会调用到对应方法中,检查当前mTrackedJobs中是否有满足条件的
    6. 如果满足delay 时间的,则通知JobSchedulerService中去且检查job 是否应该触发,如果满足deadline的,则去直接run该job


      TimeController.png

    除了TimeoController 与我们联系比较多,还有两个controller 非常特殊,那就是DeviceIdleJobController & AppIdleController 。他们的特殊主要有三点:

    1. 所有的Job 都必须受到这两个controller 的控制

    2. DeviceIdleJobController 监控设备是否进入Doze 模式,如果进入doze 模式,则所有的job都不能被执行,即使正在被执行的job 也会停掉

    3. AppIdleController 控制App 状态,如果一个app 处于了Idle状态,则会从UsageStateService.reportEvent()回调到AppIdleController。然后判断当前App是否为idle状态,如果是则该job即使其他条件都满足也不会被执行。

    (注: 如何为idle状态呢:1. 非deviceAdmin app; 2. 不是当前给网络打分的app (NETWORK_SCORE_SERVICE);3.桌面上没有绑定小部件; 4.不是开机引导; 5.不是idle(距离上次亮屏大于12小时且2天没有用过该app);6.不是运营商app。)

    Android P 关于Job的新特性

    Android 9引入了新的电池管理功能App Standby Buckets。 App Standby Buckets可根据应用程序的最近使用频率和频率,帮助系统确定应用程序资源请求的优先级。 根据应用程序使用模式,每个应用程序都放在五个优先级存储区之一。 系统会根据应用所在的存储区限制每个应用可用的设备资源。

    共有五个standby buckets:

    Active: 用户正在使用该app,包括:lunched activity, fg service, provider used by fg app, clicks app's notification
    Working set:经常运行,但当前并不是active状态,例如:社交app,media app等 
    Frequent:经常使用类,但是并不一定是每天使用,例如:健身 app,团购app,出行app等 
    Rare:很少使用类,例如:订票app,工具类app,购物类app等 
    Never:安装了但是从未运行的app,系统对该类app 做了非常严格的限制
    

    android 官网上一张图能说明各个不同的bucket 对于不同的行为的限制


    AndroidP.png

    具体是怎么操作的呢一张简图来说明一下


    机器学习操作流程.png

    Android P 上针对UsageStatsService 服务添加了两个接口,setAppStandbyBucket 和getAppStandbyBucket 方法

    getAppStandbyBucket(): 从系统中获取,各个App 所在的Bucket 。

    setAppStandbyBucket: 将机器学习训练好的模型,预测出的App 属于哪个bucket 设置到系统中。

    其P 上机器学习的流程大致如下:

    1. 具有机器学习模型的App 调用方法queryEvents 获取某一段时间内的一些,用户使用App的活动数据
    2. 并且调用getAppStandbyBucket 获取系统中所有App 所处的bucket(usageStatsService 根据使用频率计算出来的结果)。
    3. 结合1,2 中数据,导入机器学习预测框架中,预测某一时间断,app 应该处于哪个bucket中。
    4. 应用处于不同的bucket 中,对于App 的NetWork,Alarm,Job 都有不同的限制。

    那么他是怎么限制Job 的


    bucket如何限制job.png
    1. mHeartbeat :在HeartbeatAlarmListener 每次触发一次(11分触发一次),就会将已经触发的job数目累加起来
    2. 每次触发alarm 的时候会判断mHeartbeat > appLastRan? appLastRan 是上一次该job的触发时的心跳数目
    3. 但job 要触发的时候,便会经历以上判断。 其实是通过heartbeat 的数目来触发的。一次触发多个job

    综述

    jobScheduler 是一个提供给App 端设定一个满足条件执行的任务,从App 设定一个Job到服务端接受这个job ,到服务端管理这个job,到最终触发这个job 以及重系统中移除,整个流程整体并不太复杂,而是细节计算上有很多点比较让人烦恼,我这里大概将其流程梳理一遍:

    1. App 创建一个Job,并scheduler该job
    2. JobSchedulerImpl通过Binder调用,调用到服务端JobSchedulerService端的scheduleAsPackage
    3. 在添加到mJobs集合中之前先搜索系统中是否有相同的Job已经存在,如果存在则先canel掉并,再将其加入到mJobs中
    4. 判断该Job是否有对应的限制条件为其Job 分配对应的Controller 。
    5. Controllers 中所有的控制器要么是监听广播,要么是注册listener 去监听系统状态改变,当系统状态发生改变则都会去通过回调到JobSchedulerService
    6. onStateChanged中在发送MSG_CHECK_JOB 消息,处理对应消息,首先判断mReportedActive 是否为true
    7. 将ready好的Job加入到mPendingsJob列表中,然后调用到assignJobsToContextsLocked 核心方法
    8. 在assignJobsToContextsLocked 完成各种计算后,将mActiveService的可以运行的Job,调用executeRunnableJob 方法。
    9. 调用到JobServiceContext 中,开始启动服务bindService。在当服务binder上App端的JobService 服务,回调回onServiceConnected()
    10. 在doServiceBoundLocked() 中调用到handleServiceBoundLocked中通过service.startJob() ,继而通过BinderCall 回调到JobServiceEnginee中,发送消息MSG_EXECUTE_JOB到main线程中
    11. 调用到App 实现的JobService 中onStartjob中,执行App JobService 的具体事物
    12. App端调用JobFinish,binder call 调用到JobServiceContext ,清理该Job的一些资源和变量,并将其从mJobStore 中的删掉。

    下面是终极方法调用流程图:


    JobScheduler触发Job流程图.png

    相关文章

      网友评论

        本文标题:【Android P】 JobScheduler服务源码解析(二

        本文链接:https://www.haomeiwen.com/subject/ahfzzftx.html