JobScheduler源码分析

2018-07-12  本文已影响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的整个代码过程就都搞明白了。

上一篇下一篇

猜你喜欢

热点阅读