美文网首页
JobScheduler源码分析

JobScheduler源码分析

作者: 董成鹏 | 来源:发表于2018-07-12 15:29 被阅读0次

    下面来分析一下JobSchedulerService的源码,看一下我们在自定义的JobService中的那些回调方法是怎么被调用的。
    该文章参考了袁辉辉达摩院的文章

    JobSchedulerService是在SystemServer启动的时候被启动的

    SystemServer.java

    private void startOtherServices() {
      mSystemServiceManager.startService(JobSchedulerService.class);
    }
    

    那我们接下来看一下JobSchedulerService启动之后都做了哪些工作

    JobSchedulerService.java

    JobSchedulerService {
        List<StateController> mControllers;
        final JobHandler mHandler;
        final JobSchedulerStub mJobSchedulerStub;
        final JobStore mJobs;
        ...
    
        public JobSchedulerService(Context context) {
            super(context);
    
            //添加了几个StateController
            //这几个Controller就是对应于我们前面提到的JobInfo.Builder中设置的那几个触发条件
            //我们暂且不关心
            mControllers = new ArrayList<StateController>();
            mControllers.add(ConnectivityController.get(this));
            mControllers.add(TimeController.get(this));
            mControllers.add(IdleController.get(this));
            mControllers.add(BatteryController.get(this));
            mControllers.add(AppIdleController.get(this));
    
            //一个Handler在主线程分发消息,也不是很关心
            mHandler = new JobHandler(context.getMainLooper());
            //这是一个Binder的Stub端,具体的调度工作应该在这里执行,
            //可以稍后看一下
            mJobSchedulerStub = new JobSchedulerStub();
            //这个JobStore是什么?从名字上来看可能是对我们的Job进行存储一个内存对象
            mJobs = JobStore.initAndGet(this);
        }
    
        public void onStart() {
            publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
        }
    }
    

    既然感觉JobStore是对我们的Job进行存储的,那么我们先看一下使用到的这个JobStore.initAndGet()方法。

    先看一下这个JobStore类的注释,简单的翻译成中文

    保存一系列正在被JobScheduler tracking的Job, 这些job根据他们的引用来进行区别,所以这个类中所有方法都不应该对这些Job进行拷贝。
    所有对该类的操作都必须获得该类的锁,该类有两个方法重要的Runnable,一个是WriteJobsMapToDiskRunnable(用于持久化这些Job),一个是ReadJobMapsFromDiskRunnable(用于反序列化这些Job)

    那么我们看他的initAndGet方法,这个方法是在JobSchedulerService启动的时候会调用的方法。

    static JobStore initAndGet(JobSchedulerService jobManagerService) {
        //看起来这个类也没有做什么操作
        //就是初始化了一个JobStore的单例
        //并且还保存了JobSchedulerService的实例
        //看起来这个类也应该对JobScheduler的调度产生影响
        synchronized (sSingletonLock) {
            if (sSingleton == null) {
                //[见小节2.6]
                sSingleton = new JobStore(jobManagerService.getContext(),
                        Environment.getDataDirectory());
            }
            return sSingleton;
        }
    }
    

    接下来看一下JobStore的构造方法都做了哪些事情

        private JobStore(Context context, File dataDir) {
            mContext = context;
            mDirtyOperations = 0;
    
            File systemDir = new File(dataDir, "system");
            File jobDir = new File(systemDir, "job");
            jobDir.mkdirs();
            //可以看出JobScheduler会把Job的信息存储到data/system/job/jobs.xml中
            mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
            mJobSet = new ArraySet<JobStatus>();
            //这是一个很重要的方法
            readJobMapFromDisk(mJobSet);
        }
    

    再看readJobMapFromDisk这个方法是怎么从磁盘读取Job信息的

    readJobMapFromDisk最终会调用到readJobMapImpl这个方法,在解析这个xml文件之前,我们先来看一下这个jobs.xml的文件结构

    <job-info version="0">
    <job jobid="101" package="con.chico.dong.test" 
      class="com.chico.dong.test.TimingJobService" 
      sourcePackageName="com.chico.dong.test" 
      sourceUserId="0"
      uid="10090" 
      priority="0"
      flags="0">
      <constraints connectivity="true"/>
      <periodic period="86400000" flex="86400000" deadline="1531466690997" delay="1531380290997"/>
      <extras/>
    </job>
    </job-info>
    

    可以看出这个xml中主要记录了每一个Job的jobid, JobService的名字,包名,以及触发该Job的一些条件信息

    接下来再看如何解析这个jobs.xml

    private List<JobStatus> readJobMapImpl(FileInputStream fis)
             throws XmlPullParserException, IOException {
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(fis, StandardCharsets.UTF_8.name());
         ...
    
         String tagName = parser.getName();
         if ("job-info".equals(tagName)) {
             final List<JobStatus> jobs = new ArrayList<JobStatus>();
             ...
             eventType = parser.next();
             do {
                 //解析每一个job的信息
                 if (eventType == XmlPullParser.START_TAG) {
                     tagName = parser.getName();
                     if ("job".equals(tagName)) {
                        //从这些job中找出来persisted job,因为只有persisted job才需要在开机的时候重新schedule
                         JobStatus persistedJob = restoreJobFromXml(parser);
                         if (persistedJob != null) {
                             jobs.add(persistedJob);
                         }
                     }
                 }
                 eventType = parser.next();
             } while (eventType != XmlPullParser.END_DOCUMENT);
             return jobs;
         }
         return null;
     }
    

    主要方法是restorJobFromXml,看一下他的实现

    private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
        JobInfo.Builder jobBuilder;
        int uid;
        jobBuilder = buildBuilderFromXml(parser);
        jobBuilder.setPersisted(true);
        uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
        ...
    
        buildConstraintsFromXml(jobBuilder, parser);
        //读取该job的delay和deadline,相对于当前的开机时间
        Pair<Long, Long> elapsedRuntimes = buildExecutionTimesFromXml(parser);
        ...
        //从JobStatus的构造方法可以看出,JobStatus包含了JobInfo信息,还包含了执行情况的信息
        return new JobStatus(jobBuilder.build(), uid,
                    elapsedRuntimes.first, elapsedRuntimes.second);
    }
    

    从上面的代码逻辑来看,就是从xml中读取job的信息,然后利用这些信息创建JobStatus, JobStatus对象包含了JobInfo信息,还有该Job的delay,deadline信息,用于schedule.

    启动过程的其余部分就不用太关心了,我们只要知道JobSchedulerService启动过程的时候会从data/system/job/jobs.xml读取信息,每一个Job信息生成一个JobStatus对象,JobStatus对象包含了JobInfo信息,还包含了上一次被调度时候的delay和deadline信息。

    接下来重点就是看一下就是JobSchedulerService的调度过程了。先了解一个概念就是JobSchedulerService会维护三个列表,一个列表包含了所有的Job,一个列表包含了到达触发条件可以执行的Job(这个列表叫做penddingJobs),一个列表包含了正在执行的Job。
    JobSchedulerService进行调度的时候,会用到JobSchedulerContext这个类,该类代表了一个正在执行中的Job,或者说一个正在执行中的Job的执行环境。JobSchedulerService在初始化的时候会根据内存情况创建1个或者3个这个类的实例。每一个正在执行的job都会和这个类关联,当一个job执行完成或者取消执行时,会清空这个类中的信息,JobSchedulerService会一直保存这个类的实例进行复用.

    为了不看的头晕眼花,先明确几个概念。

    我们在使用JobScheduler的时候,需要自定义一个JobService,看一下这个JobService的源码

    public abstract class JobService extends Service {
      private JobServiceEngine mEngine;
      pubic final IBinder onBind(Intent intent){
        return mEngin.getBinder();
      }
      public abstract boolean onStartJob(JobParameters params);
      public abstract boolean onStopJob(JobParameters params);
      public final void jobFinished(JobParameters params, boolean needRescheduled){
        mEngine.jobFinished(params, needRescheduled);
      }
    }
    

    从上面的代码写法可以看出来,JobService包含了一个JobServiceEngine,这个JobServiceEngine拥有JobSchedulerService的binder,所以可以看做JobService可以做为服务端和JobSchedulerService进行通信

    在看我们获取JobScheduler的方法,

    mJobScheduler = (JobScheduler)mContext.getSystemService(JOB_SCHEDULER_SERVICE);
    

    这个最终会返回一个JobSchedulerImpl对象,该对象包含了一个JobSchedulerService的binder,用于和JobSchedulerService进行binder通信。

    所以现在在我们的App中有两个binder,一个是JobSerive,这个和JobSchedulerService进行通信的时候是做为服务端存在的,JobSchedulerService会做为client端调用我们的onStartJobonStopJob方法。
    另一个是JobScheduler或者说是JobSchedulerImpl,这个类做为client端接收JobSchedulerService的调度。

    那么我们再来看JobSchedulerschedule方法,最终会调用的JobSchedulerServiceschdeuler方法

    JobSchedulerService.schedule

        JobStatus jobStatus = new JobStatus(job, uId);
        //当有相同jodid的任务在执行的时候先取消该任务
        cancelJob(uId, job.getId());
        //追踪该任务的状态
        startTrackingJob(jobStatus);
        //在system_server的主线程中发送消息
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
        return JobScheduler.RESULT_SUCCESS;
    

    我们分别来看一下在schedule的时候做的操作,首先calcelJob

        JobStatus toCancel;
        synchronized (mJobs) {
            //根据uid和jobId找到对象的JobStatus
            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
        }
        if (toCancel != null) {
            //取消该Job的执行
            cancelJobImpl(toCancel);
        }
    

    进入calcelJobImpl

    private void cancelJobImpl(JobStatus cancelled) {
        //这个先不管,等看startTrackingJob的时候在看
        stopTrackingJob(cancelled);
        synchronized (mJobs) {
            //如果要calcel的job正在等待被执行,从等待队列中移除
            mPendingJobs.remove(cancelled);
            //这个方法应该是真正去cancel一个job,从而回调到我们的onStopJob方法
            stopJobOnServiceContextLocked(cancelled);
        }
    }
    

    进入stopJobOnServiceContextLocked()

    private boolean stopJobOnServiceContextLocked(JobStatus job) {
        for (int i=0; i<mActiveServices.size(); i++) {
            JobServiceContext jsc = mActiveServices.get(i);
            final JobStatus executing = jsc.getRunningJob();
            //注意这里的判断条件,只有该job正在执行的时候才会继续往下走
            //还记得上一篇我们说怎么判断一个job正在执行吗?
            //就是那个JobService的onStartJob返回true
            if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
                jsc.cancelExecutingJob();
                return true;
            }
        }
        return false;
    }
    

    最终会调用到JobSchedulerContextdoCancelLocked方法

    void doCancelLocked(int arg1, String reason){
      //如果cancel的时候任何已经结束了,比如调用了jobFinished方法
      //则什么都不做
      if(mVerb == VERB_FINISH){
        return;
      }
      handleCancelLocked();
    }
    

    这个handleCancelLocked方法最终会通过binder调用到我们app的onStopJob方法,这就不详细分析了。

    我们从上面的cancel过程可以看出,JobSchedulerService在cancel一个Job的大体思路是:

    1. 将Job从PendingJobs中移除,这个PendingJob包含了达到触发条件但还没有执行的Job
      2.如果在cancel的时候该Job正在被执行,则最终会调用到我们App的onStopJob方法。如果已经执行完了,则不会调用onStopJob方法。
      等等这只是一部分工作,我们看到在cancel的时候还会调用stopTrackingJob方法,我们还需要看一下startTrackingJobstopTrackingJob都做了哪些工作。

    JobSchedulerService.startTrackingJob

    private void startTrackingJob(JobStatus jobStatus) {
        boolean update;
        boolean rocking;
        synchronized (mJobs) {
            //mJobs保存了所有的job
            update = mJobs.add(jobStatus);
            rocking = mReadyToRock;
        }
        if (rocking) {
            for (int i=0; i<mControllers.size(); i++) {
                //使用各个控制器来监听该job设置的触发信息
                StateController controller = mControllers.get(i);
                if (update) {
                    controller.maybeStopTrackingJob(jobStatus);
                }
                controller.maybeStartTrackingJob(jobStatus);
            }
        }
    }
    

    同理,StopTrackingJob的工作如下,

    private boolean stopTrackingJob(JobStatus jobStatus) {
        boolean removed;
        boolean rocking;
        synchronized (mJobs) {
            //从mJobs中移除该Job
            removed = mJobs.remove(jobStatus);
            rocking = mReadyToRock;
        }
        if (removed && rocking) {
            for (int i=0; i<mControllers.size(); i++) {
                StateController controller = mControllers.get(i);
                //从各个控制器中移除对该Job触发信息的监听
                controller.maybeStopTrackingJob(jobStatus);
            }
        }
        return removed;
    }
    

    也就是说在cancel一个Job的时候,会将其从mJobs中移除,从pendingJobs中移除,然后如果该job还正在执行,则会调用该App的onStopJob方法,这就是cancel一个Job的流程。

    我们看到在schedule一个Job的时候,最后会发送一个MSG_CHECK_JOB,我们看一下JobSchedulerService收到该消息的时候是怎么对我们的Job进行调度的。

    JobSchedulerService$JobHandler

    private class JobHandler extends Handler {
        public void handleMessage(Message message) {
            synchronized (mJobs) {
                if (!mReadyToRock) {
                    return;
                }
            }
            switch (message.what) {
                case MSG_JOB_EXPIRED: ...                    
                    break;
                case MSG_CHECK_JOB:
                    synchronized (mJobs) {
                        maybeQueueReadyJobsForExecutionLockedH();
                    }
                    break;
            }
            maybeRunPendingJobsH();
            removeMessages(MSG_CHECK_JOB);
        }
    }
    

    从上面代码可以看出,在处理MSG_CHECK_JOB这个消息的时候,会调用到两个方法maybeQueueReadyJobsForExecutionLockedmaybeRunPenddingJobs

    从名字上来看,第一个方法的作用是判断该Job是否满足触发条件,如果满足触发条件,则把该Job放到pendingJobs

    第二个方法判断pendingJobs中的job是否可以执行
    主要看一下第二个方法

    private void maybeRunPendingJobsH() {
        synchronized (mJobs) {
            if (mDeviceIdleMode) {
                return;
            }
            Iterator<JobStatus> it = mPendingJobs.iterator();
            while (it.hasNext()) {
                JobStatus nextPending = it.next();
                JobServiceContext availableContext = null;
                for (int i=0; i<mActiveServices.size(); i++) {
                    JobServiceContext jsc = mActiveServices.get(i);
                    final JobStatus running = jsc.getRunningJob();
                    if (running != null && running.matches(nextPending.getUid(),
                            nextPending.getJobId())) {
                        availableContext = null;
                        break;
                    }
                    if (jsc.isAvailable()) {
                        availableContext = jsc;
                    }
                }
                if (availableContext != null) {
                    //看样子这个方法是真正执行一个Job的方法
                    if (!availableContext.executeRunnableJob(nextPending)) {
                        mJobs.remove(nextPending);
                    }
                    it.remove();
                }
            }
        }
    }
    

    看一下executeRunnableJob这个方法,该方法真正执行一个Job

    boolean executeRunnableJob(JobStatus job) {
        synchronized (mLock) {
            if (!mAvailable) {
                return false;
            }
    
            mRunningJob = job;
            final boolean isDeadlineExpired =
                    job.hasDeadlineConstraint() &&
                            (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
            mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired);
            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
    
            mVerb = VERB_BINDING;
            scheduleOpTimeOut();
            final Intent intent = new Intent().setComponent(job.getServiceComponent());
            //还记得我们在使用JobScheduler的时候必须要自定义一个JobService吗
           //该方法会bind到我们自定义的JobService
           //在这里JobSchedulerService做为client端。
            boolean binding = mContext.bindServiceAsUser(intent, this,
                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
                    new UserHandle(job.getUserId()));
            if (!binding) {
                mRunningJob = null;
                mParams = null;
                mExecutionStartTimeElapsed = 0L;
                mVerb = VERB_FINISHED;
                removeOpTimeOut();
                return false;
            }
            ...
            mAvailable = false;
            return true;
        }
    }
    

    既然是采用bind的方式启动一个service,那说明JobSchedulerService中肯定有onServiceConnected方法

    public void onServiceConnected(ComponentName name, IBinder service) {
        JobStatus runningJob;
        synchronized (mLock) {
            runningJob = mRunningJob;
        }
        if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
            mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
            return;
        }
        //系统最终会通过这个JobService的代理,来通知JobService执行onStartJob方法
        this.service = IJobService.Stub.asInterface(service);
    
        mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
    }
    

    这样,JobScheduler的整个代码过程就都搞明白了。

    相关文章

      网友评论

          本文标题:JobScheduler源码分析

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