上一篇文章:JobScheduler(二)——JobScheduler框架介绍及JSS的启动
现在来看看Job的添加流程,当应用程序通过JobScheduler.schedule(jobinfo)
添加一个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()
方法中,该方法如下:
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相关的逻辑分别进行总结,共有以下几个:
在应用中创建一个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, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/*innerFlags=*/ 0);
}
在JobStatus构造方法中,初始化了一些全局变量,这里只说明一个全局变量:会将创建Job时在JobInfo设置的所有约束条件通过按位或运算,标记在全局变量requiredConstraints
上:
//从JobInfo中获取Job约束条件
int requiredConstraints = job.getConstraintFlags();
//获取网络限制
if (job.getRequiredNetwork() != null) {
requiredConstraints |= CONSTRAINT_CONNECTIVITY;
}
//获取最早执行时间限制
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_TIMING_DELAY;
}
requiredConstraints
表示一个Job所需的所有约束,在后续的逻辑中判断一个Job是否有约束时,将通过requiredConstraints
按位与运算进行,比如:
//判断是否有充电条件限制
public boolean hasChargingConstraint() {
return (requiredConstraints & CONSTRAINT_CHARGING) != 0;
}
此外,JobStatus中还有一个全局变量satisfiedConstraints
,这个表示该Job当前满足的约束。
是否对一个Job豁免,通过添加一个标记来判断:
/**
* Flag for {@link #mInternalFlags}: this job was scheduled when the app that owns the
* job service (not necessarily the caller) was in the foreground and the job has no
* time constraints, which makes it exempted from the battery saver job restriction.
* @hide
*/
//此标记表示没有时间限制的Job,当该job所属应用处于前台时,该Job将被调度执行
public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
如果确定对一个Job,那就意味着将不受省电管理的限制,当该Job所在应用处于前台时,就可以运行该Job。
判断是否豁免,通过JobStatus的maybeAddForegroundExemption()
进行,该方法如下:
public void maybeAddForegroundExemption(Predicate<Integer> uidForegroundChecker) {
//对于有时间限制的Job,不进行豁免
if (job.hasEarlyConstraint() || job.hasLateConstraint()) {
return;
}
//如果存在此标记,则说明已经进行了豁免,直接return
if ((mInternalFlags & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
return;
}
//如果对应应用处于活动状态,则添加相关标记
if (uidForegroundChecker.test(getSourceUid())) {
addInternalFlags(INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
}
}
豁免标记,主要是为了从省电管理机制中豁免该应用,这样在进入某些省电状态时,就不会限制该Job的运行了。如果在创建Job的时候,通过setMinimumLatency()
或setOverrideDeadline()
设置过限制Job调度的时间,那么是不会添加这个豁免标记的。
每向JobSchedulerService中添加一个Job,都将对该Job进行跟踪和记录,而这个工作,全部由StateController
的派生类负责,当各个StateController中监测到相关连的条件发生改变时,将触发回调,然后符合条件的Job将执行,这部分代码如下:
private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (!jobStatus.isPreparedLocked()) {
Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
}
//记录入队时间
jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
//添加到JobStore中,如果已存在则更新
final boolean update = mJobs.add(jobStatus);
if (mReadyToRock) {
//各个控制器中开始跟踪这个Job
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
if (update) {//更新,则先停止
controller.maybeStopTrackingJobLocked(jobStatus, null, true);
}
//开始跟踪
controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
}
}
}
更详细的StateController在接下来的文章中分析,这里我们以DeviceIdlerController中的实现为例:
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
//通过JobInfo.setImportantWhileForeground()设置的标记
if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
//添加到Doze豁免列表中
mAllowInIdleJobs.add(jobStatus);
}
//更新Job有关Doze限制的标记
updateTaskStateLocked(jobStatus);
}
private boolean updateTaskStateLocked(JobStatus task) {
//是否豁免在Doze模式限制
final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
&& (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
//Job所属应用是否处于Doze白名单中
final boolean whitelisted = isWhitelistedLocked(task);
//Job是否允许调度:Doze模式不处于IDLE状态或应用处于白名单中或应用处于前台且带有FLAG_IMPORTANT_WHILE_FOREGROUND
final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
//设置Doze模式约束条件
return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
}
在setDeviceNotDozingConstraintSatisfied()
方法中,将对参数enableTask和JobStatus全局变量satisfiedConstraints
进行按位运算,satisfiedConstraints
表示该Job当前满足的约束:
boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
dozeWhitelisted = whitelisted;
return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
}
boolean setConstraintSatisfied(int constraint, boolean state) {
boolean old = (satisfiedConstraints & constraint) != 0;
if (old == state) {
return false;
}
satisfiedConstraints = (satisfiedConstraints & ~constraint) | (state ? constraint : 0);
return true;
}
在以上方法中,将参数设置添加到satisfiedConstraints
上,或者从satisfiedConstraints
上清除,而在接下来的流程中,将通过satisfiedConstraints
判断是否满足指定约束。
当有新的Job时,是直接运行呢?还是暂不运行,当满足某一条件再运行呢?这是通过isReadyToBeExecutedLocked()
进行决策的:
private boolean isReadyToBeExecutedLocked(JobStatus job) {
//该Job是否已准备好运行
final boolean jobReady = job.isReady();
//未准备好,直接返回false
if (!jobReady) {
return false;
}
//检查在mJobs中是否已添加了当前job,这是在`startTrackingJobLocked()`中添加
final boolean jobExists = mJobs.containsJob(job);
//获取UserID
final int userId = job.getUserId();
//该User是否启动过
final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
//不存在Job或者当该Job所对应User不存在,返回false(该条件一般都不可能)
if (!jobExists || !userStarted) {
return false;
}
//当前job是否已经处于待执行Job列表中
final boolean jobPending = mPendingJobs.contains(job);
//当前job是否正在运行
final boolean jobActive = isCurrentlyActiveLocked(job);
//如果当前job处于待执行队列中或者正在运行,返回false
if (jobPending || jobActive) {
return false;
}
//appStandby不处于“赦免”状态 && 该job所属应用不处于活动状态&&未设置AppStandby豁免标记
if (!mInParole
&& !job.uidActive
&& !job.getJob().isExemptedFromAppStandby()) {
//获取job所属应用处于哪个待机桶中
final int bucket = job.getStandbyBucket();
//如果心跳数小于该应用所处待机桶规定的心跳数,则不会触发执行Job,将延迟这个Job
if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
//获取该Job所属应用上次的运行Job时的心跳数
final long appLastRan = heartbeatWhenJobsLastRun(job);
//如果Job所属app处于NEVER待机桶中,且心跳数未达到该应用待机桶规定心跳数,那就就不执行它了,给他设置一个推迟时间点,方便判断
if (bucket >= mConstants.STANDBY_BEATS.length
|| (mHeartbeat > appLastRan
&& mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) {
if (job.getWhenStandbyDeferred() == 0) {
//返回false,推迟该Job的执行,设置Job推迟时间点为当前
job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
}
return false;
} else {//说明当前心跳数已经大于等于待机桶中规定心跳数,将不会推迟Job
}
}
}
//验证此时Job所属应用包和service是否可用
final boolean componentPresent;
try {
//验证此时Job所属应用包和service是否可用
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
userId) != null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
return componentPresent;
}
在以上方法中,刚开始就调用了JobStatus.isReady()
方法,用于判断该Job是否已准备好运行,它由多个因素决定,比如设置有截至期限的Job(不管任何条件,只要到达截至期限后,必须运行)等等,这个判断就是通过isReady()
来进行的,而且这些判断,就是通过获取上面所说的satisfiedConstraints
实现,该方法如下:
public boolean isReady() {
//截至期限是否满足:非定期Job&&设置过overdeadline&&已经到达截至期限时间
final boolean deadlineSatisfied = (!job.isPeriodic() && hasDeadlineConstraint()
&& (satisfiedConstraints & CONSTRAINT_DEADLINE) != 0);
//Doze模式是否处于非IDLE状态:不处于IDLE状态&&没有设置FLAG_WILL_BE_FOREGROUND标记
final boolean notDozing = (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0
|| (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
//是否对后台网络没有限制进行了豁免(appStandby限制)
final boolean notRestrictedInBg =
(satisfiedConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0;
return (isConstraintsSatisfied() || deadlineSatisfied) && notDozing && notRestrictedInBg;
}
isConstraintsSatisfied()
方法用来判断当前约束是否已满足所需所有的约束:
public boolean isConstraintsSatisfied() {
//该Job所有的约束条件
final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST;
//该Job现在所满足的约束条件
int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
//相等,说明当前所满足约束已是所需所有约束
return (sat & req) == req;
}
一句话概括isReady()
方法:如果满足所有限制条件或已经到达截至期限,且不处于Doze状态且appStandby机制无后台限制且,说明它已准备好。在JobScheduler的使用时说道,使用setOverrideDeadline()
方法设置此Job的最大延迟调度,当到达截至时间后,无论任何条件,都会执行该Job,就是在这里判断的。
回到isReadyToBeExecutedLocked()
,如果该方法返回true,说明该Job已经准备好执行了,将开始执行Job,如果返回false,那么就没有其他事情要做了,只有等待它的一个控制器改变状态并适当地调度Job。假设该方法返回true,来接着分析执行流程。
当该Job已准备好执行时,将调用maybeRunPendingJobsLocked()
方法,开始执行Job,在调用该方法前,首先会将Job添加到待执行队列中:
addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
maybeRunPendingJobsLocked()
方法如下:
private void maybeRunPendingJobsLocked() {
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
}
//为待执行队列中的Job分配JobServiceContext并执行
assignJobsToContextsLocked();
//向DeviceIdleController报告Job状态
reportActiveLocked();
}
这个方法中依次调用了两个方法,其中reportActiveLocked()
负责向DeviceIdleController报告是否有Job在运行,在Job执行时、完成时、取消时,都会对其进行报告,这里就不详细说明,其核心逻辑如下:
void reportActiveLocked() {
//......
if (mReportedActive != active) {
mReportedActive = active;
if (mLocalDeviceIdleController != null) {
//向DeviceIdleController报告
mLocalDeviceIdleController.setJobsActive(active);
}
}
}
来看assignJobsToContextsLocked()
方法:
private void assignJobsToContextsLocked() {
int memLevel;
try {
//获取系统当前内存级别
memLevel = ActivityManager.getService().getMemoryTrimLevel();
} catch (RemoteException e) {
memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
}
//根据系统当前内存状况决定最大运行Job数
switch (memLevel) {
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
break;
case ProcessStats.ADJ_MEM_FACTOR_LOW:
mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
break;
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
break;
default:
mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
break;
}
//临时分配的用于和mActiveServices中的JSC实例相映射的JobStatus实例数组
//即contextIdToJobMap[i]在mActiveServices[i]上执行
JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
//表示"是否在此JobServiceContext上执行job"的布尔数组
boolean[] act = mTmpAssignAct;
//表示"要将其作业分配给上下文的首选UID"的数组
int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
//以上三个数组的大小均为MAX_JOB_CONTEXTS_COUNT=16
//正在运行的Job数量
int numActive = 0;
//Job所属应用处于前台的数量
int numForeground = 0;
//遍历获取所有的JobServiceContext对象
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
//获取数组中实例化的JobServiceContext对象
final JobServiceContext js = mActiveServices.get(i);
//获取在该JobServiceContext对象上正在执行的Job,没有则返回null
final JobStatus status = js.getRunningJobLocked();
//将contextIdToJobMap[i]和status进行映射,且如果有Job正在运行,则将numActive +1
if ((contextIdToJobMap[i] = status) != null) {
numActive++;
//如果优先级大于等于PRIORITY_TOP_APP,说明处于前台,numForeground +1
if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
numForeground++;
}
}
//全部置为false
act[i] = false;
//获取各个JobServiceContext的首选Uid,如果未设置,默认为-1
preferredUidForContext[i] = js.getPreferredUid();
}
//此时,每个JSC实例都和JobStatus进行了映射:contextIdToJobMap[i] -> status,而且status也许为null
//遍历待执行Job队列,为每个待执行Job分配JSC实例
for (int i=0; i<mPendingJobs.size(); i++) {
JobStatus nextPending = mPendingJobs.get(i);
//如果nextPending已经存在于contextIdToJobMap中,则说明该Job已经运行,处理下一个Job
int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
if (jobRunningContext != -1) {
continue;
}
//调整并设置Job优先级,各个Job默认优先级为`PRIORITY_DEFAULT`(0),当Job所属应用处于前台时,将会设置该uid的优先级为`PRIORITY_TOP_APP`
final int priority = evaluateJobPriorityLocked(nextPending);
nextPending.lastEvaluatedPriority = priority;
//找一个可用的JSC或着将优先级最低的JSC腾出来给这个Job
int minPriority = Integer.MAX_VALUE;
//优先级最小的JobScheduleContext id
int minPriorityContextId = -1;
for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
// 得到JobStatus
JobStatus job = contextIdToJobMap[j];
//得到首选执行Job所对应的Uid
int preferredUid = preferredUidForContext[j];
//说明contextIdToJobMap[j]为空
if (job == null) {
//如果存在未被占用的JSC,且没有设置首选uid,那么就是它了,就用它来执行这个Job,否则,找下一个吧
//(正在执行Job数量小于最大执行Job数量 || (应用处于前台&&当前前台Job数小于最大前台执行数)) &&
//(该Job所属应用为该JobServiceContext首选应用|| 该JobServiceContext首选应用为默认值-1)
if ((numActive < mMaxActiveJobs ||
(priority >= JobInfo.PRIORITY_TOP_APP &&
numForeground < mConstants.FG_JOB_COUNT)) &&
(preferredUid == nextPending.getUid() ||
preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
minPriorityContextId = j;
break;
}
//继续下一次循环
continue;
}
//如果队列中的job所属应用和contextIdToJobMap中的不同,直接进行下一次循环
if (job.getUid() != nextPending.getUid()) {
continue;
}
//如果job优先级大于队列中Job的优先级,直接进行下一次循环
if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
continue;
}
//如果能到这,说明16个JSC都没空,那么将nextPending的优先级设为最小优先级,从而得到优先级最小的JSC
if (minPriority > nextPending.lastEvaluatedPriority) {
minPriority = nextPending.lastEvaluatedPriority;
minPriorityContextId = j;
}
}
//找到优先级最低的JSC,将待执行的Job和它进行映射
if (minPriorityContextId != -1) {
contextIdToJobMap[minPriorityContextId] = nextPending;
act[minPriorityContextId] = true;//需要执行Job的JSC将可执行
numActive++;//正在执行Job数 +1
if (priority >= JobInfo.PRIORITY_TOP_APP) {
numForeground++;//前台执行Job数 +1
}
}
}
mJobPackageTracker.noteConcurrency(numActive, numForeground);
for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
boolean preservePreferredUid = false;
if (act[i]) {
//正在运行的Job
JobStatus js = mActiveServices.get(i).getRunningJobLocked();
//如果存在正在运行的Job
if (js != null) {
//如果此JSC上有正在运行的Job,则先进行停止,但将这个Job所属应用继续保留为首选应用
mActiveServices.get(i).preemptExecutingJobLocked();
preservePreferredUid = true;
//如果不存在正在运行的Job,则从contextIdToJobMap中取出为该JSC事先分配的JobStatus对象
} else {
final JobStatus pendingJob = contextIdToJobMap[i];
//通过各个控制器,做好运行Job的准备,实际上只有ContentObserverController对他进行了实现
for (int ic=0; ic<mControllers.size(); ic++) {
mControllers.get(ic).prepareForExecutionLocked(pendingJob);
}
//开始执行Job,执行成功返回true
if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
Slog.d(TAG, "Error executing " + pendingJob);
}
//从待执行Job列表中移除该Job
if (mPendingJobs.remove(pendingJob)) {
mJobPackageTracker.noteNonpending(pendingJob);
}
}
}
//清除首选Uid
if (!preservePreferredUid) {
mActiveServices.get(i).clearPreferredUid();
}
}
}
在以上方法中,将会为待执行队列中的Job分配JobServiceContext对象(以下简称JSC),我们之前说过,JJobServiceContext处理应用中JobService的绑定和Job的生命周期,且一个JobServiceContext对象只能执行一个Job。正因如此,在上述方法中,将从JSC列表中,查找剩余的JSC实例,如果JSC实例都已经分配了Job了,那么将根据Job的优先级,最小优先级的Job只能倒霉了,它将被优先级高的Job替换。而且如果它正在运行,则将被停止。或许是出于不好意思,将正在运行的低优先级Job取消后,会将其所属应用作为首选应用,仿佛在说:“下次肯定轮到你”,然而还是取决于优先级大小。
当分配成功后,将调用JSC的executeRunnableJob(pendingJob)
方法,开始启动Job。
我们说过,Job的执行,是在对应应用的进程中执行,为何在对应应用进程执行,是因为在JSC中,通过bindService
的方式启动了服务,而且JSC本身就是一个SeviceConnection,所以当绑定Service成功后,将回调onServiceConnected()
,解除绑定后将回调onServiceDisconnected()
方法,在Service中,绑定后将出发生命周期方法中的onBind()
方法…先来看启动Job的executeRunnableJob(pendingJob)
:
boolean executeRunnableJob(JobStatus job) {
synchronized (mLock) {
//首先检查该JSC实例是否可用,当该JSC上已经在运行Job时mAvailable为false
if (!mAvailable) {
return false;
}
//清空JSC实例的首选应用id
mPreferredUid = NO_PREFERRED_UID;
//表示正在此JSC实例上运行的Job
mRunningJob = job;
//实例化一个回调接口
mRunningCallback = new JobCallback();
//是否到达截至期限
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&//是否设置了截至期限
(job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());//截至期限时间点
//???
Uri[] triggeredUris = null;
if (job.changedUris != null) {
triggeredUris = new Uri[job.changedUris.size()];
job.changedUris.toArray(triggeredUris);
}
//???
String[] triggeredAuthorities = null;
if (job.changedAuthorities != null) {
triggeredAuthorities = new String[job.changedAuthorities.size()];
job.changedAuthorities.toArray(triggeredAuthorities);
}
final JobInfo ji = job.getJob();
//实例化JobParameters,将作为回调方法参数
mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
//设置开始执行Job时间
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
//获取由appStandby导致Job推迟的时间
final long whenDeferred = job.getWhenStandbyDeferred();
if (whenDeferred > 0) {
//如果存在推迟时间,说明该job被appStandy推迟过,得到推迟的具体时长,然后打个log就完事了...
final long deferral = mExecutionStartTimeElapsed - whenDeferred;
if (DEBUG_STANDBY) {
}
}
//???
job.clearPersistedUtcTimes();
//设置Job和客户端交互状态为绑定状态,有绑定-启动-执行-停止-完成五个,VERB_BINDING表示开始绑定
mVerb = VERB_BINDING;
// 当向客户端发送消息时,我们无法控制其执行,因此通过该方法,设置一个确定的时长,
// 如果当达到一定时间内没有收到回应,则取消该动作,继续其他流程
// 和它对应的是removeOpTimeOutLocked(),用来取消定时
scheduleOpTimeOutLocked();
//创建Intent
final Intent intent = new Intent().setComponent(job.getServiceComponent());
//绑定应用对应的Service并启动
boolean binding = mContext.bindServiceAsUser(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
new UserHandle(job.getUserId()));
//如果绑定失败
if (!binding) {
mRunningJob = null;//置空
mRunningCallback = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
mVerb = VERB_FINISHED;//Job交互状态设置为完成
removeOpTimeOutLocked();//移除定时
return false;
}
//绑定成功
mJobPackageTracker.noteActive(job);
try {
//通知BatteryStatsService Job运行,以用于统计耗电
mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
final String jobPackage = job.getSourcePackageName();
final int jobUserId = job.getSourceUserId();
UsageStatsManagerInternal usageStats =
LocalServices.getService(UsageStatsManagerInternal.class);
//通知USageStatsService最后Job的运行时间
usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
JobSchedulerInternal jobScheduler =
LocalServices.getService(JobSchedulerInternal.class);
//通知JobSchedulerService Job开始运行
jobScheduler.noteJobStart(jobPackage, jobUserId);
mAvailable = false;//表示该JSC实例将不可用
mStoppedReason = null;
mStoppedTime = 0;
return true;
}
}
在以上方法中,绑定Service之前,首先要判断当前JSC对象是否已经有Job在运行,如果是直接返回false,否则将开始绑定Service,并根据绑定结果,执行相应的操作。
下面就分别来看一下,通过Binder绑定Service后客户端和服务端的回调,首先来看客户端中第一个回调方法、也是属于生命周期方法的onBind()
,在JobService中:
onBind()
回调 public final IBinder onBind(Intent intent) {
if (mEngine == null) {
//实例化一个JobServiceEngine匿名对象
mEngine = new JobServiceEngine(this) {
@Override
public boolean onStartJob(JobParameters params) {
return JobService.this.onStartJob(params);
}
@Override
public boolean onStopJob(JobParameters params) {
return JobService.this.onStopJob(params);
}
};
}
//返回IJobService.Stub类实例
return mEngine.getBinder();
}
在JobService的onBind()
方法中,将创建一个JobServiceEngine类的匿名对象,然后通过getBinder()
返回一个IBinder对象,再确切一点,这个IBinder是JobServiceEngine.JobInterface
对象。因为在JobServiceEngine的构造方法中:
public JobServiceEngine(Service service) {
mBinder = new JobInterface(this);
mHandler = new JobHandler(service.getMainLooper());
}
同时可以看到,当调用JobServiceEngine的onStartJob()
和onStopJob()
方法时,将会调用JobService对应的onStartJob()
和onStopJob()
方法,从而进入应用线程中执行。那么接下来,只需知道JobServiceEngine的这两个方法什么时候会执行,那么JobService中将开始执行这个Job。
onServiceConnected()
回调分析完客户端回调后,再来看服务端绑定成功后回调的onServiceConnected()
方法,在JobServiceContext中:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
JobStatus runningJob;
synchronized (mLock) {
runningJob = mRunningJob;
//做个小判断
if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
closeAndCleanupJobLocked(true /* needsReschedule */,
"connected for different component");
return;
}
//IJobService实例
this.service = IJobService.Stub.asInterface(service);
//申请WakLock,系统将被唤醒
final PowerManager pm =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
runningJob.getTag());
//为该Wakelock设置工作源,方面统计应用耗电(在Service代表应用程序执行工作的情况下很有用)
wl.setWorkSource(deriveWorkSource(runningJob));
//非计数锁
wl.setReferenceCounted(false);
wl.acquire();//申请WakeLock
//如果之前的WakLock存在,则释放
if (mWakeLock != null) {
mWakeLock.release();
}
mWakeLock = wl;
//启动服务
doServiceBoundLocked();
}
}
在这个方法中,参数中的IBinder对象,则是客户端onBind()
方法的返回值,在分析客户端回调时我们知道,这个返回值为JobServiceEngine.JobInterface实例,在这个方法中将这个实例赋值给了全部变量service
。然后申请了一个WakeLock,使得系统唤醒或保持唤醒,接下来调用doServiceBoundLocked()
去执行Job了。
onstartJob()
现在我们接着上一步分析,来看看doServiceBoundLocked()
方法:
@GuardedBy("mLock")
void doServiceBoundLocked() {
//取消定时
removeOpTimeOutLocked();
//处理服务的绑定
handleServiceBoundLocked();
}
其中removeOpTimeOutLocked()
只是取消防止耗时的定时,定时是为了提示系统,当超过这个时间后,将取消和该Job所对应JobService的交互:
private void removeOpTimeOutLocked() {
mCallbackHandler.removeMessages(MSG_TIMEOUT);
}
我们将注意力放在handleServiceBoundLocked()
中:
@GuardedBy("mLock")
private void handleServiceBoundLocked() {
//Job交互状态不处于绑定状态时,取消了
if (mVerb != VERB_BINDING) {
closeAndCleanupJobLocked(false /* reschedule */, "started job not pending");
return;
}
//如果调用cancel()等方法,取消该Job
if (mCancelled) {
closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind");
return;
}
try {
//Job交互状态由绑定状态变为启动状态
mVerb = VERB_STARTING;
//设置一个定时,超过时间将取消
scheduleOpTimeOutLocked();
//调用JobServiceEngine.JobInterface的startJob()
service.startJob(mParams);
} catch (Exception e) {
}
}
在这个方法中,经过两个判断后,将Job交互状态由VERB_BINDING变为VERB_STARTING,意思是启动了,然后设置一个定时,最后调用JobServiceEngine.JobInterface
的startJob()
方法,我们来看看这个方法:
//frameworks/base/core/java/android/app/job/JobServiceEngine.java
@Override
public void startJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams);
m.sendToTarget();
}
}
在mHandler中:
@Override
public void handleMessage(Message msg) {
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
case MSG_EXECUTE_JOB:
try {
//调用JobServiceEngine的onStartJob(),并获取返回值
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
//将回调JSC中,确认启动信息,将根据workOngoing决定是否继续执行该Job
ackStartMessage(params, workOngoing);
} catch (Exception e) {
throw new RuntimeException(e);
}
break;
在handleMessage()
中,首先将调用JobServiceEngine的onStartJob()
,而在绑定服务的时候看到,JobServiceEngine的onStartJob()
方法中又调用了JobService的onStartJob()
方法,从而开始执行Job。
回到handleMessage()
中,当执行完成onStartJob()
后,开始执行ackStartMessage()
方法,并将onStartJob()
方法的返回值作为其参数。经过一系列的调用:ackStartMessage()
->JobServiceContext.acknowledgeStartMessage()
->doAcknowledgeStartMessage()
->doCallback()
->doCallbackLocked()
,最终是调用进入到了JobServiceContext的doCallbackLocked()
方法中,来看下这个方法:
onStartJob()
完毕后的收尾 @GuardedBy("mLock")
void doCallbackLocked(boolean reschedule, String reason) {
removeOpTimeOutLocked();//移除定时
if (mVerb == VERB_STARTING) {//Job交互状态为启动
handleStartedLocked(reschedule);
} else if (mVerb == VERB_EXECUTING ||
mVerb == VERB_STOPPING) {
handleFinishedLocked(reschedule, reason);
} else {
}
}
根据当前方法的执行,此时Job和客户端交互状态为VERB_STARTING
,因此将通过handleStartedLocked()
,切换Job的交互状态,并根据onStartJob()
的返回值决定是否停止该Job,该方法如下:
@GuardedBy("mLock")
private void handleStartedLocked(boolean workOngoing) {
switch (mVerb) {
case VERB_STARTING:
//Job交互状态由VERB_START变为VERB_EXECUTING
mVerb = VERB_EXECUTING;
//也就是说如果onStartJob()返回false,则将关闭Job和客户端的绑定
if (!workOngoing) {
handleFinishedLocked(false, "onStartJob returned false");
return;
}
if (mCancelled) {
// Cancelled *while* waiting for acknowledgeStartMessage from client.
handleCancelLocked(null);
return;
}
scheduleOpTimeOutLocked();//设置VERB_EXECUTING状态定时
break;
default:
return;
}
}
在以上方法中,首先将Job和客户端交互状态由启动转变成了执行,然后,如果onStartJob()
返回false,则将关闭Job和客户端的绑定,这将说明一个Job的调度已经完成,如果onStartJob()
返回true,那么此时不会立即关闭和客户端的绑定,而是设置一个VERB_EXECUTING状态的定时这个定时为600s,当到达时间后,进入STOPPING状态,并调用客户端onStopJob()
方法,最终会在8s后进入FINISHED状态。
如果onStartJob()
返回false,则将执行handleFinishedLocked()
,该方法如下:
@GuardedBy("mLock")
private void handleFinishedLocked(boolean reschedule, String reason) {
switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
//关闭当前JobServiceContext和JobService的连接
closeAndCleanupJobLocked(reschedule, reason);
break;
}
}
在这个方法中又调用了closeAndCleanupJobLocked()
方法,该方法如下:
@GuardedBy("mLock")
private void closeAndCleanupJobLocked(boolean reschedule, String reason) {
final JobStatus completedJob;
if (mVerb == VERB_FINISHED) {
return;
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
mRunningJob.getSourceUid(), mParams.getStopReason());
} catch (RemoteException e) {
// Whatever.
}
//释放WakeLock
if (mWakeLock != null) {
mWakeLock.release();
}
//解除该JobServiceContext实例和JobService的绑定
mContext.unbindService(JobServiceContext.this);
//置空对象
mWakeLock = null;
mRunningJob = null;
mRunningCallback = null;
mParams = null;
mVerb = VERB_FINISHED;//Job交互状态由
mCancelled = false;
service = null;
mAvailable = true;
removeOpTimeOutLocked();//移除定时
//回调
mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
}
在以上方法中,首先将释放WakeLock,接下来将通过unbindService()
解除该JSC实例和JobService的绑定,再接下来置空了一些对象,然后将Job交互状态由VERB_EXECUTE
变为VERB_FINISHED
,并移除前一个交互状态的定时,最后,将回调mCompletedListener.onJobCompletedLocked()
方法。而这个方法的实现,在JSS中:
@Override
public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
//是否需要重新执行?是:则根据原来JobStatus创建新的JobStatus实例
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus) : null;
//停止跟踪Job,并从JobStore中移除,移除成功返回true
if (!stopTrackingJobLocked(jobStatus, rescheduledJob, !jobStatus.getJob().isPeriodic())) {
mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
return;
}
//如果需要重新执行,则开始记录这个重新执行的新Job
if (rescheduledJob != null) {
try {
rescheduledJob.prepareLocked(ActivityManager.getService());
} catch (SecurityException e) {
}
startTrackingJobLocked(rescheduledJob, jobStatus);
//如果是定期Job,也需要重新执行,则开始记录这个重新执行的新Job
} else if (jobStatus.getJob().isPeriodic()) {
JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
try {
rescheduledPeriodic.prepareLocked(ActivityManager.getService());
} catch (SecurityException e) {
}
startTrackingJobLocked(rescheduledPeriodic, jobStatus);
}
//uriPerms.revoke(am)
jobStatus.unprepareLocked(ActivityManager.getService());
//向DeviceIdleController报告Job状态
reportActiveLocked();
//Handler中重新进行入队操作
mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
}
在这个回调方法中,会根据参数needsReschedule
决定是否重新执行Job,需要注意的是,当Job完成后,该值为false(handleFinishedLocked()中传入)。我们在前面说过,当客户端onStopJob()
方法返回ture时,将会重新执行该Job,正是这个参数的原因,当执行完onStopJob()
方法后,最终也会进入这里,这部分在后续文章中说明。
总而言之,此方法主要有三个工作,其细节就不再详细分析:
JobService.onStopJob()
返回true,则根据该Job重新创建一个新Job,并开始跟踪记录;queueReadyJobsForExecutionLocked()
重新整顿Job队列。如此一来,一个Job从创建到执行完毕的过程,就分析完毕了。从整个流程来看,JobSchedulerService
中,负责Job前期的准备和执行后的回调工作,而Job的执行过程控制,则在JobServiceContext中,Job和客户端JobService交互的五种状态,我们可以理解为一个Job的生命周期:
VERB_BINDING
:JobServiceContext和JoService开始绑定时的状态,该状态最长持续18s;VERB_STARTING
:JobServiceContext和JoService绑定成功,即将调用onStartJob()
开始启动时的状态,该状态最长持续8s;VERB_EXECUTING
:onStartJob()
执行完毕后的状态,如果该方法返回false,则该状态将结束,进入下一个状态,该状态最长持续600s;VERB_STOPPING
:当Job处于VERB_EXECUTING
时,如果通过cancel()
取消该Job时,将进入该状态,该状态下将回调客户端JobService的onStopJob()
方法,该状态最长持续8s;VERB_FINISHED
:整个Job执行完毕后的状态,该状态下将解除Service绑定、释放WakeLock。当Job处于VERB_EXECUTING
和VERB_STOPPING
状态时会进入该状态,该状态时说明已经Job执行完毕。而这些生命周期状态的限制时长,就是多次提到的定时:
private void scheduleOpTimeOutLocked() {
removeOpTimeOutLocked();
final long timeoutMillis;
switch (mVerb) {
case VERB_EXECUTING:
timeoutMillis = EXECUTING_TIMESLICE_MILLIS;//600s
break;
case VERB_BINDING:
timeoutMillis = OP_BIND_TIMEOUT_MILLIS;//18s
break;
default:
timeoutMillis = OP_TIMEOUT_MILLIS;//8s
break;
}
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
}
每成功执行一步,都会通过removeOpTimeOutLocked()
方法移除定时,并设置一个新的定时:
private void removeOpTimeOutLocked() {
mCallbackHandler.removeMessages(MSG_TIMEOUT);
}
当到达定时后,说明超时了,这时将通过handleOpTimeOutLocked()
方法处理定时:
@GuardedBy("mLock")
private void handleOpTimeoutLocked() {
switch (mVerb) {
case VERB_BINDING://绑定客户端JobService时超时
closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
break;
case VERB_STARTING://执行onStartJob()时超时
closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
break;
case VERB_STOPPING://停止后超时
closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
break;
case VERB_EXECUTING://执行Job时超时,典型的onStartJob()返回true时
mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
sendStopMessageLocked("timeout while executing");
break;
default:
closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
}
}
下一篇文章:JobScheduler(四)——Job约束条件的控制