Android P JobScheduler服务源码解析 ——框架解析

Posted 风雨田

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.以上是关于Android P JobScheduler服务源码解析 ——框架解析的主要内容,如果未能解决你的问题,请参考以下文章

Android P JobScheduler服务源码解析—— 使用Job需要注意的点

Android P JobScheduler服务源码解析—— 使用Job需要注意的点

Android进阶——更节电的后台任务JobScheduler 机制使用详解

Android进阶——更节电的后台任务JobScheduler 机制使用详解

Android进阶——更节电的后台任务JobScheduler 机制使用详解

Android源码面试宝典之JobScheduler从使用到原理分析JobServiceJobInfo