LightDoze表示轻度doze模式,如果设备处于未充电且屏幕关闭状态,但未处于静止状态时,就会进入Light Doze模式,在LightDoze模式中,会定期进行维护,这种维护会持续N分钟,在维护状态(maintenance)时,会进行网络的访问,和同步、JobScheduler的操作,然后又会进入Idle状态,持续多次。之后如果设备仍旧保持静止,则会进入Deep Doze模式,因此,如果没有运动传感器,则无法检测设备是否处于静止,因此无法进入Deep Doze模式。
在DeviceIdleController(以下简称DIC)中,定义了几个用来表示Light Doze模式有关的值,这些值及其意义如下:
//表示轻度doze模式
private int mLightState;
//mLightState状态值,表示设备处于活动状态
private static final int LIGHT_STATE_ACTIVE = 0;
//mLightState状态值,表示设备处于不活动状态
private static final int LIGHT_STATE_INACTIVE = 1;
//mLightState状态值,表示设备进入空闲状态前,需要等待完成必要操作
private static final int LIGHT_STATE_PRE_IDLE = 3;
//mLightState状态值,表示设备处于空闲状态,该状态内将进行优化
private static final int LIGHT_STATE_IDLE = 4;
//mLightState状态值,表示设备处于空闲状态,要进入维护状态,先等待网络连接
private static final int LIGHT_STATE_WAITING_FOR_NETWORK = 5;
//mLightState状态值,表示设备处于维护状态
private static final int LIGHT_STATE_IDLE_MAINTENANCE = 6;
//mLightState状态值,表示设备轻度Doze被覆盖,开始进入深度Doze模式
private static final int LIGHT_STATE_OVERRIDE = 7;
从上述代码中可知,共有7个状态类表示Light Doze模式的不同状态。接下来开始分析Light Doze的实现以及其七个状态之间的转换。
在第一部分中分析启动流程时,当DCI启动流程的最后,会调用becomeInactiveIfAppropriateLocked()
方法和becomeActiveLocked()方法,这两个方法用来切换交互/不可交互状态,这里先看第一个方法:
void becomeInactiveIfAppropriateLocked() {
//仅当屏幕灭屏且没充电时,才能进入Doze模式,mForceIdle是shell设置相关
if ((!mScreenOn && !mCharging) || mForceIdle) {
//处理深度Doze,该状态表示当前设备处于交互状态,即亮屏、有动作移动
if (mState == STATE_ACTIVE && mDeepEnabled) {
//该状态表示DeepDoze的不可交互状态
mState = STATE_INACTIVE;
//重置标记值、取消原有的用于DeepDoze的alarm等
resetIdleManagementLocked();
//设置DeepAlarm,时间为30mins
scheduleAlarmLocked(mInactiveTimeout, false);//30mins
}
//进入轻度Doze模式
if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
//处理LightDoze模式进入不可交互状态
mLightState = LIGHT_STATE_INACTIVE;
//取消用于LightDoze的定时Alarm,重置标记值
resetLightIdleManagementLocked();
//设置用于LightDoze模式的Alarm,到达时间后回调
//mLightAlarmListener,时间为5mins
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_
AFTER_INACTIVE_TIMEOUT);//5mins
}
}
}
如果要执行该方法,首要条件必须是灭屏且放电,或者mForceIdle为true,mForceIdle
是用于在adb shell下使用命令设置的,因此分析时可忽略。
如果满足灭屏且放电,则分别进行DeepDoze和LightDoze的判断.这里先分析Light Doze的判断,如果当前LightDoze处于LIGHT_STATE_ACTIVE
状态,且mLightEnabled为true,则进行如下的处理:
在DIC启动时,将mLightState设置为了LIGHT_STATE_ACTIVE
,所以这个值是一个默认值。先来看第二点,调用了resetLightIdleManagementLocked()
,该方法如下:
void resetLightIdleManagementLocked() {
//取消mLightAlarmListener要接受的Alarm
cancelLightAlarmLocked();
}
void cancelLightAlarmLocked() {
if (mNextLightAlarmTime != 0) {
mNextLightAlarmTime = 0;
mAlarmManager.cancel(mLightAlarmListener);
}
}
如果设有用于LightDoze的定时Alarm,则进行取消。
再来看看第三点,在scheduleLightAlarmLocked()
方法中:
void scheduleLightAlarmLocked(long delay) {
mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
//到达时间后,回调mLightAlarmListener.onAlarm()
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextLightAlarmTime, "DeviceIdleController.light",
mLightAlarmListener, mHandler);
}
结合第二点和第三点,可以认为重置了一个用于LightDoze的Alarm定时器。mLightAlarmListener
用来接受Alarm的回调,定时器到达指定时间后,会触发mLightAlarmListener接口的onAlarm()
方法,从而在onAlarm()中完成接下来的工作,该接口如下:
private final AlarmManager.OnAlarmListener mLightAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (DeviceIdleController.this) {
//每次LightDoze的状态改变,都会调用该方法进行处理
stepLightIdleStateLocked("s:alarm");
}
}
};
因此,当定时器到达时间后,通过onAlarm()方法调用stepLightIdleStateLocked()
方法。stepLightIdleStateLocked()方法负责LightDoze模式的改变,在这个方法中根据不同的状态发送不同的Alarm定时器来改变状态。可以这样理解,LightDoze模式改变就是通过scheduleLightAlarmLocked()---onAlarm()---stepLightIdleStateLocked()---scheduleLightAlarmLocked()
不断的循环来进行的,这样说可能不太明白,还是从代码进行分析,最后对它进行总结,该方法代码如下:
void stepLightIdleStateLocked(String reason) {
//如果mLigthSate为LIGHT_STATE_OVERRIDE,说明DeepDoze处于Idle状态,由
// DeepDoze将LightDoze覆盖了,因此不需要进行LightDoze了
if (mLightState == LIGHT_STATE_OVERRIDE) {
return;
}
switch (mLightState) {
case LIGHT_STATE_INACTIVE:
//当前最小预算时间
mCurIdleBudget =
mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;//1min
//表示LightDoze 进入空闲(Idle)状态的时间
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;//5mins
//LightDoze进入维护状态(maintenance)的开始时间
mMaintenanceStartTime = 0;
//如果有活动的操作或JobScheduler、Alarm处于活动状态,
//再给他们一次机会进行关闭,break返回,
//否则由于没有break,会进入下条case语句
if (!isOpsInactiveLocked()) {
//将状态置为LIGHT_STATE_PRE_IDLE状态
mLightState = LIGHT_STATE_PRE_IDLE;
//设置一个定时器,到达时间后用来处理LightDoze处于PRE_IDLE
//状态的操作
scheduleLightAlarmLocked(mConstants.LIGHT_PRE_
IDLE_TIMEOUT);//10mins
break;
}
case LIGHT_STATE_PRE_IDLE:
case LIGHT_STATE_IDLE_MAINTENANCE:
if (mMaintenanceStartTime != 0) {
//维护状态的时长
long duration = SystemClock.elapsedRealtime() -
mMaintenanceStartTime;
if (duration <
mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
//
mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE
_MIN_BUDGET-duration);
} else {
//
mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_
MAINTENANCE_MIN_BUDGET);
}
}
mMaintenanceStartTime = 0;//重置维护开始时间
//设置一个定时器,到达时间后用来处理LightDoze处于IDLE状态的操作
scheduleLightAlarmLocked(mNextLightIdleDelay);
//计算下次进入Idle状态的
mNextLightIdleDelay =
Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,//900000,15mins
(long)(mNextLightIdleDelay *
mConstants.LIGHT_IDLE_FACTOR));
if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
}
//将LightDoze模式置为IDLE状态,开始进行一些限制
mLightState = LIGHT_STATE_IDLE;
addEvent(EVENT_LIGHT_IDLE);
//申请一个wakelock锁,保持CPU唤醒
mGoingIdleWakeLock.acquire();
//处理LightDoze进入Idle状态后的操作
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
break;
case LIGHT_STATE_IDLE:
case LIGHT_STATE_WAITING_FOR_NETWORK:
if (mNetworkConnected || mLightState ==
LIGHT_STATE_WAITING_FOR_NETWORK) {
//如果网络有链接或者当前LightDoze模式为等待网络状态,则进行维护,
// 并将LightDoze模式退出IDLE状态,进入维护状态
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
mMaintenanceStartTime = SystemClock.elapsedRealtime();
// 保证10<=mCurIdleBudget<=30mins ,mCurIdleBudget是维护状态的时间
if (mCurIdleBudget <
mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
mCurIdleBudget =
mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
} else if (mCurIdleBudget >
mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
mCurIdleBudget =
mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
}
//设置一个定时器,到达时间后用来处理LightDoze处于维护状态的操作
scheduleLightAlarmLocked(mCurIdleBudget);
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;//进入维护状态
addEvent(EVENT_LIGHT_MAINTENANCE);
//处理LightDoze进入Maintenance状态后的操作
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
} else {
//将LightDoze模式置为LIGHT_STATE_WAITING_FOR_NETWORK,
//在进入维护状态前需要获取网络
//设置一个定时器,到达时间后用来处理LightDoze处于
//WAITING_FOR_NETWORK状态的操作
scheduleLightAlarmLocked(mNextLightIdleDelay);//600000,5mins
mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
}
break;
}
}
在这个方法中,处理了LightDoze所有的状态改变时的工作,同时也会在这个方法中进行状态的转换,各个case分析如下:
case LIGHT_STATE_INACTIVE
:这部分主要处理由INACTIVE
到IDLE/PRE_IDLE
的过程。
第一次调用该方法时,LightDoze模式处于INACTIVE(不可交互状态)状态,因此会进入第一个case语句,在这个语句中,如果当前还有为处理完的任务,则会先进入IDLE状态之前的一个状态——PRE_IDLE状态,如果没有需要处理的任务,由于这里没有break,因此会继续执行到下一条case语句,将状态由INACTIVE设置为IDLE状态,并开始限制网络访问、Job延迟等处理;PRE_IDLE
状态是为了再等待一次未处理完成的工作,这个状态持续5mins,直到下次Alarm时间到再次进入该方法,直接走第二条case语句。因此,在执行第二条语句之前,LightDoze处于PRE_IDLE或者INACTIVE状态。
case LIGHT_STATE_PRE_IDLE
:case LIGHT_STATE_IDLE_MAINTENANCE
:这部分主要处理由PRE_IDLE/INACTIVE
到IDLE
和MAINTENANCE
到IDLE
的过程。在执行这两个case语句之前时,LightDoze处于PRE_IDLE或者INACTIVE状态,此时会变为IDLE状态;或者LightDoze处于MAINTENANCE,此时会变为IDLE状态。当LightDoze状态转为IDLE
后,会通过Handler发送广播,通知IDLE状态的改变,开始做优化工作(无法访问网络、Job、同步延迟)。
case LIGHT_STATE_IDLE
:case LIGHT_STATE_WAITING_FOR_NETWORK
:这部分主要处理由IDLE
到WAITING_FOR_NETWORK
和WAITING_FOR_NETWORK
到MAINTENANCE
的过程。当LightDoze状态由IDLE
转换为MAINTENANCE
后,会通过Handler发送广播,通知IDLE状态的改变,说明进入维护状态,开始做维护工作。
LightDoze的状态转换图如下:
下面我们就看当LightDoze状态转换为IDLE
和MAINTENANCE
后,是如何进行功耗的优化和维护工作的。
在上面我们分析了,当由INACTIVE/PRE_IDLE
状态进入IDLE状态时,和由IDLE/WAIT_FOR_NETWORK状态时,会通过Handler发送一个LightDoze模式改变的广播,通知接收器进行相应的处理,这里进行详细分析,进入IDLE状态时和进入维护状态时Handler中的相关操作如下:
@Override public void handleMessage(Message msg) {
.............................
switch (msg.what) {
case MSG_REPORT_IDLE_ON:
//LightDoze模式进入IDLE状态
case MSG_REPORT_IDLE_ON_LIGHT: {
if (msg.what == MSG_REPORT_IDLE_ON) {
//DeepDoze相关,这里略去
........
} else {
//通知PMS设置DeepDoze模式不处于IDLE状态
deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
//通知PMS设置轻度Doze模式处于IDLE状态
lightChanged =
mLocalPowerManager.setLightDeviceIdleMode(true);
}
try {
//通知NetworkPolicyManager LightDoze进入IDLE状态
mNetworkPolicyManager.setDeviceIdleMode(true);
//通知BatteryStatsService统计LightDoze进入IDLE状态
mBatteryStats.noteDeviceIdleMode(msg.what ==
MSG_REPORT_IDLE_ON
? BatteryStats.DEVICE_IDLE_MODE_DEEP
: BatteryStats.DEVICE_IDLE_MODE_LIGHT, null,
Process.myUid());
} catch (RemoteException e) {
}
//发送DeepDoze模式改变的广播
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
//发送LightDoze模式改变的广播
getContext().sendBroadcastAsUser(mLightIdleIntent,
UserHandle.ALL);
}
//释放wakelock锁
mGoingIdleWakeLock.release();
} break;
//退出IDLE状态时,包括DeepDoze
case MSG_REPORT_IDLE_OFF: {
final boolean deepChanged =
mLocalPowerManager.setDeviceIdleMode(false);
final boolean lightChanged =
mLocalPowerManager.setLightDeviceIdleMode(false);
try {
mNetworkPolicyManager.setDeviceIdleMode(false);
mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_
IDLE_MODE_OFF,null, Process.myUid());
} catch (RemoteException e) {
}
if (deepChanged) {
incActiveIdleOps();
getContext().sendOrderedBroadcastAsUser(mIdleIntent,
UserHandle.ALL, null, mIdleStartedDoneReceiver,
null, 0, null, null);
}
if (lightChanged) {
incActiveIdleOps();
getContext().sendOrderedBroadcastAsUser(mLightIdleIntent,
UserHandle.ALL,null, mIdleStartedDoneReceiver,
null, 0, null, null);
}
decActiveIdleOps();
} break;
}
}
从这部分代码可以知道,在LightDoze进入IDLE状态或退出IDLE状态时时,首先通知PMS、NetworkPolicyManager设置DeviceIdleMode为true或false,然后发送一个Intent为mLightIdleIntent的广播,该mLightIdleIntent是在启动时onBootPhase()
中定义:
mLightIdleIntent = new
Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
因此,当LightDoze进入IDLE
状态时或退出IDLE
状态进入MAINTENANCE
状态时,都会发送该广播,其他模块如果注册了该广播接受器,则会触发onReceive()方法进行处理。当前版本中注册了mLightIdleIntent的广播接收器的模块只有一处:DeviceIdleJobsController
,该类是一个用于根据Doze模式状态控制”Job”的控制类,当Doze(LightDoze和DeepDoze)模式状态为IDLE时,该类中会限制所有除白名单外的应用的”Job”
,当退出IDLE状态时,该类中会解除这些应用的”Job”限制,这里所指的”Job”,时Android L后实现的一个JobScheduler机制,允许开应用在符合某些条件时创建执行在后台的任务。接受广播方法如下:
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
//当DeepDoze或LightDoze的IDLE状态改变时,都会执行updateIdleMode()
if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE
_CHANGED.equals(action)|| PowerManager.ACTION_DEVICE
_IDLE_MODE_CHANGED.equals(action)) {
updateIdleMode(mPowerManager != null
? (mPowerManager.isDeviceIdleMode()
|| mPowerManager.isLightDeviceIdleMode())
: false);
} else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_
CHANGED.equals(action)) {
updateWhitelist();
}
}
};
再来看下DeviceIdleJobsController中updateIdleMode()
方法:
void updateIdleMode(boolean enabled) {
boolean changed = false;
// Need the whitelist to be ready when going into idle
if (mDeviceIdleWhitelistAppIds == null) {
updateWhitelist();//更新白名单列表
}
synchronized (mLock) {
if (mDeviceIdleMode != enabled) {
changed = true;
}
mDeviceIdleMode = enabled;
//遍历所有的Job
mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
}
if (changed) {
//回调到JobSchedulerService中,停止或开始Job
mStateChangedListener.onDeviceIdleStateChanged(enabled);
}
}
在这个方法中,通过JobSchedulerService回调onDeviceIdleStateChanged()方法对后台Jobs进行限制或运行,至于如何限制,涉及到JobScheduler相关内容,不再往下分析。
再此对上面分析内容进行一次总结:
当LightDoze进入IDLE/MAINTENANCE状态时,在Handler中:
如此反复多次,如果设备依旧保持静止且灭屏,则30分钟后,DeepDoze的Alarm时间到,因此会进入DeepDoze模式。LightDoze模式转换图示如下:
DeviceIdleController中仅仅调用NetworkPolicyManagerService提供的接口,具体如何限制在NetworkPolicyManagerService内部实现:
mNetworkPolicyManager.setDeviceIdleMode(true);
进入退出LightDoze后发送广播,DeviceIdleJobController
中进行了接收,接收后也是内部进行实现:
// onReceive
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
|| PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
updateIdleMode(mPowerManager != null
? (mPowerManager.isDeviceIdleMode()
|| mPowerManager.isLightDeviceIdleMode())
: false);
} else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) {
updateWhitelist();
}
}
};