JobScheduler源码分析
下面来分析一下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端调用我们的onStartJob
和onStopJob
方法。
另一个是JobScheduler
或者说是JobSchedulerImpl
,这个类做为client端接收JobSchedulerService
的调度。
那么我们再来看JobScheduler
的schedule
方法,最终会调用的JobSchedulerService
的schdeuler
方法
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;
}
最终会调用到JobSchedulerContext
的doCancelLocked
方法
void doCancelLocked(int arg1, String reason){
//如果cancel的时候任何已经结束了,比如调用了jobFinished方法
//则什么都不做
if(mVerb == VERB_FINISH){
return;
}
handleCancelLocked();
}
这个handleCancelLocked
方法最终会通过binder调用到我们app的onStopJob
方法,这就不详细分析了。
我们从上面的cancel过程可以看出,JobSchedulerService
在cancel一个Job的大体思路是:
- 将Job从PendingJobs中移除,这个PendingJob包含了达到触发条件但还没有执行的Job
2.如果在cancel的时候该Job正在被执行,则最终会调用到我们App的onStopJob
方法。如果已经执行完了,则不会调用onStopJob
方法。
等等这只是一部分工作,我们看到在cancel的时候还会调用stopTrackingJob
方法,我们还需要看一下startTrackingJob
和stopTrackingJob
都做了哪些工作。
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
这个消息的时候,会调用到两个方法maybeQueueReadyJobsForExecutionLocked
和maybeRunPenddingJobs
从名字上来看,第一个方法的作用是判断该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的整个代码过程就都搞明白了。