上一篇文章:JobScheduler(二)——JobScheduler框架介绍及JSS的启动
现在来看看Job的添加流程,当应用程序通过JobScheduler.schedule(jobinfo)
添加一个Job,到它最终完成调度,这个过程是怎样的呢?现在就来看看这个过程。
首先来看其时序图:
受限于图片大小,以上时序图中对一些流程进行了省略,只画出了重要的步骤,不过所有的步骤,都会在下面的内容分析中说明。关于以上流程中涉及到的类,在上一篇文章中都进行了说明。下面开始对该图流程进行分解。
1.客户端创建Job
一个应用需要通过Job执行任务时,通过JobScheduler来完成:
JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
private ComponentName mServiceComponent;
//根据JobService创建一个ComponentName对象
mServiceComponent = new ComponentName(this, MyJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
builder.setMinimumLatency(1000);//设置延迟调度时间
builder.setOverrideDeadline(2000);//设置最大延迟截至时间
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//设置所需网络类型
builder.setRequiresDeviceIdle(true);//设置在DeviceIdle时执行Job
builder.setRequiresCharging(true);//设置在充电时执行Job
builder.setExtras(extras);//设置一个额外的附加项
mJobScheduler.schedule(builder.build());//调度Job
当JobScheduler.schedule(jobinfo)
时,实际调用的是JobSchedulerImpl中的schedule()
方法,该方法如下:
@Override
public int schedule(JobInfo job) {
try {
//调用进入JobSchedulerService
return mBinder.schedule(job);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
}
在该方法中,mBinder
就是JobSchedulerService.JobSchedulerStub类的实例,因为在注册JobSchedulerService服务时,在SystemServiceRegistry.java中:
registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
new StaticServiceFetcher<JobScheduler>() {
@Override
public JobScheduler createService() throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);
return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
}});
在这里,首先通过ServiceManager.getServiceOrThrow()
拿到JobSchedulerStub对象,然后作为实力化JobSchedulerImpl的参数传入。
接着下一步分析,接下来将进入JobSchedulerService.JobSchedulerStub的schedule()
中:
@Override
public int schedule(JobInfo job) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling job: " + job.toString());
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
//权限检查
enforceValidJobRequest(uid, job);
//如果通过JobInfo.Builder.setPersisted(true)设置了该Job在每次启动时执行,则对应应用需要RECEIVE_BOOT_COMPLETED权限
if (job.isPersisted()) {
if (!canPersistJobs(pid, uid)) {
throw new IllegalArgumentException("Error: requested job be persisted without"
+ " holding RECEIVE_BOOT_COMPLETED permission.");
}
}
//校验携带的标记是否合理
validateJobFlags(job, uid);
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
在以上方法中 ,只有一个功能:检查权限是否授予,检查标记是否正确,然后将调用进入JobSchedulerService中的scheduleAsPackage()
方法中,该方法如下:
2.Binder调用进入服务端(JobSchedulerService)
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
try {
//根据包名判断该应用是否允许启动,如果该应用禁用,则return RESULT_FAILURE
if (ActivityManager.getService().isAppStartModeDisabled(uId,
job.getService().getPackageName())) {
Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ " -- package not allowed to start");
return JobScheduler.RESULT_FAILURE;
}
} catch (RemoteException e) {
}
synchronized (mLock) {
//查找是否已存在该Job(先找uid,再找uid中的jobId)
final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
//初始化时work == null toCancel == null,故不会执行该if
if (work != null && toCancel != null) {
if (toCancel.getJob().equals(job)) {
toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
return JobScheduler.RESULT_SUCCESS;
}
}
//1.根据JoInfo创建一个JobStatus对象,JobStatus是job在内部的唯一标识
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
//2.对于没有时间限制,且当前应用处于active时,会对job添加一个INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION标记
jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
//每个应用最多只能有100个Job
if (ENFORCE_MAX_JOBS && packageName == null) {
if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
Slog.w(TAG, "Too many jobs for uid " + uId);
throw new IllegalStateException("Apps may not schedule more than "
+ MAX_JOBS_PER_APP + " distinct jobs");
}
}
//设置为准备状态,并申请URI权限
jobStatus.prepareLocked(ActivityManager.getService());
if (toCancel != null) {
cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
}
if (work != null) {
jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
}
//3.开始跟踪记录这个Job
startTrackingJobLocked(jobStatus, toCancel);
//4.如果Job此时已经满足其调度的所有条件,则加入执行列表中调度它
//否则什么也不做,等待控制器触发监听后改变其状态
if (isReadyToBeExecutedLocked(jobStatus)) {
// This is a new job, we can just immediately put it on the pending
// list and try to run it.
//mJobPackageTracker记录
mJobPackageTracker.notePending(jobStatus);
//5.添加到"将要运行job"队列中
addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
//6.运行队列中的Job
maybeRunPendingJobsLocked();
}
}
return JobScheduler.RESULT_SUCCESS;//返回给客户端
}
这个方法较长,并且其中部分逻辑不属于添加Job时的,所以下面对添加Job相关的逻辑分别进行总结,共有以下几个:
2.1.创建Job内部唯一标识——JobStatus
在应用中创建一个Job时,是以JobInfo为“单位”进行,而进入Framework层后,Job的唯一标识为JobStatus,每一个Job都对应一个唯一的JobStatus对象,JobStatus对象也正是在这个方法中实例化的,来看看如何实例化:
public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
int sourceUserId, String tag) {
//当前时间
final long elapsedNow = sElapsedRealtimeClock.millis();
//Job运行最早/最晚时间点
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
if (job.isPeriodic()) {
//判断是否是定期Job
//由setPeriodic()方法设置的
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
//Job运行最早时间点
earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
} else {
//如果不是定期Job,分别加setMinimumLatency()和setOverrideDeadline()设置的时间值
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = job.hasLateConstraint() ?
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
//客户端JobService完全限定名
String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
//获取该应用处于哪个待机桶中(standbyBucket,应用待机桶,将应用划分在5个桶中,不同的桶有不同的省电策略)
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
//获取心跳时间???
long currentHeartbeat = js != null
? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
: 0;
//实例化一个JobStatus对象
return new JobStatus(job, callingUid, resolveTargetSdkVersion(job), sourcePkg, sourceUserId,
standbyBucket, currentHeartbeat, tag