下面來分析一下JobSchedulerService的源碼露该,看一下我們在自定義的JobService
中的那些回調(diào)方法是怎么被調(diào)用的犹撒。
該文章參考了袁輝輝達摩院的文章
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中設置的那幾個觸發(fā)條件
//我們暫且不關心
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在主線程分發(fā)消息师脂,也不是很關心
mHandler = new JobHandler(context.getMainLooper());
//這是一個Binder的Stub端织阳,具體的調(diào)度工作應該在這里執(zhí)行站削,
//可以稍后看一下
mJobSchedulerStub = new JobSchedulerStub();
//這個JobStore是什么些侍?從名字上來看可能是對我們的Job進行存儲一個內(nèi)存對象
mJobs = JobStore.initAndGet(this);
}
public void onStart() {
publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
}
}
既然感覺JobStore
是對我們的Job進行存儲的攘蔽,那么我們先看一下使用到的這個JobStore.initAndGet()
方法龙屉。
先看一下這個JobStore
類的注釋,簡單的翻譯成中文
保存一系列正在被JobScheduler tracking的Job, 這些job根據(jù)他們的引用來進行區(qū)別满俗,所以這個類中所有方法都不應該對這些Job進行拷貝转捕。
所有對該類的操作都必須獲得該類的鎖,該類有兩個方法重要的Runnable,一個是WriteJobsMapToDiskRunnable(用于持久化這些Job),一個是ReadJobMapsFromDiskRunnable(用于反序列化這些Job)
那么我們看他的initAndGet
方法唆垃,這個方法是在JobSchedulerService
啟動的時候會調(diào)用的方法五芝。
static JobStore initAndGet(JobSchedulerService jobManagerService) {
//看起來這個類也沒有做什么操作
//就是初始化了一個JobStore的單例
//并且還保存了JobSchedulerService的實例
//看起來這個類也應該對JobScheduler的調(diào)度產(chǎn)生影響
synchronized (sSingletonLock) {
if (sSingleton == null) {
//[見小節(jié)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
最終會調(diào)用到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的名字辕万,包名枢步,以及觸發(fā)該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
渐尿,看一下他的實現(xiàn)
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信息砖茸,還包含了執(zhí)行情況的信息
return new JobStatus(jobBuilder.build(), uid,
elapsedRuntimes.first, elapsedRuntimes.second);
}
從上面的代碼邏輯來看隘擎,就是從xml中讀取job的信息,然后利用這些信息創(chuàng)建JobStatus
, JobStatus
對象包含了JobInfo信息渔彰,還有該Job的delay,deadline信息嵌屎,用于schedule.
啟動過程的其余部分就不用太關心了,我們只要知道JobSchedulerService
啟動過程的時候會從data/system/job/jobs.xml
讀取信息恍涂,每一個Job信息生成一個JobStatus
對象宝惰,JobStatus
對象包含了JobInfo信息,還包含了上一次被調(diào)度時候的delay和deadline信息再沧。
接下來重點就是看一下就是JobSchedulerService
的調(diào)度過程了尼夺。先了解一個概念就是JobSchedulerService
會維護三個列表,一個列表包含了所有的Job,一個列表包含了到達觸發(fā)條件可以執(zhí)行的Job(這個列表叫做penddingJobs)淤堵,一個列表包含了正在執(zhí)行的Job寝衫。
在JobSchedulerService
進行調(diào)度的時候,會用到JobSchedulerContext
這個類拐邪,該類代表了一個正在執(zhí)行中的Job慰毅,或者說一個正在執(zhí)行中的Job的執(zhí)行環(huán)境。JobSchedulerService
在初始化的時候會根據(jù)內(nèi)存情況創(chuàng)建1個或者3個這個類的實例扎阶。每一個正在執(zhí)行的job都會和這個類關聯(lián)汹胃,當一個job執(zhí)行完成或者取消執(zhí)行時,會清空這個類中的信息东臀,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通信。
所以現(xiàn)在在我們的App中有兩個binder枝冀,一個是JobSerive,這個和JobSchedulerService
進行通信的時候是做為服務端存在的耘子,JobSchedulerService
會做為client端調(diào)用我們的onStartJob
和onStopJob
方法果漾。
另一個是JobScheduler
或者說是JobSchedulerImpl
,這個類做為client端接收JobSchedulerService
的調(diào)度谷誓。
那么我們再來看JobScheduler
的schedule
方法绒障,最終會調(diào)用的JobSchedulerService
的schdeuler
方法
JobSchedulerService.schedule
JobStatus jobStatus = new JobStatus(job, uId);
//當有相同jodid的任務在執(zhí)行的時候先取消該任務
cancelJob(uId, job.getId());
//追蹤該任務的狀態(tài)
startTrackingJob(jobStatus);
//在system_server的主線程中發(fā)送消息
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
return JobScheduler.RESULT_SUCCESS;
我們分別來看一下在schedule
的時候做的操作捍歪,首先calcelJob
JobStatus toCancel;
synchronized (mJobs) {
//根據(jù)uid和jobId找到對象的JobStatus
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
}
if (toCancel != null) {
//取消該Job的執(zhí)行
cancelJobImpl(toCancel);
}
進入calcelJobImpl
private void cancelJobImpl(JobStatus cancelled) {
//這個先不管糙臼,等看startTrackingJob的時候在看
stopTrackingJob(cancelled);
synchronized (mJobs) {
//如果要calcel的job正在等待被執(zhí)行变逃,從等待隊列中移除
mPendingJobs.remove(cancelled);
//這個方法應該是真正去cancel一個job,從而回調(diào)到我們的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正在執(zhí)行的時候才會繼續(xù)往下走
//還記得上一篇我們說怎么判斷一個job正在執(zhí)行嗎粟矿?
//就是那個JobService的onStartJob返回true
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
jsc.cancelExecutingJob();
return true;
}
}
return false;
}
最終會調(diào)用到JobSchedulerContext
的doCancelLocked
方法
void doCancelLocked(int arg1, String reason){
//如果cancel的時候任何已經(jīng)結束了,比如調(diào)用了jobFinished方法
//則什么都不做
if(mVerb == VERB_FINISH){
return;
}
handleCancelLocked();
}
這個handleCancelLocked
方法最終會通過binder調(diào)用到我們app的onStopJob
方法掏秩,這就不詳細分析了哗讥。
我們從上面的cancel過程可以看出杆煞,JobSchedulerService
在cancel一個Job的大體思路是:
- 將Job從PendingJobs中移除腐泻,這個PendingJob包含了達到觸發(fā)條件但還沒有執(zhí)行的Job
2.如果在cancel的時候該Job正在被執(zhí)行,則最終會調(diào)用到我們App的onStopJob
方法构诚。如果已經(jīng)執(zhí)行完了铆惑,則不會調(diào)用onStopJob
方法纸兔。
等等這只是一部分工作麻车,我們看到在cancel的時候還會調(diào)用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++) {
//使用各個控制器來監(jiān)聽該job設置的觸發(fā)信息
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觸發(fā)信息的監(jiān)聽
controller.maybeStopTrackingJob(jobStatus);
}
}
return removed;
}
也就是說在cancel一個Job的時候冀值,會將其從mJobs中移除滑蚯,從pendingJobs中移除抵栈,然后如果該job還正在執(zhí)行古劲,則會調(diào)用該App的onStopJob方法,這就是cancel一個Job的流程疤剑。
我們看到在schedule
一個Job的時候,最后會發(fā)送一個MSG_CHECK_JOB
弯菊,我們看一下JobSchedulerService
收到該消息的時候是怎么對我們的Job進行調(diào)度的管钳。
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
這個消息的時候躏仇,會調(diào)用到兩個方法maybeQueueReadyJobsForExecutionLocked
和maybeRunPenddingJobs
從名字上來看,第一個方法的作用是判斷該Job是否滿足觸發(fā)條件糟描,如果滿足觸發(fā)條件船响,則把該Job放到pendingJobs
中
第二個方法判斷pendingJobs
中的job是否可以執(zhí)行
主要看一下第二個方法
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) {
//看樣子這個方法是真正執(zhí)行一個Job的方法
if (!availableContext.executeRunnableJob(nextPending)) {
mJobs.remove(nextPending);
}
it.remove();
}
}
}
}
看一下executeRunnableJob
這個方法见间,該方法真正執(zhí)行一個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;
}
//系統(tǒng)最終會通過這個JobService的代理回季,來通知JobService執(zhí)行onStartJob方法
this.service = IJobService.Stub.asInterface(service);
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
這樣茧跋,JobScheduler的整個代碼過程就都搞明白了哪亿。