1、概述
在Android中Job源码讲解这篇文章中对Job的基本用法、如何添加Job到JobStore以及最终是如何执行到自定义的JobService中等做了详细的讲解,但是当Job所要求的条件满足之后是如何回调到JobSchedulerService这一过程却只是简单的叙述了一下。因此,该篇文章主要则针对各个Controller追踪器的相关源码进行讲解。
2、回顾
在讲解具体的Controller源码之前,首先来回顾一下应用层调用到JobSchedulerImpl之后,然后通过Binder的方式再调用到JobSchedulerService的大致流程。在JobSchedulerImpl中提供了三个方法供应用层设置Job到框架层中,分别是:schedule、enqueue和scheduleAsPackage,而这三个方法也仅仅只是通过Binder的方式调用到了JobSchedulerService中而已,并且最终都调用到了scheduleAsPackage函数中,在该函数中做了如下的事情:
(1)根据设置应用的uid以及jobId判断jobs列表中是否存在相同的Job,如果存在则返回并赋值给toCancel;
(2)如果work不为null(通过equeue的方式设置job)并且toCancel也不为null同时toCancel中存储的jobInfo和当前设置的JobInfo是同一个对象,则将work添加到toCancel对象中(pendingWork列表中),并计算当前Job下载和上传所需要的总的流量大小;同时如果当前Job没有时间延迟并且其所对应的应用处于交互状态,那么该Job就不会受到省电模式的影响(在根据应用待机群组判断应用Job是否允许执行中有用到);最后直接返回RESULT_SUCCESS;
(3)如果条件(2)不满足执行条件,则会根据构建的JobInfo、uid、packageName、UserId以及tag构建新的JobStatus对象,并且判断是否添加Job不受省电模式影响的flag;
(4)判断当前应用是否设置了100个Job,如果超过了100个则不允许再继续设置并抛出异常;
(5)设置标识表示该Job已经准备就绪只要条件满足就可以被执行了;
(6)如果toCancel不等于null,那么先Stop掉该Job并从Controller的追踪列表中移除相关Job;
(7)如果work不等于null,则将该work添加到新的JobStatus对象中;
(8)将JobStatus添加到JobStore和Controller追踪列表中;
(9)判断当前Job是否需要马上执行,如果需要则马上执行。
从上面的执行流程可以知道,一个应用中同一个JobId只能对应一个Job,如果JobId相同,前面设置的Job会被后续的Job所替代,并且如果Job正在被执行会被立即停止。
3、Controller讲解
在对Job从应用层设置到框架层的基本流程有所了解之后,现在开始讲解具体到Controller执行逻辑。在前面到文章中我们知道Job的执行就是当需要在后台执行满足特定条件后台任务的时候则可使用。而在Job的整个被执行流程中其所需要的特定条件则是通过具体的Controller进行控制的。在JobSchedulerService的构造方法中初始化了如下几个Controller对象,并将初始化的对象添加到了mControllers集合中:
(1)ConnectivityController:用于监听网络连接;
(2)TimeController:用于控制Job的延迟执行时间;
(3)IdleController:用来监听设备是否处于闲置状态(idle),有区别于Doze模式中的idle状态,和setRequiresDeviceIdle方法相关;该处的idle和系统中Doze模式相互独立(只是进入的条件部分相同而已,后续会讲到);
(4)BatteryController:用于监听设备电池信息状态变化;
(5)StorageController:用于监听设备内存状态变化;
(6)BackgroundJobsController:根据AppSandby判断应用是否允许后台运行job;
(7)ContentObserverController:用来检测ContentUri对Job的影响;
(8)DeviceIdleJobsController:用于监听设备是否进入了Doze模式,具体可查阅Android Doze状态源码分析
每个Controller代表着一种Job可被执行的特定条件。在阅读源码的过程中不难发现所有的Controller都继承了父类StateController,因此在讲解具体的Controllr之前首先看一下StateController的相关源码:
3.1 StateController
在抽象类StateController中并没有做任何事情,仅仅是定义了一个构造方法、四个抽象方法以及两个被实现的空方法而已,所有具体的实现都在子类当中,因此,下面对StateController相关的子类进行讲解。
3.2 ConnectivityController
该控制器用来监听系统网络的变化,当网络发生变化的时候会更新该控制器中所有被跟踪Job的网络状态。首先看一下该控制器中的构造方法做了什么事情。
3.2.1 构造方法
public ConnectivityController(JobSchedulerService service) {
super(service);
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
//用于监听系统网络变化
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
mConnManager.registerNetworkCallback(request, mNetworkCallback);
//用于监听某一个应用后台数据网络访问策略是否改变
mNetPolicyManager.registerListener(mNetPolicyListener);
}
构造方法中并没有做其它多余的处理,仅仅只是初始化了网络相关的manager并将网络变化监听器设置到manager中而已,当监听器监听到网络发生变化的时候最终会调用到函数updateTrackedJobs中,不过在讲解该方法之前还需要了解一下是如何将Job添加到该控制器的追踪列表又是如何从列表中移除的。
3.2.2 maybeStartTrackingJobLocked
该方法的主要目的是将Job添加到追踪列表中,具体源码如下:
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
//判断当前Job是否设置了需要网络连接或者需要的网络类型
if (jobStatus.hasConnectivityConstraint()) {
//判断当前网络是否满足条件,后续会进行讲解
updateConstraintsSatisfied(jobStatus);
//将当前Job添加到追踪队列中
mTrackedJobs.add(jobStatus);
//表示该Job已经成功添加到该控制器中
jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
}
}
在该方法中首先会判断当前Job是否设置需要网络对应于JobInfo中的setRequiredNetworkType和setRequiredNetwork两个方法,然后会判断当前网络是否满足当前Job的执行条件,紧接着将该Job添加到控制器的追踪队列中,最后设置当前Job已经被网络控制器所追踪也就是在对应的flag中添加TRACKING_CONNECTIVITY。
3.2.3 maybeStopTrackingJobLocked
用于在网络监听器中取消追踪对应的Job。源码如下:
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,boolean forUpdate) {
//判断是否已经从对应的flag中移除了TRACKING_CONNECTIVITY
if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
//从追踪队列中移除该Job
mTrackedJobs.remove(jobStatus);
}
}
3.2.4 updateTrackedJobs
当控制器监听到网络变化的时候则会调用到函数updateTrackedJobs,下面来看看其具体到源码:
/**
* filterUid:如果该值不等于-1则只需要更新该Uid下的所有Job,否则更新所有追踪队列中的job
* filterNetwork:如果该值不为null,则只需要更新使用到该网络的job,否则更新所有追踪队列中的job
**/
private void updateTrackedJobs(int filterUid, Network filterNetwork) {
synchronized (mLock) {
final SparseArray<Network> uidToNetwork = new SparseArray<>();
final SparseArray<NetworkCapabilities> networkToCapabilities = new SparseArray<>();
boolean changed = false;
for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
final JobStatus js = mTrackedJobs.valueAt(i);
final int uid = js.getSourceUid();
//如果filerUid不为-1并且在追踪队列中存在该uid所对应的job,则只会更新该uid下所有的job
final boolean uidMatch = (filterUid == -1 || filterUid == uid);
if (uidMatch) {
//缓存当前uid所对应的网络状态
Network network = uidToNetwork.get(uid);
if (network == null) {
network = mConnManager.getActiveNetworkForUid(uid);
uidToNetwork.put(uid, network);
}
//如果filterNetwork不为null,或者应用所需要的网络和filterNetwork匹配,则会更新当前job的网络状态
final boolean networkMatch = (filterNetwork == null
|| Objects.equals(filterNetwork, network));
//如果当前应用所对应的网络状态发生了变化则会强制更新当前Job
final boolean forceUpdate = !Objects.equals(js.network, network);
if (networkMatch || forceUpdate) {
final int netId = network != null ? network.netId : -1; //用于获取当前网络具有什么样的能力,比如该网络能够访问internet等
NetworkCapabilities capabilities = networkToCapabilities.get(netId);
//缓存当前网络所对应的能力
if (capabilities == null) {
capabilities = mConnManager.getNetworkCapabilities(network);
networkToCapabilities.put(netId, capabilities);
}
//判断当前网络是否满足当前Job执行要求
changed |= updateConstraintsSatisfied(js, network, capabilities);
}
}
}
//如果有Job需要的网络状态发生变化,检测哪些Job已经满足执行条件,并将满足执行条件的Job添加到待执行队列中
if (changed) {
mStateChangedListener.onControllerStateChanged();
}
}
}
每次执行该方法的过程中都会重新缓存uid所对应的网络状态以及网络id所对应的网络能力以便下次遍历的时候如果uid所对应的网络状态相同或者netId所对应的网络能力相同则不再需要重新通过Binder的方式重新获取,这样能够节约系统资源。当遍历到的job需要更新网络状态的时候则会判断当前网络是否满足要求,如果满足则会将当前网络添加到Job中并设置标识当前网络已经能够满足job的执行。
3.2.5 updateConstraintsSatisfied
该方法主要判断当前网络是否满足要求,如果满足要求则会将当前网络添加到Job中,相关源码如下:
private boolean updateConstraintsSatisfied(JobStatus jobStatus, Network network,NetworkCapabilities capabilities) {
//当前job所对应到JobService会在前台运行,因此会忽略掉后台网络限制
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
//获取当前网络信息
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network,
jobStatus.getSourceUid(), ignoreBlocked);
//获取当前网络是否已经连接
final boolean connected = (info != null) && info.isConnected();
//判断当前网络是否满足条件:
//(1)预估当前网络处理当前Job中所设置的数据流量最大时间,如果超过的10min则不满足
//(2)如果当前网络拥挤并且当前执行时间在job的最早执行时间和最晚执行时间之间,同时
//(如果当前时间-job最早执行时间)/(最晚执行时间 - 最早执行时间)小于了50%,则不满足条件
//(3)当前网路能力是否和job所要求的网络能力是否匹配,如果不匹配则不满足;
//(4)针对调用了setPrefetch方法的Job,调用了该方法之后会放宽网络限制
//并判断在放宽了网络限制之后当前网络是否满足条件。
//如果上面四种条件都不满足则表示当前网络不满足当前job的执行条件。
final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
//如果当前网络满足执行条件,则会在表示Job条件的flag中添加CONSTRAINT_CONNECTIVITY表示网络条件已经达到要求
final boolean changed = jobStatus
.setConnectivityConstraintSatisfied(connected && satisfied);
//存储当前网络
jobStatus.network = network;
return changed;
}
该方法主要是为了判断当前网络是否满足Job执行的要求,其中包括网络是否连接、预估的Job上传下载数据总的时间不能超过10min、当前网路所具备的能力是否和job所要求的能力是否匹配等,如果条件满足才会对当前Job标识网络条件已经具备了,并且可以执行Job了。不过这里有必要提一下其中匹配条件(2),也就是在网络拥挤的情况下当前Job已经相对于最早执行时间的延迟时间百分比计算过程:
(1)首先会判断当前网络是否拥挤;
(2)然后获取当前的开机时间,并且会做如下的条件判断,具体代码如下:
//返回1表示该Job需要被执行,返回0则可以被继续延迟执行
public float getFractionRunTime() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
//如果没有设置最早执行时间和最晚执行时间则直接返回1
if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
return 1;
//如果没有设置最早执行时间并且当前时间大于等于最晚执行时间则返回1
} else if (earliestRunTimeElapsedMillis == 0) {
return now >= latestRunTimeElapsedMillis ? 1 : 0;
//如果没有设置最晚执行时间并且当前时间大于等于了最早执行时间则返回1
} else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
return now >= earliestRunTimeElapsedMillis ? 1 : 0;
//设置了最早执行时间和最晚执行时间
} else {
//如果当前时间还没有到Job的最早执行时间则返回0
if (now <= earliestRunTimeElapsedMillis) {
return 0;
//如果当前时间大于等于了最晚执行时间则直接返回1
} else if (now >= latestRunTimeElapsedMillis) {
return 1;
//如果当前时间在最早执行时间和最晚执行时间之间,则计算job已经被延迟的时间占最晚和最早时间段的百分比,并返回
} else {
return (float) (now - earliestRunTimeElapsedMillis)
/ (float) (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis);
}
}
}
(3)如果通过步骤(2)获取到的值小于百分之50则可以继续延迟Job的执行,这样可以在网络拥挤的情况下减轻网络压力。
3.3 TimeController
在JobInfo中提供了三个接口分别用于设置Job的循环执行时间、最早延迟执行时间和最晚延迟执行时间,并且后两个方法和第一个方法不能同时使用,否则会抛出异常。三个方法如下:
(1)setPeriodic(long intervalMillis, long flexMillis):其中参数1表示该job循环执行的时间间隔,也就是最早执行时间,最小值为15min;参数2表示在最早执行时间之后可被延迟的时间,也就是最晚执行时间,最大值为5min;
(2)setMinimumLatency(long minLatencyMillis):用于设置Job最早执行时间;
(3)setOverrideDeadline(long maxExecutionDelayMillis):用于设置最晚执行时间,如果达到了该时间设置的其它条件没有满足,任务仍然会被执行,当然需要该Job不受Doze影响并且AppSandby也允许该Job运行。
当Job调用了这三个方法之后,TimeController控制器才会生效。而在该控制器中的主要实现方式则是通过注册Alarm,当注册的Alarm被执行之后则会判断追踪队列中的Job是否允许被执行。相关源码如下:
3.3.1 maybeStartTrackingJobLocked
该方法的主要目的是将job添加到该控制到追踪列表中,相关源码如下:
public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
//判断当前job是否设置了最早最晚执行时间
if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
//从追踪列表该job,并cancel设置的Alarm
maybeStopTrackingJobLocked(job, null, false);
final long nowElapsedMillis = sElapsedRealtimeClock.millis();
//如果没有设置最早最晚执行时间则不添加到追踪队列
if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
return;
//如果设置了最早执行时间并且最早执行时间大于当前时间,说明还没有达到执行时间,则会在后续添加到追踪队列中
//如果当前时间大于最早执行时间,则设置标签已经满足了最早执行时间,但是没有设置最晚执行时间,则不需要添加到追踪队列中
} else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
nowElapsedMillis)) {
if (!job.hasDeadlineConstraint()) {
return;
}
}
boolean isInsert = false;
//获取list列表中iterator对象,用于遍历Job
ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
//从列表的最末端向前进行遍历,并按照Job的最晚执行时间从小到大进行插入,也就是最晚执行时间越小则越早执行
while (it.hasPrevious()) {
JobStatus ts = it.previous();
if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
isInsert = true;
break;
}
}
if (isInsert) {
it.next();
}
it.add(job);
//表示TimeController这个控制器已经成功追踪该Job
job.setTrackingController(JobStatus.TRACKING_TIME);
//根据job最早最晚执行时间设置Alarm
maybeUpdateAlarmsLocked(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
}
}
在该方法中首先判断该Job是否设置了最早最晚执行时间,然后判断该job是否已经被追踪,如果是则先停止追踪该Job并cancel所设置的相关Alarm,接着判断当前job的最早延迟时间是否已经达到并且没有设置最晚执行时间则不将该job添加到追踪队列中;否则将该Job按照最晚执行时间从小到大的顺序添加到追踪队列中,最后设置相关延迟时间Alarm。这个方法里面有一个有趣的地方就是会根据job设置的最晚执行时间来插入到队列的相关位置,值越小越靠前那么job被执行的时间也就越早。
3.3.2 maybeUpdateAlarmsLocked
private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,WorkSource ws) {
//如果当前Job最小延迟时间小于上一次Job的最小延迟时间则设置最小延迟时间Alarm
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
}
//如果当前Job最晚延迟时间小于上一次Job的最晚延迟时间则设置最大延迟时间Alarm
if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
}
}
该方法中分别设置了最早延迟时间和最晚延迟时间Alarm,并且保证所设置的Alarm触发时间是所有Alarm中的最早时间,防止Job被误延迟;同时如果当前Job没有设置最早触发时间或者最晚触发时间则会取消已经注册的Alarm(当然如果之前已经有Job注册的Alarm一般不会存在这种情况),否则根据延迟时间注册最早延迟时间和最晚延迟时间相关Alarm。
在阅读这一部分源码的过程中不知道有没有这样一个疑问:如果所有Job都满足注册Alarm的条件每个Job都会重新注册一遍,这样会不会存在一个Job对应一个Alarm从而导致系统资源的浪费。其实这种情况并不会出现,在阅读AlarmManagerService的时候会发现,如果当前注册的Alarm中的PendingIntent或者OnAlarmListener对象相同的话则将Alarm队列中原来的Alarm用新的Alarm替换。也就是说在TimerController的生命周期中只会存在两个Alarm,分别是最早延迟时间和最晚执行时间对应的Alarm。下面对最早执行时间到达之后和最晚执行时间达到之后,所调用到的方法分别讲解。
3.3.3 checkExpiredDelaysAndResetAlarm
该方法是当最早延迟时间达到后的处理过程,源码如下:
private void checkExpiredDelaysAndResetAlarm() {
synchronized (mLock) {
final long nowElapsedMillis = sElapsedRealtimeClock.millis();
long nextDelayTime = Long.MAX_VALUE;
int nextDelayUid = 0;
String nextDelayPackageName = null;
boolean ready = false;
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
final JobStatus job = it.next();
//判断当前Job是否设置了延迟时间执行,如果没有则直接过滤
if (!job.hasTimingDelayConstraint()) {
continue;
}
//判断当前job最早执行时间是否已经到达,如果到达则设置该job执行时间已经满足条件,当其它条件满足之后会执行该Job
if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
//如果当前Job已经达到了最早执行时间并且最晚执行时间没有设置或者已经达到了最晚执行时间则从追踪队列中移除该Job,其它controller继续追踪
if (canStopTrackingJobLocked(job)) {
it.remove();
}
//判断job是否满足所有执行条件或者是否已经达到了最晚执行时间并且系统不处于Doze状态同时不受后台限制则满足执行条件
if (job.isReady()) {
ready = true;
}
//如果当前Job不满足最早执行时间,则计算下一次Alarm的触发时间
} else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) {
final long jobDelayTime = job.getEarliestRunTime();
if (nextDelayTime > jobDelayTime) {
nextDelayTime = jobDelayTime;
nextDelayUid = job.getSourceUid();
nextDelayPackageName = job.getSourcePackageName();
}
}
}
/回调到JobSchedulerService中,检测哪些Job已经满足执行条件,并将满足执行条件的Job添加到待执行队列中
if (ready) {
mStateChangedListener.onControllerStateChanged();
}
//设置下一次Alarm的触发时间
setDelayExpiredAlarmLocked(nextDelayTime,
deriveWorkSource(nextDelayUid, nextDelayPackageName));
}
当最早执行时间相关Alarm回调到该函数之后,会遍历追踪队列中所有的Job,如果当前时间已经满足了job的最早执行时间,那么会设置已经满足了执行时间相关的flag=CONSTRAINT_TIMING_DELAY到总的flags=satisfiedConstraints中,同时如果没有设置最晚时间或者最晚执行时间已经达到了则会从追踪队列中移除该Job,然后判断该Job是否满足允许被执行条件。如果当前Job的最早时间还没有到达那么会将该job的最早触发时间参与到计算下一次Alarm的触发时间。
在该方法遍历job的过程中,如果当前Job的最早执行时间已经满足了要求,则会去判断当前Job的其它执行条件是否已经满足,如果满足了才会去通知JobSchedulerService更新可被执行的Job队列;如果没有满足则会判断当前Job的最晚执行时间是否已经达到,如果达到了并且当前系统不处于Doze同时当前Job不受后台限制则会更新可被执行队列。也就是说job设置的最晚执行时间会在满足一定条件下确保该Job最晚什么时候会被执行,否则必须等待该Job所有条件满足了才会被执行。
3.3.4 checkExpiredDeadlinesAndResetAlarm
该方法的主要目的是判断job的最晚执行时间是否达到。相关源码如下:
private void checkExpiredDeadlinesAndResetAlarm() {
synchronized (mLock) {
long nextExpiryTime = Long.MAX_VALUE;
int nextExpiryUid = 0;
String nextExpiryPackageName = null;
final long nowElapsedMillis = sElapsedRealtimeClock.millis();
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
JobStatus job = it.next();
if (!job.hasDeadlineConstraint()) {
continue;
}
//判断当前时间是否达到了当前Job的最晚执行时间,如果到达了则会立马执行该Job,并从追踪队列中移除该Job
if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
mStateChangedListener.onRunJobNow(job);
it.remove();
//如果没有达到则会重新计算下一次最晚执行时间相关Alarm
} else {
nextExpiryTime = job.getLatestRunTimeElapsed();
nextExpiryUid = job.getSourceUid();
nextExpiryPackageName = job.getSourcePackageName();
break;
}
}
setDeadlineExpiredAlarmLocked(nextExpiryTime,
deriveWorkSource(nextExpiryUid, nextExpiryPackageName));
}
}
3.3.5 总结
在TimeController中设置了两个定时Alarm,其触发时间分别对应于追踪队列中Jobs的最小最早执行时间和最小最晚执行时间。当job的最晚执行时间达到了之后就会立马执行该任务不论其它条件是否满足;如果只是达到了最早执行时间但是其它执行条件没有满足则会继续等待其它执行条件,直达其它条件也满足才会执行。
3.4 IdleController
该类用于控制设备是否到达了Idle状态,对应于 JobInfo中的setRequiresDeviceIdle方法。在该类中主要通过监听亮灭屏广播、开始/结束屏保以及无线充电相关广播并通过配置文件中的阀值来判断当前设备是否已经进入到Idle状态。
当调用该类的maybeStartTrackingJobLocked方法首先会判断当前Job是否设置了需要设备处于Idle状态才会被执行,如果设置了则将该Job添加到追踪队列中,如果当前设备已经处于Idle状态,则会设置满足idle状态的flag;当调用maybeStopTrackingJobLocked方法则会从追踪队列中移除该方法。其中的代码比较简单这里就不再贴上来了,主要对广播接收器中的onReceive函数进行讲解,源码如下:
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
//这几个action表示当前设备可能处于可交互状态
if (action.equals(Intent.ACTION_SCREEN_ON)
|| action.equals(Intent.ACTION_DREAMING_STOPPED)
|| action.equals(Intent.ACTION_DOCK_ACTIVE)) {
//如果当前处于灭屏状态则会忽略掉该action,表示当前可继续计时进入到Idle状态Alarm
if (action.equals(Intent.ACTION_DOCK_ACTIVE)) {
if (!mScreenOn) {
return;
} else {
mDockIdle = false;
}
} else {
mScreenOn = true;
mDockIdle = false;
}
//如果当前处于可交互状态,则会取消之前所设置的到达idle状态的相关Alarm
mAlarm.cancel(mIdleAlarmListener);
//如果设备从idle状态变更为了可交互状态,则会从Job的flag中移除满足idle状态相关flag=CONSTRAINT_IDLE,并通知JobSchedulerService设备idle状态有所变化
if (mIdle) {
mIdle = false;
reportNewIdleState(mIdle);
}
//这几个action表示当前设备可能处于非交互状态
} else if (action.equals(Intent.ACTION_SCREEN_OFF)
|| action.equals(Intent.ACTION_DREAMING_STARTED)
|| action.equals(Intent.ACTION_DOCK_IDLE)) {
//如果是在灭屏情况下则会直接忽略该action,如果是灭屏则表明灭屏广播已经触发了该逻辑不用再次触发
if (action.equals(Intent.ACTION_DOCK_IDLE)) {
if (!mScreenOn) {
return;
} else {
mDockIdle = true;
}
} else {
mScreenOn = false;
mDockIdle = false;
}
final long nowElapsed = sElapsedRealtimeClock.millis();
//在71分钟之后进入idle状态
final long when = nowElapsed + mInactivityIdleThreshold;
//设置Alarm,该Alarm因为是系统设置的所以在AMS中会被FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED标签因此不会受到Doze模式的影响,不过因为设置了windowLength的值所以可能会被添加到其它Batch队列中去
mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
}
//如果接收到action=ActivityManagerService.ACTION_TRIGGER_IDLE到广播表示可能是在测试,当然需要系统上次处于非idle状态并且处于灭屏或者无线充电处于idle状态
else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
handleIdleTrigger();
}
}
在该类中会通过广播的方式判断系统是处于可交互状态还是不可交互状态,如果当前处于灭屏状态下则会直接忽略掉action=ACTION_DOCK_IDLE相关广播。当到达了Alarm所设置的时间会通知追踪队列中的所有job更新已经满足idle条件,然后再通知JobSchedulerService更新可执行列表。
3.5 BatteryController
该Controller追踪器监听设备是否充电或者是否低电量,对应于JobInfo中的setRequiresCharging和setRequiresBatteryNotLow方法,只有调用了这两个方法的Job才会被添加到该追踪器的追踪列表中。
在该类的构造方法中会注册ACTION_BATTERY_LOW、ACTION_BATTERY_OKAY、ACTION_CHARGING和ACTION_DISCHARGING四个action相关的广播接收器,用于判断设备是否正在充电或者是否处于低电量,当四个条件有一个变化的时候就会根据情况判断是否更新job状态,相关源码如下:
private void maybeReportNewChargingStateLocked() {
//判断当前设备是否正在充电且处于非低电量
final boolean stablePower = mChargeTracker.isOnStablePower();
//判断设备是否处于非低电量
final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
boolean reportChange = false;
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.valueAt(i);
//更新该Job所要求的是否充电条件
boolean previous = ts.setChargingConstraintSatisfied(stablePower);
//判断当前是否设备充电状态和上一次是否一致,如果有所改变则会通知JobschedulerService有Job需要的条件改变了
if (previous != stablePower) {
reportChange = true;
}
//更新该Job要求的是否非低电量条件
previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
if (previous != batteryNotLow) {
reportChange = true;
}
}
//如果满足了非低电量或者充电则可能会马上执行所有已经准备好的Job
if (stablePower || batteryNotLow) {
mStateChangedListener.onRunJobNow(null);
//说明当前设备可能处于非充电或者低电量的情况,需要更新准备执行队列中的Job
} else if (reportChange) {
mStateChangedListener.onControllerStateChanged();
}
}
3.6 StorageController
该Controller追踪器监听系统是内存状态,对应于Jobinfo中的setRequiresStorageNotLow方法,需要设备非低内存的情况下才会执行该Job。在该类的构造方法中注册了ACTION_DEVICE_STORAGE_LOW和ACTION_DEVICE_STORAGE_OK两个action相关的广播接收器,当接收到内存充足的广播之后则会回调到maybeReportNewStorageState。相关源码如下:
private void maybeReportNewStorageState() {
//获取当前是否处于低内存状态
final boolean storageNotLow = mStorageTracker.isStorageNotLow();
boolean reportChange = false;
synchronized (mLock) {
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.valueAt(i);
//设置内存最新状态到JobStatus中,以更新是否满足非低内存状态
boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
if (previous != storageNotLow) {
reportChange = true;
}
}
}
if (reportChange) {
mStateChangedListener.onControllerStateChanged();
}
if (storageNotLow) {
mStateChangedListener.onRunJobNow(null);
}
}
当然当调用该方法的maybeStartTrackingJobLocked方法将Job添加到该Controller到追踪列表的时候首先会判断该Job是否设置了需要满足非低内存,如果设置了则会将该Job添加到追踪队列中,并设置当前内存状态到Job中。
3.7 BackgroundJobsController
该Controller追踪器的作用是根据应用待机群组(AppStandby)对后台应用Job使用加以限制,该策略是Android9.0新增的电池管理策略,目的是对系统中各个应用根据用户的使用频率划分为不同的类型,以降低系统功耗。
在该类的构造方法中通过设置监听器到AppStateTracker对象中,当AppStandby中状态发生变化的时候则会调用到所设置当AppStateTracker对象监听器对应的方法中最后调用到updateJobRestrictionsLocked方法中。构造方法的代码块比较简单这里不做讲解,主要讲解updateJobRestrictionsLocked方法。源码如下:
/**
* filterUid:某一个应用的状态改变了,-1系统状态改变了(会影响所有job的执行)
* newActiveState:该应用是否处于活跃状态(应用待机群组权限最高的状态)
**/
private void updateJobRestrictionsLocked(int filterUid, int newActiveState) {
//当在JobStore中遍历Job的时候会回调到该类中到accept方法中
final UpdateJobFunctor updateTrackedJobs = new UpdateJobFunctor(newActiveState);
final JobStore store = mService.getJobStore();
//如果是某一个应用状态改变了则只需要遍历该应用中的job,否则遍历所有的Job
if (filterUid > 0) {
store.forEachJobForSourceUid(filterUid, updateTrackedJobs);
} else {
store.forEachJob(updateTrackedJobs);
}
//如果Job或者job所对应的应用状态变化了则通知JobschedulerService更新可执行队列
if (updateTrackedJobs.mChanged) {
mStateChangedListener.onControllerStateChanged();
}
}
在该方法中并没有做什么实际的处理,只是根据AppStandy回传的值判断是处理某一个应用中的Job还是处理所有Job。在JobStore中遍历每个Job的时候会回调到UpdateJobFunctor对象中的accept方法,而在该方法中又会调用到updateSingleJobRestrictionLocked方法中判断当前Job是否被限制后台job运行。源码如下:
boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, int activeState) {
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
//判断当前Job是否允许后台运行
final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,(jobStatus.getInternalFlags() &JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
!= 0);
//获取当前应用是否处于运行状态
final boolean isActive;
if (activeState == UNKNOWN) {
isActive = mAppStateTracker.isUidActive(uid);
} else {
isActive = (activeState == KNOWN_ACTIVE);
}
//设置当前Job是否允许后台运行
boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
//如果当前job所对应的应用状态改变则更改
didChange |= jobStatus.setUidActive(isActive);
return didChange;
}
该Controller追踪器的实现方式和ConnectivityController的实现方式有类似之处,都是设置监听器到对应到模块中,当对应模块中的状态发生变化之后则会调用到监听器中的方法去更新Job所需要的状态。
3.8 ContentObserverController
该处不做讲解,后续补充。
3.9 DeviceIdleJobsController
在Android6.0以上google为系统处于深度睡眠的情况下优化功耗,新增了Doze状态,而在类的主要作用就是在Doze模式对系统中的Job进行限制。首先在该类的构造方法中初始化了变量并注册了ACTION_DEVICE_IDLE_MODE_CHANGED、ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED、ACTION_POWER_SAVE_WHITELIST_CHANGED和ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED四个action相关的广播接收器,即是idle、lightidle、白名单和临时白名单变化相关广播,当系统的Doze状态发生变化之后会发送相应的广播通知各个模块。
3.9.1 maybeStartTrackingJobLocked
当Job进入到该Controller之后会判断当前Job是否在Doze状态下允许运行。相关源码如下:
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
//标识前台应用Job很重要或者该应用处于临时白名单中,则将该应用添加到允许Doze下运行名单中
if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
mAllowInIdleJobs.add(jobStatus);
}
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));
//该应用是否处于Doze模式白名单中
final boolean whitelisted = isWhitelistedLocked(task);
//当前不处于Doze状态或者处于Doze状态白名单或者运行在Doze模式下运行
final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
//设置该应用是否运行在Doze模式下运行,对应于flag=CONSTRAINT_DEVICE_NOT_DOZING
return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
}
在JobSchedulerService中会调用各个Controller追踪器将设置了当前Controller监听条件相关的Job添加到对应的追踪列表中,当Controller中监听到条件改变之后则会改变对应追踪列表中所有Job的条件改变标识,并通知JobSchedulerService更新待执行列表。而对于Doze相关的Controller是针对所有的Job,因此当任何Job进入到该追踪队列中时都会判断当前Job是否满足在Doze模式执行的flag。
3.9.2 广播接收器
在前面讲解到在该类的构造函数中有注册相关的广播接收器以监听Doze模式的变化,相关源码如下:
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
//deepDoze和lightDoze模式变化广播通知
case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
|| mPowerManager.isLightDeviceIdleMode()));
break;
//Doze模式白名单变化广播
case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
synchronized (mLock) {
mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
}
break;
//Doze模式临时白名单变化广播
case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
synchronized (mLock) {
mPowerSaveTempWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
boolean changed = false;
//如果临时白名单变化则会更新在Doze模式下是否允许应用使用Job
for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
}
}
break;
}
}
};
//当Doze模式变化的时候会调用到该方法
void updateIdleMode(boolean enabled) {
boolean changed = false;
synchronized (mLock) {
//判断当前Doze是否变化
if (mDeviceIdleMode != enabled) {
changed = true;
}
mDeviceIdleMode = enabled;
//如果设备进入到了Doze模式,会回调到updateTaskStateLocked方法中设置Job是否允许在Doze状态下执行
if (enabled) {
mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
} else {
//如果当前是退出Doze模式,则会立即处理处于前台应用的相关Job,并在处理完之后,再过3s再处理处于后台应用的job(允许Job运行)
for (int i = 0; i < mForegroundUids.size(); i++) {
if (mForegroundUids.valueAt(i)) {
mService.getJobStore().forEachJobForSourceUid(
mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
}
}
//在3s以后处理所有后台应用的Job
mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
}
}
//如果Doze模式改变则会通知JobSchedulerService做相应的处理,比如进入到Doze之后会停止所有除允许运行的Job运行
if (changed) {
mStateChangedListener.onDeviceIdleStabteChanged(enabled);
}
}
四个action相关的广播主要处理了Doze模式状态变化的广播,当监听到设备进入到Doze模式之后则会更新系统中所有的Job是否允许在Doze模式下使用Job,如果不允许则会停止掉Job的运行。如果监听到当前设备退出Doze模式则首先会处理处于前台应用的Job,然后在3s之后处理处于后台运行应用的Job。
4、总结
其实在了解完了所有Controller追踪器之后,会发现它们的实现细节虽然各有不同,但是在大致的实现逻辑上都是大同小异的。当JobSchedulerService在初始化各个Controller的时候会注册对应的监听器或者广播或者Alarm等用于检测系统诸如电池信息、网络状态、内存状态以及Doze状态等是否发生了变化。然后等待设置了对应Controller所检测条件的Job到对应Controller的追踪列表中,当Controller检测到所检测的状态发生变化的时候则会更新Job中与该状态相关联的flag,并通知到JobSchedulerService中更新待执行队列,如果满足执行条件则会执行待执行队列中的Job。
当然在所有的Controller中也有两个特例,分别是DeviceIdleJobsController和BackgroundJobsController,它们会影响到所有的Job,所以在这两个Controller中没有对应的追踪队列。