上一篇文章:JobScheduler源码分析(三) Job从创建到执行
在前两篇文章中,对JobSchedulerService的启动和Job的调度过程大致做了个梳理,通过前几篇的分析我们知道,要使得客户端Job被JSS调度执行,必须满足该Job在创建时所设置的约束,而这些约束何时满足,这将由StateController
进行控制,本篇中将对所有StateController
类的控制流程进行分析。
通过前面文章的分析得,共有八个StateController
的子类,各自单独地记录每个Job的状态,并在Job准备好运行时通知JSS进行调度执行,或者在约束条件不满足时通知JSS停止Job的执行。所有的StateController类有如下共性:
- 1.在JobSchedulerService的构造方法中进行实例化;
- 2.每向JSS中加入一个Job,都会调用
StateController.maybeStartTrackingJobLocked()
方法开始记录跟踪此Job; - 3.当Job调度执行完毕后、或当Job被取消时,都会调用
StateController.maybeStopTrackingJobLocked()
方法停止此Job的记录;
除了以上共同特性外,各个状态控制器中对约束的设置,大部分是通过广播实现的,状态控制器内部会注册一个广播,当广播接收器收到广播后,将根据得到的状态,通过setXXXConstraintSatisfied()
方法,对satisfiedConstraints
进行按位运算,比如在DeviceIdleJobsController中会调用setDeviceNotDozingConstraintSatisfied(true/false)
来设置是否Doze的约束条件满足。第三篇文章分析时说过:JobStatus中有一个全局变量satisfiedConstraints
,这个表示该Job当前满足的约束,如果当前状态控制器所检测到的系统状态满足某个约束,则按位或,否则清零即可。最终在判断Job约束条件是否满足时,将拿satisfiedConstraints
和requiredConstraints
进行比较,requiredConstraints
变量上记录的是创建Job时在JobInfo设置的所有约束条件,也是通过按位或运算进行标记的:
public boolean isConstraintsSatisfied() {
//该Job所有的约束条件
final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST;
//该Job现在所满足的约束条件
int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
//相等,说明当前所满足约束已是所需所有约束
return (sat & req) == req;
}
下面我们就具体来看看,各个状态控制器如何获取并将结果设置给satisfiedConstraints
的,这部分逻辑比较清晰易懂,就不再啰哩啰嗦了,点到为止即可。
1. DeviceIdleJobsController
DeviceIdleJobsController用来控制Job对Doze的依赖条件,或者也可以说Doze对Job的限制,当设备进入Doze模式的IDLE状态时,将会限制除了Doze白名单外的所有应用的Job调度,当Doze退出IDLE状态进入维护状态后,将会对所有应用的Job解除限制。而DeviceIdleJobsController中则是通过广播的形式来感知Doze模式的状态变化,在其构造方法中可以看到广播的注册和监听:
public DeviceIdleJobsController(JobSchedulerService service) {
super(service);
// ......
final IntentFilter filter = new IntentFilter();
// Deep Doze状态发生改变后发送
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
// Light Doze状态发生改变后发送
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
// 白名单列表发生变化时发送
filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
// i临时白名单列表发生变化时发送
filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
mContext.registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
}
来看看关于广播接收器中的逻辑,其中只看当Doze的状态发生变化的部分:
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
// Deep Doze和Light Doze进入/退出IDLE后发送
case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
//根据Doze是否进入IDLE状态更新状态
updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
|| mPowerManager.isLightDeviceIdleMode()));
break;
}
}
};
以上逻辑中可以看到,不管是Deep Doze还是Light Doze,当进入或者退出IDLE后,执行的是同样的流程,这一方面也说明两种Doze对Job都是有延迟的。然后通过PowerManager获取到当前Doze的状态,并调用updateIdleMode()
更新,updateIdleMode()
方法中的处理主要如下:
- 1.Doze退出/进入时,都会进行Job的遍历,并最终会调用
updateTaskStateLocked()
方法更新Job的Doze约束; - 2.如果退出Doze后,立即会处理处于前台的进程的Job,其余进程通过Handler延时3s处理;
- 3.一旦Doze状态发生改变,将通过
onDeviceIdleStateChanged()
回调进入JSS中。
来看看updateTaskStateLocked()
:
private boolean updateTaskStateLocked(JobStatus task) {
//是否豁免Doze模式的限制:设置了标记且(对应app处于前台或app处于临时白名单列表中)
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);
}
在以上方法中,判断是否这个Job可以在Doze下调度呢?并将结果通过setDeviceNotDozingConstraintSatisfied()
设置给JobStatus:
boolean setDeviceNotDozingConstraintSatisfied(boolean state,