一、概述
Doze模式也称低功耗模式,是google在Android6.0开始引入的,目的是为了帮助用户延长电池的使用时间,当处于该模式下的时候系统会限制应用Alarm、wakelock、wifi以及网络等系统资源的使用。当然系统会定期退出Doze模式一小段时间,让应用完成其延迟的活动。在此维护期内,系统会运行所有待处理的Alarm、Job以及wakelock等,并允许应用访问网络。这里借助官方文档的一张图。
一旦用户通过移动设备、打开屏幕或连接至充电器唤醒设备,系统就会立即退出低电耗模式,并且所有应用都会恢复正常活动。
1、分类
即使是Doze模式,google也将其分为了两个大类:
(1)lightDoze模式:也就是轻度Doze状态,系统处于灭屏并且没有充电,但是设备可能处于移动;
(2)DeepDoze模式:系统处于灭屏状态并且没有充电同时还需要系统保持静止状态。
其实根据Doze模式的状态会发现和人睡觉的状态很类似,当人处于睡眠状态的时候(1)不会吃东西;(2)身体会保持一定的静止状态;(3)退出Doze状态就类似于人在睡眠过程的呼吸了。
2、限制
当系统处于Doze状态的时候,会对应用进行如下的限制:
(1)暂停网络访问;
(2)系统忽略唤醒类wakelock;
(3)标准的Alarm(包括setExact()和setWindow())将会被推迟到下一个维护阶段。如果需要设置在设备处于低电耗模式时触发的闹钟,可以使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。使用 setAlarmClock() 设置的闹钟将继续正常触发,系统会在这些闹钟触发之前不久退出低电耗模式;
(4)系统不执行wifi扫描;
(5)系统不允许运行同步适配器;
(6)系统不允许运行Job。
二、源码讲解
1、DeviceidleController介绍
Doze模式是由DeviceIdleController这个类进行控制的,它和PowerManagerService等一样都是属于系统服务,并且在SystemServer中进行启动注册。由于它不像PowerMaangerService等系统服务会提供一个代理类诸如PowerManager给应用使用;因此,在理解该类的过程中则需要从SystemServer启动DeviceIdleController开始。相关流程图如下所示
1.1、onStart
在onstart方法中主要做了如下事情:
(1)初始化部分变量;
(2)将配置文件中的白名单应用读取出来存储到列表中;
(3)注册LocalService和Binder供系统使用,如果外界应用想要使用到注册的Binder对象则必须通过反射的方式进行使用。
1.2、onBootPhase
在onBootPhase方法中主要做了如下事情:
(1)首先判断系统Service是否已经启动完毕;
(2)初始化诸如AlarmManager、PowerManager以及ConnectivityService等系统服务;用于设置Alarm,获取wakelock以及获取是否有网络等操作;
(3)初始化lightDoze以及Doze相关Intent,当这两种状态发生变化的时候用于通知系统中其他模块;
(4)初始化广播接收器,其中包括亮灭屏、网络变化、包移除以及电池状态变化等广播,用于改变系统当前所处状态;
(5)更新当前网路状态以及系统状态。
2、核心源码介绍
2.1 相关变量讲解
在正式介绍lightDoze模式和Doze模式下各个状态之间的转换之前,首先需要去了解一下其中所涉及到的一些变量的含义,不然看得过程中总是云里雾里的。
2.1.1 lightDoze相关变量介绍
在DeviceIdleController中定义了如下几个和lightDoze模式相关联的变量值:
boolean mForceIdle;//该变量值主要是通过adb shell方式进行赋值,例如adb shell dumpsys deviceidle force-idle deep/light,让系统强制进入到Doze模式
LIGHT_STATE_ACTIVE = 0;//当前系统处于活动状态,比如亮屏;
LIGHT_STATE_INACTIVE = 1;//当前系统处于非活动状态,比如灭屏;
LIGHT_STATE_PRE_IDLE = 3;//当前系统还有没有完成的任务,需要等待任务完成才能进入到idle状态;
LIGHT_STATE_IDLE = 4;//系统当前处于lightDoze状态,此时网络、wakelock等都会受到限制;
LIGHT_STATE_WAITING_FOR_NETWORK = 5;//当前系统仍然处于lightDoze状态,但是需要等待系统有网络之后才能进入到维护状态;
LIGHT_STATE_IDLE_MAINTENANCE = 6;//系统处于到了维护阶段;
LIGHT_STATE_OVERRIDE = 7;//表示lightDoze状态被覆盖了,开始进入DeepDoze状态
2.1.1 DeepDoze相关变量介绍
在DeviceIdleController中定义了如下几个和deepDoze模式相关联的变量值:
//当前系统处于active状态
private static final int STATE_ACTIVE = 0;
//系统处于inactive状态,也就是灭屏且未充电状态,这个时候系统等待进入deepIdle
private static final int STATE_INACTIVE = 1;
//表示系统刚结束inactive状态,准备进入deepidle状态
private static final int STATE_IDLE_PENDING = 2;
//表示系统正在感应设备是否被移动
private static final int STATE_SENSING = 3;
//表示设备正在定位,并获取当前定位精度
private static final int STATE_LOCATING = 4;
//系统当前处于deepidle状态
private static final int STATE_IDLE = 5;
//系统处于维护状态
private static final int STATE_IDLE_MAINTENANCE = 6;
2.2 代码讲解
对lightDoze和DeepDoze两种模式下不同变量的含义有了清楚的认识之后那么就可以正式开始对系统是如何退出和进入Doze模式的代码分析工作了。首先,我们需要明白Doze模式的退出和进入都是根据系统自身行为来进行控制的,而在前面我们也了解到如果想要进入到lightDoze模式则需要系统灭屏并且未充电,进入DeepDoze模式除了灭屏和未充电之外还需要保持设备静止。因此,我们的入手点就是亮灭屏广播接收器和电池变化广播接受器了。
在亮灭屏广播接收器中会调用到updateInteractivityLocked函数中,在该函数中首先会获取到屏幕状态,然后根据屏幕状态判断是否需要开始进入Doze状态,相关源码如下(省略非必要代码):
void updateInteractivityLocked() {
//从PowerManager中获取当前是否处于可交互状态(亮屏或者屏保状态)
boolean screenOn = mPowerManager.isInteractive();
//如果系统从亮屏状态变为灭屏状态
if (!screenOn && mScreenOn) {
mScreenOn = false;
//如果没有强制进入到Doze状态,
if (!mForceIdle) {
becomeInactiveIfAppropriateLocked();
}
} else if (screenOn) {
mScreenOn = true;
//如果没有强制进入Doze状态并且屏幕没有被锁定则将所有到数据重新初始化并cancel所设置到Alarm以及停止设备是否移动检测
if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
becomeActiveLocked("screen", Process.myUid());
}
}
}
该方式是将系统变为inactive和active的入口而已,真正idle的各个状态切换需要继续看becomeInactiveIfAppropriateLocked函数;相关源码如下:
/**
* 在讲解方法之前首先了解一下下面两个变量:
* (1)mDeepEnabled:是否允许进入到DeepDoze状态;
* (2)mLightEnabled:是否允许进入到lightDoze状态;
* 这两个值都可以通过dump到方式进行修改,其实也就是相当于控制Doze模式到开关
**/
void becomeInactiveIfAppropriateLocked() {
//如果系统灭屏并且没有充电
if ((!mScreenOn && !mCharging) || mForceIdle) {
//系统上一个状态处于可交互状态并且允许进入深度睡眠
if (mState == STATE_ACTIVE && mDeepEnabled) {
//将系统状态设置为不可交互状态
mState = STATE_INACTIVE;
//重新初始化各个标志、取消所设置到相关Alarm以及取消sensing状态、位置状态、motion状态检测
resetIdleManagementLocked();
//设置Alarm,在30min之后检测是否能够进入到Doze模式(idle状态)
scheduleAlarmLocked(mInactiveTimeout, false);
}
//系统上一个状态处于可交互状态,并且允许进入lightDoze状态
if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
//将系统状态设置为不可交互状态
mLightState = LIGHT_STATE_INACTIVE;
//cancel掉和lightDoze相关到Alarm
resetLightIdleManagementLocked();
//设置Alarm,在5min之后检测是否能够进入到lightDoze模式
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
}
}
}
在该函数中,首先判断系统是否处于灭屏且未充电状态,如果是则:
(1)首先判断系统是否满足进入DeepDoze模式,如果满足条件则将DeepDoze相关变量重新初始化并cancel掉原有Alarm,然后设置一个30min后触发的Alarm用于检测系统是否能够进入到DeepDoze状态;
(2)然后还会判断系统是否满足进入到lightDoze模式,如果满足条件则会cancel掉原油lightDoze相关Alarm并设置一个5min后触发到Alarm用于检测系统是否能够进入到lightDoze状态;
从该方法到实现来看,lightDoze的进入时间肯定早于DeepDoze的进入时间,所以接下来首先对lightDoze相关代码进行讲解。
2.2.1 lightDoze代码讲解
在系统灭屏5min之后,进入到lightDoze模式的Alarm将会被触发,并在AlarmManagerService中回调到mLightAlarmListener中,继而调用到stepLightIdleStateLocked函数中,stepLightIdleStateLocked函数相关源码如下(省略掉部分无关紧要代码):
/**
* 该函数中涉及到了很多相关时间,可以通过adb shell dumpsys
* deviceidle进行查看
**/
void stepLightIdleStateLocked(String reason) {
if (mLightState == LIGHT_STATE_OVERRIDE) {
return;
}
switch (mLightState) {
case LIGHT_STATE_INACTIVE:
//维护时间段的预算时间1min
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
//lightidle状态的最小时间段5min
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
//开始维护的时间
mMaintenanceStartTime = 0;
//如果当前有正在进行的任务(Alarm、job以及wakelock)
if (!isOpsInactiveLocked()) {
//表示当前有任务需要完成才能进入到lightidle状态
mLightState = LIGHT_STATE_PRE_IDLE;
//设置10min后触发到Alarm
scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);
break;
}
case LIGHT_STATE_PRE_IDLE:
case LIGHT_STATE_IDLE_MAINTENANCE:
//如果为true,当前处于maintenance状态即将退出该状态
if (mMaintenanceStartTime != 0) {
//当前处于维护状态的实际时间
long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
//如果处于维护状态的实际时间小于1min,则下次的维护时间段将加上小于1min的时间段
if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
//如果大于等于1min,则下次的维护时间段将减去大于1min的时间段
} else {
mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
}
}
//重新初始化维护开始时间
mMaintenanceStartTime = 0;
//设置退出lightidle的alarm
scheduleLightAlarmLocked(mNextLightIdleDelay);
//保持lightidle状态的最大时间为15min
mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
(long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
//保持lightidle的时间最小为5min
if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
}
//表示系统当前处于lightidle状态
mLightState = LIGHT_STATE_IDLE;
//系统处于idle状态,开始对wakelock、job以及网络等资源进行限制
mGoingIdleWakeLock.acquire();
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) {
mActiveIdleOpCount = 1;
//系统即将进入到维护状态,因此需要持有wakelock锁防止系统休眠
mActiveIdleWakeLock.acquire();
//记录维护的开始时间
mMaintenanceStartTime = SystemClock.elapsedRealtime();
//系统处于维护状态的时间最大为5min最小为1min
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;
}
//设置定时alarm以退出维护状态
scheduleLightAlarmLocked(mCurIdleBudget);
//标识当前系统处于维护阶段
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
//释放掉wakelock、job以及网络相关限制
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
} else {
//表示当前需要等待网络才能进入维护阶段
scheduleLightAlarmLocked(mNextLightIdleDelay);
mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
}
break;
}
}
该方法实现了lighDoze模式相关各个状态之间的相互转换,以及处于维护阶段和lightIdle状态的时间计算。各个状态之间关系的状态转换图如下:
处于维护阶段的预算时间(mCurIdleBudget)最大值为5min最小值为1min,默认值为1min。如果实际处于维护阶段的时间(duration)不是1min,那么下次处于维护阶段的预算时间将加上1min-duration的值。
处于lightidle状态时间(mNextLightIdleDelay)的最大值为15min最小值为5min,默认值为5min。下次处于lightidle状态时间=上次处于lightidle状态时间*2并与15min取最小值。
也就是随着处于lightidle状态的时间越来越长,那么处于lightIdle状态的时间也会越来越长但是最大值为15min,而处于maintenance状态的时间基本就是在1min左右。
2.2.2 资源限制、放开代码讲解
当进入退出idle状态的时候会对系统资源诸如wakelock、alarm以及网络等进行限制和放开,在该类中通过调用对应模块所提供的api或者通过广播的方式通知到相应的模块,具体的资源限制实现并没有在该类中。代码(省略掉部分代码)如下:
@Override public void handleMessage(Message msg) {
switch (msg.what) {
//将列表中的白名单应用信息写入到文件中
case MSG_WRITE_CONFIG: {
handleWriteConfigFile();
} break;
//idle和lightidle状态资源处理
case MSG_REPORT_IDLE_ON:
case MSG_REPORT_IDLE_ON_LIGHT: {
final boolean deepChanged;
final boolean lightChanged;
//用于通知PowerManagerService现在系统已经处于idle状态,并限制不符合要求的wakelock
if (msg.what == MSG_REPORT_IDLE_ON) {
deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
} else {
deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
}
try {
//通知NetwaorkPolicyManagerService现在系统已经处于idle状态,需要重新更新规则
mNetworkPolicyManager.setDeviceIdleMode(true);
} catch (RemoteException e) {
}
//发送广播给需要知道Doze模式状态改变的模块
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
}
//资源限制已经处理完成,释放掉wakelock锁
mGoingIdleWakeLock.release();
} break;
//退出idle状态进入到maintenance阶段
case MSG_REPORT_IDLE_OFF: {
//放开wakelock资源限制
final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
try {
//放开网路限制
mNetworkPolicyManager.setDeviceIdleMode(false);
} catch (RemoteException e) {
}
//发送广播给需要知道Doze模式状态改变的模块
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;
//进入到active状态
case MSG_REPORT_ACTIVE: {
//获取进入active的原因以及唤醒系统的uid
String activeReason = (String)msg.obj;
int activeUid = msg.arg1;
//放开所有的资源限制并发送doze状态变化广播
final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
try {
mNetworkPolicyManager.setDeviceIdleMode(false);
mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
activeReason, activeUid);
} catch (RemoteException e) {
}
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
}
} break;
.................
}
对于lightDoze模式中各个状态的切换流程以及是如何通知到系统对wakelock、job以及网络等资源进行管控的大致代码是讲解的差不多。大致的文字叙述流程总结如下:
(1)当手机处于灭屏和未充电并且当前系统处于active状态的时候,则会将系统状态变更为inactive状态,同时将数据重新初始化为原始数据并清空上次设置的相关Alarm,继而设置一个5min中的alarm来判断系统是否可以进入到lightidle状态;
(2)当系统处于灭屏状态5min,并且在这之间没有亮灭屏以及充电等操作,那么系统就会通过调用stepLightIdleStateLocked方法来判断系统是否可以进入到lightidle状态;
(3)首先会初始化处于mantenance状态和ligthidle状态的时间,然后会判断当前是否有alarm、job等活动正在进行,如果存在则会10min(设置Alarm)之后再进入到lightidle状态,否则直接进入到lightidle状态;
(4)当进入到lightidle状态之后,会通过发送广播和调用相关服务api的方式来进行通知,各个模块在接收到通知之后会对相关的资源做出一定的限制;
(5)如果当前时间是进入到mantenance状态,但是并没有网络连接,那么系统就会进入到等到网络连接状态(其实还是lightidle状态,只是将mLightstate变更为了LIGHT_STATE_WAITING_FOR_NETWORK),在mNextLightIdleDelay(处于idle状态到时间)之后不管是否有网络连接都会进入到mantenance状态;
(6)放开在lightidle状态所限制到资源,通知各个模块的方式也是调用对应模块的api和发送广播,当然在系统处于mantenance状态的时候会持有wakelock锁以防止系统休眠。当维护状态的时间到达之后又会走到步骤(4)以此往复。
2.2.2 deepDoze代码讲解
deepDoze相对于lightDoze而言,整体的状态变化流程大致相似只是增加了位置变化监听而已。
在讲解进入到lightDoze状态过程中讲到了方法becomeInactiveIfAppropriateLocked,在该方法中首先会判断系统是否处于灭屏并且未充电,如果满足条件则会先判断系统是否满足进入到deepDoze状态然后再判断是否能够进入到lightDoze状态,而当系统满足进入到deepDoze的条件时,首先会将相关变量重新初始化并cancel掉上次设置的alarm并重新设置一个30min后执行的alarm,当相关alarm时间到达之后会调用到方法stepIdleStateLocked,因此下面则围绕该方法进行讲解。
在讲解该方法之前首先需要了解一下关于设备位置变化监听相关知识讲解。在Android中有四种方式来获取设备的位置相关信息,下面只介绍当前所使用到的两种方式(通常也是这两种方式配合使用来获取设备的地理位置信息):
(1)通过GPS的方式获取位置的经纬度信息,该方法获取的地理位置信息精度高,但是速度慢并且只能在户外使用;
(2)通过移动网路基站或者wifi来获取地理位置信息,该方法定位速度快但是精确度不高;
相关源码(省略部分源码)如下:
void stepIdleStateLocked(String reason) {
final long now = SystemClock.elapsedRealtime();
//如果在1h之内存在唤醒系统的Alarm,则重置进入到deepIdle状态的时间
if ((now+mConstants.MIN_TIME_TOLARM) > mAlarmManager.getNextWakeFromIdleTime()) {
if (mState != STATE_ACTIVE) {
becomeActiveLocked("alarm", Process.myUid());
becomeInactiveIfAppropriateLocked();
}
return;
}
switch (mState) {
case STATE_INACTIVE:
//注册sensor以监听设备是否发生了位置变化
startMonitoringMotionLocked();
//系统处于STATE_IDLE_PENDING状态为30min
scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
//初始化处于维护状态时间为5min
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
//初始化处于deepidle状态的时间1h
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
//当前系统处于STATE_IDLE_PENDING状态
mState = STATE_IDLE_PENDING;
break;
case STATE_IDLE_PENDING:
//当前处于STATE_SENSING状态,用于检测当前设备是否有移动
mState = STATE_SENSING;
//设置一个4min后到alarm检测当前是否仍然处于STATE_SENSING状态,如果是则会再次调用becomeInactiveIfAppropriateLocked方法(不过好像并没有什么用处)
scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
//取消监听设备位置更新以及GPS位置更新
cancelLocatingLocked();
mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
//通过AnyMotionDetector检测设备是否移动,如果没有移动则会进入到STATE_LOCATING状态
mAnyMotionDetector.checkForAnyMotion();
break;
case STATE_SENSING:
//清除掉sensing状态相关的alarm
cancelSensingTimeoutAlarmLocked();
//用于获取当前定位精度
mState = STATE_LOCATING;
//获取定位精度的时间为30s,如果30s之后没有获取到定位精度或者获取到的定位精度小于20m并且没有移动则会直接进入到deepIdle状态
scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
//用系统会选取当前最适合定位的方式进行定位(可能只会选择一种方式进行定位,可能多种方式混合进行定位)
if (mLocationManager != null
&& mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
mLocationManager.requestLocationUpdates(mLocationRequest,
mGenericLocationListener, mHandler.getLooper());
mLocating = true;
} else {
mHasNetworkLocation = false;
}
//使用GPS进行定位
if (mLocationManager != null
&& mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
mHasGps = true;
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
mGpsLocationListener, mHandler.getLooper());
mLocating = true;
} else {
mHasGps = false;
}
//如果当前没有任何定位则直接停止检测并进入到deepIdle状态,否在在30s之后再停止检测
if (mLocating) {
break;
}
//停止定位和设备是否处于运动状态检测,但是并没有停止sensing的检测
case STATE_LOCATING:
cancelAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.stop();
//进入到idle状态
case STATE_IDLE_MAINTENANCE:
//在1h之后退出deepidle状态
scheduleAlarmLocked(mNextIdleDelay, true);
//下次处于deepidle状态的时间是上次处于idle状态时间的2倍
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
//处于deepidle状态的最大时间为6h
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
//处于deepidle状态的最小时间为1h
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
}
//当前处于deepidle状态
mState = STATE_IDLE;
//lightidle状态已经被deepidle状态覆盖了,因此清除掉lightidle相关的alarm
if (mLightState != LIGHT_STATE_OVERRIDE) {
mLightState = LIGHT_STATE_OVERRIDE;
cancelLightAlarmLocked();
}
//限制系统wakelock、job、alarm以及网络等资源
mGoingIdleWakeLock.acquire();
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
//系统进入到mantenance状态
case STATE_IDLE:
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
//处于mantenance状态为5min
scheduleAlarmLocked(mNextIdlePendingDelay, false);
mMaintenanceStartTime = SystemClock.elapsedRealtime();
//下次处于维护状态的时间为上次的2倍,但是最大值为10min
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
//处于维护状态的最小时间为5min
if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
}
//当前系统处于维护状态
mState = STATE_IDLE_MAINTENANCE;
//释放由于处于idle状态而被限制的相关资源
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}
呼~,终于把进入到deepIdle的整个流程走完了,当然里面还涉及到了一些方法在后面还需要继续分析,在这里对deepIdle的整个大致流程简单的总结一下。
(1)在系统灭屏之后并且手机没有充电那么系统将清除上次进入到deepIdle相关的数据,并且设置一个30min之后的Alarm来检测是否可以进入到deepIdle状态;
(2)如果系统灭屏了30min并且没有充电,则会调用到stepIdleStateLocked方法判断是否可以进入到deepIdle状态;
(3)如果在1h以内系统中存在可以唤醒系统的Alarm则会直接return当前方法并重新初始化相关条件进入deepDoze状态;如果没有则会继续走以下步骤;
(4)进入到STATE_IDLE_PENDING状态,并设置Sensor传感器检测,用于判断手机是否被移动,同时设置一个30min后触发的alarm进入到STATE_SENSING状态;
(5)在STATE_SENSING状态检测设备是否被移动,如果被移动了则会将当前状态变更为active状态,并重新调用becomeInactiveIfAppropriateLocked方法,从头开始进行状态检测;如果没有被移动则会进入到STATE_LOCATING状态;
(6)在STATE_LOCATING状态系统通过LocationManager用于检测移动网络定位和gps定位的精度是否在20m以内,如果精度在20m以内并且在步骤(5)也没有检测到设备位置发生了变化,则清除移动数据网络定位和gps定位以及停止设备移动检测;并在接下来进入到deepIdle状态;
(7)进入到deepidle状态,并计算下一次处于deepIdle的时间,同时显示job、wakelock、alarm以及网络等资源限制。如果没有其它行为打断这种状态,那么在设定的时间之后退出该状态进入到mantenance状态;
(8)进入到mantenance状态,并释放掉因为进入到deepIdle状态而被限制的资源,在设定的时间之后退出维护状态并重复步骤(7)。
随着处于deepIdle的次数越来越多,那么处于deepIdle状态的时间也会越来越长,最长为6h;处于维护阶段的时间也会越来越长,最长为10min。而当sensor检测到设备被移动了之后就会打破这种平衡,并返回到步骤(1)重新进行状态转换。也就是说当检测到设备运动到时候就会打破所有到平衡已经状态检测过程,并重新开始。
2.2.3 运动检测代码讲解
当系统处于STATE_SENSING状态的时候会调用AnyMotionDetector中的checkForAnyMotion方法,最后会回调到onAnyMotionResult方法中,代码如下:
public void onAnyMotionResult(int result) {
//如果返回-1,也就是不清楚设备是否移动,则会清除在sensing状态所设置到alarm
if (result != AnyMotionDetector.RESULT_UNKNOWN) {
synchronized (this) {
cancelSensingTimeoutAlarmLocked();
}
}
//如果设备被移动了或者不知道是否被移动,则都会被当作设备被移动处理,具体的处理细节后面会讲到
if ((result == AnyMotionDetector.RESULT_MOVED) ||
(result == AnyMotionDetector.RESULT_UNKNOWN)) {
synchronized (this) {
handleMotionDetectedLocked(mConstants.INACTIVE_TIMEOUT, "non_stationary");
}
//如果设备没有被移动,比如一直被放在桌子上面
} else if (result == AnyMotionDetector.RESULT_STATIONARY) {
//判断当前系统所处的状态
if (mState == STATE_SENSING) {
synchronized (this) {
mNotMoving = true;
//继续保持系统状态并将系统转换为STATE_LOCATING状态
stepIdleStateLocked("s:stationary");
}
} else if (mState == STATE_LOCATING) {
synchronized (this) {
mNotMoving = true;
//如果没有移动设备,并且location位置检测精度在20m以内
if (mLocated) {
stepIdleStateLocked("s:stationary");
}
}
}
}
}
2.2.4 设备如果被移动代码讲解
不论在检测设备检测到设备被移动还是所设置到sensor检测到了设备被移动了,最终都会调用到handleMotionDetectedLocked方法中,相关代码如下:
void handleMotionDetectedLocked(long timeout, String type) {
boolean becomeInactive = false;
//判断当前系统是否处于active状态
if (mState != STATE_ACTIVE) {
//如果设备被移动了并不会影响到lightDoze状态,因此需要排除lightDoze状态
boolean lightIdle = mLightState == LIGHT_STATE_IDLE
|| mLightState == LIGHT_STATE_WAITING_FOR_NETWORK
|| mLightState == LIGHT_STATE_IDLE_MAINTENANCE;
//如果当前不处于lightDoze状态,则会释放掉因deepDoze状态而被限制到资源
if (!lightIdle) {
scheduleReportActiveLocked(type, Process.myUid());
}
//重新初始化数据
mState = STATE_ACTIVE;
mInactiveTimeout = timeout;
mCurIdleBudget = 0;
mMaintenanceStartTime = 0;
becomeInactive = true;
}
//如果已经进入到了deepDoze状态则需要重新初始化lightDoze状态为active状态
if (mLightState == LIGHT_STATE_OVERRIDE) {
mLightState = LIGHT_STATE_ACTIVE;
becomeInactive = true;
}
//重新设置alarm以进入到doze状态
if (becomeInactive) {
becomeInactiveIfAppropriateLocked();
}
}
2.2.5 获取定位精度相关代码讲解
当系统处于STATE_LOCATING状态的时候会注册移动数据网络定位以及gps定位相关listener到LocationManagerService中,当有位置变化的时候会回调到相关listener中,它们的实现方式类似,这里只讲解移动数据网络定位回调函数,代码如下:
void receivedGenericLocationLocked(Location location) {
//当前设备不处于STATE_LOCATING状态,则取消掉相关alarm并直接返回
if (mState != STATE_LOCATING) {
cancelLocatingLocked();
return;
mLastGenericLocation = new Location(location);
//如果定位的精度大于20m并且存在gps定位则直接返回,等待30s之后自动进入到deepDoze状态
if (location.getAccuracy() > mConstants.LOCATION_ACCURACY && mHasGps) {
return;
}
mLocated = true;
//如果在STATE_SENSING没有检测到设备移动下一步则让系统进入到deepIdle状态
if (mNotMoving) {
stepIdleStateLocked("s:location");
}
}
2、退出Doze状态源码讲解
在文章开篇讲到,如果系统要进入到Doze状态必须满足三个条件,(1)灭屏(2)未充电(3)设备处于静止不动;因此想要退出Doze模式只需要破坏这三种条件之一也就可以了。当设备处于非静止状态是如何破坏deepDoze相关状态在讲解deepDoze相关代码已经讲解到了,因此这里只需要关注条件(1)和(2)两种情况就行了。
在文章开篇已经讲到了,在onBootPhase函数中注册了亮灭屏广播以及电池变化接收广播以监听系统亮灭屏以及是否充电状态,当监听到系统亮屏或者充电到时候都会调用到becomeActiveLocked方法用于退出Doze模式;相关源码如下:
void becomeActiveLocked(String activeReason, int activeUid) {
if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
//放开因Doze模式而被限制到wakelock、alarm、job以及网络等资源
scheduleReportActiveLocked(activeReason, activeUid);
//将状态变更为active状态
mState = STATE_ACTIVE;
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
mCurIdleBudget = 0;
mMaintenanceStartTime = 0;
//重新初始化相关变量并且取消掉注册到Alarm以及相关listener
resetIdleManagementLocked();
resetLightIdleManagementLocked();
}
}
三、总结
历经一周左右的时间,系统是如何从active状态进入到lightDoze模式,又是如何在lightDoze模式的基础上进入到Doze模式的整体流程基本上来说是理完了,当然里面还有很多细节部分并没有讲到比如说是如何添加Doze白名单的,都还需要去慢慢琢磨才行。文章中可能存在很多理解不对的地方或者没有叙述清楚的地方,还希望大家多多指正。
官方文档:developer.android.com/training/mo…
参考博客:blog.csdn.net/FightFightF…
google源码:androidxref.com/