參考:http://blog.csdn.net/gaugamela/article/details/52981984

在Android M中。Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。

在该状态,后台仅仅有部分任务被同意执行,其他任务都被强制停止。

在之前的博客中分析过Doze模式。就是device idle状态。可能有的地方分析的不是非常具体,如今在android7.0上又一次分析下。

一、基本原理

Doze模式能够简单概括为:

 若推断用户在连续的一段时间内没有使用手机。就延缓终端中APP后台的CPU和网络活动,以达到降低电量消耗的目的。

上面这张图比較经典,基本上说明了Doze模式的含义。

图中的横轴表示时间,红色部分表示终端处于唤醒的执行状态。绿色部分就是Doze模式定义的休眠状态。

从图中的描写叙述,我们能够看到:假设一个用户停止充电(on battery: 利用电池供电),关闭屏幕(screen off),手机处于精巧状态(stationary: 位置没有发生相对移动)。保持以上条件一段时间之后,终端就会进入Doze模式。

一旦进入Doze模式,系统就降低(延缓)应用对网络的訪问、以及对CPU的占用,来节省电池电量。

如图所看到的。Doze模式还定义了maintenance window。

在maintenance window中,系统同意应用完毕它们被延缓的动作。即能够使用CPU资源及訪问网络。

 从图中我们能够看出,当进入Doze模式的条件一直满足时,Doze模式会定期的进入到maintenance window。但进入的间隔越来越长。

 通过这样的方式,Doze模式能够使终端处于较长时间的休眠状态。

须要注意的是:一旦Doze模式的条件不再满足,即用户充电、或打开屏幕、或终端的位置发生了移动,终端就恢复到正常模式。

 因此。当用户频繁使用手机时,Doze模式差点儿是没有什么实际用处的。

详细来讲。当终端处于Doze模式时。进行了下面操作:

1、暂停网络訪问。

2、系统忽略全部的WakeLock。

3、标准的AlarmManager alarms被延缓到下一个maintenance window。

 但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock时,alarms定义事件仍会启动。


 在这些alarms启动前,系统会短暂地退出Doze模式。

4、系统不再进行WiFi扫描。

5、系统不同意sync adapters执行。

6、系统不同意JobScheduler执行。

另外我在还有一篇博客中:http://blog.csdn.net/kc58236582/article/details/50554174也具体介绍了Doze模式。能够參考下,上面有一些命令使用等。

二、DeviceIdleController

Android中的Doze模式主要由DeviceIdleController来控制。

public class DeviceIdleController extends SystemService
implements AnyMotionDetector.DeviceIdleCallback

能够看出DeviceIdleController继承自SystemService,是一个系统级的服务。

同一时候,继承了AnyMotionDetector定义的接口。便于检測到终端位置变化后进行回调。

2.1 DeviceIdleController的初始化

接下来我们看看它的初始化过程。

private void startOtherServices() {
.........
mSystemServiceManager.startService(DeviceIdleController.class);
.........
}

如上代码所看到的,SystemServer在startOtherServices中启动了DeviceIdleController,将先后调用DeviceIdleController的构造函数和onStart函数。

构造函数

public DeviceIdleController(Context context) {
super(context);
//deviceidle.xml用于定义idle模式也能正常工作的非系统应用
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}

DeviceIdleController的构造函数比較简单,就是在创建data/system/deviceidle.xml相应的file文件。同一时候创建一个相应于后台线程的handler。这里的deviceidle.xml能够在设置中的电池选项那里。有电池优化,能够将一些应用放到白名单中,调用DeviceIdleController的addPowerSaveWhitelistApp方法。最后会写入deviceidle.xml文件,然后在下次开机的时候DeviceIdleController会又一次读取deviceidle.xml文件然后放入白名单mPowerSaveWhitelistUserApps中。

onStart函数

public void onStart() {
final PackageManager pm = getContext().getPackageManager(); synchronized (this) {
//读取配置文件。推断Doze模式是否同意被开启
mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes); //分析PKMS时提到过,PKMS扫描系统文件夹的xml,将形成SystemConfig
SystemConfig sysConfig = SystemConfig.getInstance(); //获取除了device Idle模式外,都能够执行的系统应用白名单
ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
for (int i=0; i<allowPowerExceptIdle.size(); i++) {
String pkg = allowPowerExceptIdle.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
} //获取device Idle模式下,也能够执行的系统应用白名单
ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
for (int i=0; i<allowPower.size(); i++) {
String pkg = allowPower.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
// These apps are on both the whitelist-except-idle as well
// as the full whitelist, so they apply in all cases.
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
mPowerSaveWhitelistApps.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIds.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
} //Constants为deviceIdleController中的内部类,继承ContentObserver
//监控数据库变化,同一时候得到Doze模式定义的一些时间间隔
mConstants = new Constants(mHandler, getContext().getContentResolver()); //解析deviceidle.xml,并将当中定义的package相应的app,增加到mPowerSaveWhitelistUserApps中
readConfigFileLocked(); //将白名单的内容给AlarmManagerService和PowerMangerService
//比如:DeviceIdleController推断开启Doze模式时。会通知PMS
//此时除去白名单相应的应用外,PMS会将其他全部的WakeLock设置为Disable状态
updateWhitelistAppIdsLocked(); //下面的初始化,都是如果眼下处在进入Doze模式相反的条件上
mNetworkConnected = true;
mScreenOn = true;
// Start out assuming we are charging. If we aren't, we will at least get
// a battery update the next time the level drops.
mCharging = true; //Doze模式定义终端初始时为ACTIVE状态
mState = STATE_ACTIVE;
//屏幕状态初始时为ACTIVE状态
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
} //公布服务
//BinderService和LocalService均为DeviceIdleController的内部类
mBinderService = new BinderService();
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
publishLocalService(LocalService.class, new LocalService());
}

除去公布服务外,DeviceIdleController在onStart函数中。主要是读取配置文件更新自己的变量,思路比較清晰。

在这里我们仅跟进一下updateWhitelistAppIdsLocked函数:

private void updateWhitelistAppIdsLocked() {
//构造出除去idle模式外,可执行的app id数组 (可觉得是系统和普通应用的集合)
//mPowerSaveWhitelistAppsExceptIdle从系统文件夹下的xml得到
//mPowerSaveWhitelistUserApps从deviceidle.xml得到,或调用接口增加;
//mPowerSaveWhitelistExceptIdleAppIds并未使用
mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds); //构造不受Doze限制的app id数组 (可觉得是系统和普通应用的集合)
//mPowerSaveWhitelistApps从系统文件夹下的xml得到
//mPowerSaveWhitelistAllAppIds并未使用
mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds); //构造不受Doze限制的app id数组(仅普通应用的集合)、
//mPowerSaveWhitelistUserAppIds并未使用
mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds); if (mLocalPowerManager != null) {
...........
//PMS拿到的是:系统和普通应用组成的不受Doze限制的app id数组
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
} if (mLocalAlarmManager != null) {
..........
//AlarmManagerService拿到的是:普通应用组成的不受Doze限制的app id数组
mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
}
}

updateWhitelistAppIdsLocked主要是将白名单交给PMS和AlarmManagerService。

注意Android区分了系统应用白名单、普通应用白名单等,因此上面进行了一些合并操作。这里我们有没有发现,systemConfig的app不会增加alarm的白名单,而在Settings中电池那边设置的白名单,会增加Power wakelock的白名单。

onBootPhase函数

与PowerManagerService一样,DeviceIdleController在初始化的最后一个阶段须要调用onBootPhase函数:

public void onBootPhase(int phase) {
//在系统PHASE_SYSTEM_SERVICES_READY阶段,进一步完毕一些初始化
if (phase == PHASE_SYSTEM_SERVICES_READY) {
synchronized (this) {
//初始化一些变量
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
.............. mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
//依据配置文件。利用SensorManager获取相应的传感器,保存到mMotionSensor中
.............. //假设配置文件表明:终端须要预获取位置信息
//则构造LocationRequest
if (getContext().getResources().getBoolean(
com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
mLocationManager = (LocationManager) getContext().getSystemService(
Context.LOCATION_SERVICE);
mLocationRequest = new LocationRequest()
.setQuality(LocationRequest.ACCURACY_FINE)
.setInterval(0)
.setFastestInterval(0)
.setNumUpdates(1);
} //依据配置文件,得到角度变化的门限
float angleThreshold = getContext().getResources().getInteger(
com.android.internal.R.integer.config_autoPowerModeThresholdAngle) / 100f;
//创建一个AnyMotionDetector。同一时候将DeviceIdleController注冊到当中
//当AnyMotionDetector检測到手机变化角度超过门限时,就会回调DeviceIdleController的接口
mAnyMotionDetector = new AnyMotionDetector(
(PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
mHandler, mSensorManager, this, angleThreshold); //创建两个经常使用的Intent,用于通知Doze模式的变化
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND); //监听ACTION_BATTERY_CHANGED广播(电池信息发生改变)
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter); //监听ACTION_PACKAGE_REMOVED广播(包被移除)
filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mReceiver, filter); //监听CONNECTIVITY_ACTION广播(连接状态发生改变)
filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getContext().registerReceiver(mReceiver, filter); //又一次将白名单信息交给PowerManagerService和AlarmManagerService
//这个工作在onStart函数中,已经调用updateWhitelistAppIdsLocked进行过了
//到onBootPhase时,又一次进行一次,可能:一是为了保险;二是,其他进程可能调用接口,更改了相应数据,于是进行更新
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); //监听屏幕显示相关的变化
mDisplayManager.registerDisplayListener(mDisplayListener, null); //更新屏幕显示相关的信息
updateDisplayLocked();
}
//更新连接状态相关的信息
updateConnectivityState(null);
}
}

从代码能够看出。onBootPhase方法:

 主要创建一些本地变量,然后依据配置文件初始化一些传感器,同一时候注冊了一些广播接收器和回到接口,

 最后更新屏幕显示和连接状态相关的信息。

2.2 DeviceIdleController的状态变化

充电状态的处理

对于充电状态,在onBootPhase函数中已经提到。DeviceIdleController监听了ACTION_BATTERY_CHANGED广播:

............
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
...........

我们看看receiver中相应的处理:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
.........
case Intent.ACTION_BATTERY_CHANGED: {
synchronized (DeviceIdleController.this) {
//从广播中得到是否在充电的消息
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0);
}
} break;
}
}
}。

依据上面的代码。能够看出当收到电池信息改变的广播后。DeviceIdleController将得到电源是否在充电的消息,然后调用updateChargingLocked函数进行处理。

void updateChargingLocked(boolean charging) {
.........
if (!charging && mCharging) {
//从充电状态变为不充电状态
mCharging = false;
//mForceIdle值一般为false,是通过dumpsys命令将mForceIdle改成true的
if (!mForceIdle) {
//推断是否进入Doze模式
becomeInactiveIfAppropriateLocked();
}
} else if (charging) {
//进入充电状态
mCharging = charging;
if (!mForceIdle) {
//手机退出Doze模式
becomeActiveLocked("charging", Process.myUid());
}
}
}

becomeInactiveIfAppropriateLocked函数是開始进入Doze模式。而becomeActiveLocked是退出Doze模式。

显示状态处理

DeviceIdleController中注冊了显示变化的回调

                mDisplayManager.registerDisplayListener(mDisplayListener, null);

回调会调用updateDisplayLocked函数

    private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
@Override public void onDisplayAdded(int displayId) {
} @Override public void onDisplayRemoved(int displayId) {
} @Override public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
synchronized (DeviceIdleController.this) {
updateDisplayLocked();
}
}
}
};

updateDisplayLocked函数和更新充电状态的函数updateChargingLocked类似

    void updateDisplayLocked() {
mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
// We consider any situation where the display is showing something to be it on,
// because if there is anything shown we are going to be updating it at some
// frequency so can't be allowed to go into deep sleeps.
boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;
if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
if (!screenOn && mScreenOn) {
mScreenOn = false;
if (!mForceIdle) {//開始进入Doze模式
becomeInactiveIfAppropriateLocked();
}
} else if (screenOn) {//屏幕点亮,退出Doze模式
mScreenOn = true;
if (!mForceIdle) {
becomeActiveLocked("screen", Process.myUid());
}
}
}

becomeActiveLocked函数退出Doze模式

我们先来看看becomeActiveLocked函数

//activeReason记录的终端变为active的原因
void becomeActiveLocked(String activeReason, int activeUid) {
...........
if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
............
//1、通知PMS等Doze模式结束
scheduleReportActiveLocked(activeReason, activeUid); //更新DeviceIdleController本地维护的状态
//在DeviceIdleController的onStart函数中,我们已经知道了
//初始时。mState和mLightState均为Active状态
mState = STATE_ACTIVE;//state是指设备通过传感器推断进入idle
mLightState = LIGHT_STATE_ACTIVE;//mLight是背光的状态 mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
mCurIdleBudget = 0;
mMaintenanceStartTime = 0; //2、重置一些事件
resetIdleManagementLocked();
resetLightIdleManagementLocked(); addEvent(EVENT_NORMAL);
}
}

scheduleReportActiveLocked函数就是发送MSG_REPORT_ACTIVE消息

void scheduleReportActiveLocked(String activeReason, int activeUid) {
//发送MSG_REPORT_ACTIVE消息
Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
mHandler.sendMessage(msg);
}

我们再看下消息的处理。主要调用了PowerManagerService的setDeviceIdleMode函数来退出Doze状态,然后又一次更新wakelock的enable状态, 以及通知NetworkPolicyManagerService不再限制应用上网,还有发送Doze模式改变的广播。

.........
case MSG_REPORT_ACTIVE: {
.........
//通知PMS Doze模式结束,
//于是PMS将一些Doze模式下,disable的WakeLock又一次enable
//然后调用updatePowerStateLocked函数更新终端的状态
final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false); try {
//通过NetworkPolicyManagerService更改Ip-Rule,不再限制终端应用上网
mNetworkPolicyManager.setDeviceIdleMode(false);
//BSS做好相应的记录
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);
}
}
........

resetIdleManagementLocked函数就是取消alarm,检測等。

void resetIdleManagementLocked() {
//复位一些状态变量
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
mNextLightIdleDelay = 0; //停止一些工作,主要是位置检測相关的
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
stopMonitoringMotionLocked();
mAnyMotionDetector.stop();
}

becomeInactiveIfAppropriateLocked函数開始进入Doze模式

becomeInactiveIfAppropriateLocked函数就是我们開始进入Doze模式的第一个步骤。以下我们就具体分析这个函数

void becomeInactiveIfAppropriateLocked() {
.................
//屏幕熄灭。未充电
if ((!mScreenOn && !mCharging) || mForceIdle) {
// Screen has turned off; we are now going to become inactive and start
// waiting to see if we will ultimately go idle.
if (mState == STATE_ACTIVE && mDeepEnabled) {
mState = STATE_INACTIVE;
...............
//重置事件
resetIdleManagementLocked(); //開始检測能否够进入Doze模式的Idle状态
//若终端没有watch feature, mInactiveTimeout时间为30min
scheduleAlarmLocked(mInactiveTimeout, false);
...............
} if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
mLightState = LIGHT_STATE_INACTIVE;
.............
resetLightIdleManagementLocked();//重置事件
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
}
}
}

要进入Doze流程,就是调用这个函数,首先要保证屏幕灭屏然后没有充电。这里还有mDeepEnable和mLightEnable前面说过是在配置中定义的。一般默认是关闭(也就是不开Doze模式)。这里mLightEnabled是相应禁止wakelock持锁的。禁止网络。而mDeepEnabled相应是检測设备是否精巧,除了禁止wakelock、禁止网络、还会机制alarm。

light idle模式

我们先看light idle模式,这个模式下、会禁止网络、wakelock,可是不会禁止alarm。

我们先看scheduleLightAlarmLocked函数。这里设置了一个alarm,delay是5分钟。

到时间后调用mLightAlarmListener回调。

    void scheduleLightAlarmLocked(long delay) {
if (DEBUG) Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + ")");
mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}

mLightAlarmListener就是进入lightidle。调用stepLightIdleStateLocked函数

    private final AlarmManager.OnAlarmListener mLightAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (DeviceIdleController.this) {
stepLightIdleStateLocked("s:alarm");
}
}
};

我们来看stepLightIdleStateLocked函数,这个函数会处理mLightState不同状态,会依据不同状态。然后设置alarm。到时间后继续处理下个状态。

到LIGHT_STATE_IDLE_MAINTENANCE状态处理时,会发送MSG_REPORT_IDLE_ON_LIGHT。

这个消息的处理会禁止网络、禁止wakelock。

然后到LIGHT_STATE_WAITING_FOR_NETWORK。会先退出Doze状态(这个时候网络、wakelock恢复)。然后设置alarm,alarm时间到后,还是在LIGHT_STATE_IDLE_MAINTENANCE状态。和之前一样(禁止网络、wakelock)。仅仅是设置的alarm间隔会越来越大。也就是仅仅要屏幕灭屏后,时间越长。

设备会隔越来越长的时间才会退出Doze状态。这也符合一个实际情况,可是会有一个上限值。

    void stepLightIdleStateLocked(String reason) {
if (mLightState == LIGHT_STATE_OVERRIDE) {
// If we are already in deep device idle mode, then
// there is nothing left to do for light mode.
return;
} EventLogTags.writeDeviceIdleLightStep(); switch (mLightState) {
case LIGHT_STATE_INACTIVE:
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
// Reset the upcoming idle delays.
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
mMaintenanceStartTime = 0;
if (!isOpsInactiveLocked()) {
// We have some active ops going on... give them a chance to finish
// before going in to our first idle.
mLightState = LIGHT_STATE_PRE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);//设置alarm,时间到后到下个步骤
break;
}
// Nothing active, fall through to immediately idle.
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) {
// We didn't use up all of our minimum budget; add this to the reserve.
mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
} else {
// We used more than our minimum budget; this comes out of the reserve.
mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
}
}
mMaintenanceStartTime = 0;
scheduleLightAlarmLocked(mNextLightIdleDelay);
mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
(long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
}
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
mLightState = LIGHT_STATE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
addEvent(EVENT_LIGHT_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);//发送消息,这个消息处理就会关闭网络,禁止wakelock
break;
case LIGHT_STATE_IDLE:
case LIGHT_STATE_WAITING_FOR_NETWORK:
if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
mMaintenanceStartTime = SystemClock.elapsedRealtime();
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;
}
scheduleLightAlarmLocked(mCurIdleBudget);
if (DEBUG) Slog.d(TAG,
"Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
addEvent(EVENT_LIGHT_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);//醒一下(开启网络、恢复wakelock)
} else {
// We'd like to do maintenance, but currently don't have network
// connectivity... let's try to wait until the network comes back.
// We'll only wait for another full idle period, however, and then give up.
scheduleLightAlarmLocked(mNextLightIdleDelay);
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
}
break;
}
}

可是这里仅仅是一个light idle,一旦进入deep idle。light idle设置的alarm会无效的(这个后面细说),也就是说light idle一旦进入deep idle后无效了(由于idle step主要靠alarm驱动。而alarm无效后自然就驱动不了)。

deep idle模式

以下我们再来看deep idle模式,这个模式除了禁止网络、wakelock还会禁止alarm。

我们再来看becomeInactiveIfAppropriateLocked函数中以下代码。

是关于deep idle的设置 这里的mInactiveTimeout是半小时

    void becomeInactiveIfAppropriateLocked() {
if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
if ((!mScreenOn && !mCharging) || mForceIdle) {
// Screen has turned off; we are now going to become inactive and start
// waiting to see if we will ultimately go idle.
if (mState == STATE_ACTIVE && mDeepEnabled) {
mState = STATE_INACTIVE;
if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
resetIdleManagementLocked();
scheduleAlarmLocked(mInactiveTimeout, false);
EventLogTags.writeDeviceIdle(mState, "no activity");
}

我们来看下scheduleAlarmLocked函数,注意假设这里參数idleUntil是true会调用AlarmManager的setIdleUntil函数,调用这个函数后普通应用设置alarm将失效。

void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (mMotionSensor == null) {
//在onBootPhase时,获取过位置检測传感器
//假设终端没有配置位置检測传感器。那么终端永远不会进入到真正的Doze ilde状态
// If there is no motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving.
return;
} mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
if (idleUntil) {
//此时IdleUtil的值为false
mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
} else {
//30min后唤醒,调用mDeepAlarmListener的onAlarm函数
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
}
}

须要注意的是,DeviceIdleController一直在监控屏幕状态和充电状态,一但不满足Doze模式的条件。前面提到的becomeActiveLocked函数就会被调用。mAlarmManager设置的定时唤醒事件将被取消掉,mDeepAlarmListener的onAlarm函数不会被调用。

因此,我们知道了终端必须保持Doze模式的入口条件长达30min,才会进入mDeepAlarmListener.onAlarm:

private final AlarmManager.OnAlarmListener mDeepAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (DeviceIdleController.this) {
//进入到stepIdleStateLocked函数
stepIdleStateLocked("s:alarm");
}
}
};

以下我们就来看下stepIdleStateLocked函数:

void stepIdleStateLocked(String reason) {
..........
final long now = SystemClock.elapsedRealtime();
//个人认为,以下这段代码,是针对Idle状态设计的
//假设在Idle状态收到Alarm,那么将先唤醒终端,然后又一次推断是否须要进入Idle态
//在介绍Doze模式原理时提到过。若应用调用AlarmManager的一些指定接口。仍然能够在Idle状态进行工作
if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
// Whoops, there is an upcoming alarm. We don't actually want to go idle.
if (mState != STATE_ACTIVE) {
becomeActiveLocked("alarm", Process.myUid());
becomeInactiveIfAppropriateLocked();
}
return;
} //以下是Doze模式的状态转变相关的代码
switch (mState) {
case STATE_INACTIVE:
// We have now been inactive long enough, it is time to start looking
// for motion and sleep some more while doing so.
//保持屏幕熄灭。同一时候未充电达到30min,进入此分支 //注冊一个mMotionListener。检測是否移动
//假设检測到移动。将又一次进入到ACTIVE状态
//对应代码比較直观。此处不再深入分析
startMonitoringMotionLocked(); //再次调用scheduleAlarmLocked函数。此次的时间仍为30min
//也就说假设不发生退出Doze模式的事件,30min后将再次进入到stepIdleStateLocked函数
//只是届时的mState已经变为STATE_IDLE_PENDING
scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); // Reset the upcoming idle delays.
//mNextIdlePendingDelay为5min
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
//mNextIdleDelay为60min
mNextIdleDelay = mConstants.IDLE_TIMEOUT; //状态变为STATE_IDLE_PENDING
mState = STATE_IDLE_PENDING;
............
break;
case STATE_IDLE_PENDING:
//保持息屏、未充电、精巧状态,经过30min后。进入此分支
mState = STATE_SENSING; //保持Doze模式条件,4min后再次进入stepIdleStateLocked
scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT); //停止定位相关的工作
cancelLocatingLocked();
mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null; //開始检測手机是否发生运动(这里应该是更仔细的側重于角度的变化)
//若手机运动过,则又一次变为active状态
mAnyMotionDetector.checkForAnyMotion();
break;
case STATE_SENSING:
//上面的条件满足后,进入此分支,開始获取定位信息
cancelSensingTimeoutAlarmLocked();
mState = STATE_LOCATING;
............
//保持条件30s,再次调用stepIdleStateLocked
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;
} // If we have a location provider, we're all set, the listeners will move state
// forward.
if (mLocating) {
//无法定位则直接进入下一个case
break;
}
case STATE_LOCATING:
//停止定位和运动检測,直接进入到STATE_IDLE_MAINTENANCE
cancelAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.stop(); case STATE_IDLE_MAINTENANCE:
//进入到这个case后,终端開始进入Idle状态。也就是真正的Doze模式 //定义退出Idle的时间此时为60min
scheduleAlarmLocked(mNextIdleDelay, true);
............
//退出周期逐步递增,每次乘2
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
...........
//周期有最大值6h
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
} mState = STATE_IDLE;
...........
//通知PMS、NetworkPolicyManagerService等Doze模式开启。即进入Idle状态
//此时PMS disable一些非白名单WakeLock;NetworkPolicyManagerService開始限制一些应用的网络訪问
//消息处理的详细流程比較直观,此处不再深入分析
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break; case STATE_IDLE:
//进入到这个case时,本次的Idle状态临时结束,开启maintenance window // We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire(); //定义又一次进入Idle的时间为5min (也就是手机可处于Maintenance window的时间)
scheduleAlarmLocked(mNextIdlePendingDelay, false); mMaintenanceStartTime = SystemClock.elapsedRealtime();
//调整mNextIdlePendingDelay。乘2(最大为10min)
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR)); if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
} mState = STATE_IDLE_MAINTENANCE;
...........
//通知PMS等临时退出了Idle状态,能够进行一些工作
//此时PMS enable一些非白名单WakeLock。NetworkPolicyManagerService開始同意应用的网络訪问
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}

上面的流程在凝视里面已经非常明确了。而我们在进入Deep idle时。发送了一个MSG_REPORT_IDLE_ON消息,我们看以下这个消息的处理和之前的MSG_REPORT_IDLE_ON_LIGHT一样的。关闭网络。禁止wakelock。

                case MSG_REPORT_IDLE_ON:
case MSG_REPORT_IDLE_ON_LIGHT: {
EventLogTags.writeDeviceIdleOnStart();
final boolean deepChanged;
final boolean lightChanged;
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 {
mNetworkPolicyManager.setDeviceIdleMode(true);
mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
? BatteryStats.DEVICE_IDLE_MODE_DEEP
: BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
} catch (RemoteException e) {
}
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
}
EventLogTags.writeDeviceIdleOnComplete();
} break;

而禁止alarm是通过调用例如以下函数,注意參数是true。參数是true会调用mAlarmManager.setIdleUntil函数。

这样其它的alarm会被滞后(除非在白名单中)

scheduleAlarmLocked(mNextIdleDelay, true);

而每隔一段时间会进入Maintenance window的时间,此时是通过发送MSG_REPORT_IDLE_OFF消息,来恢复网络和wakelock。而这个时候之前设置的mAlarmManager.setIdleUntil的alarm也到期了。因此其它alarm也恢复了。

可是这个时间仅仅有5分钟,又一次设置了alarm再次进入deep idle状态。

Idle总结

当手机关闭屏幕或者拔掉电源的时候,手机開始推断是否进入Doze模式。

Doze模式分两种,第一种是light idle:

1.light idle

light idle在手机灭屏且没有充电状态下,5分钟開始进入light idle流程。

然后第一次进入LIGHT_STATE_INACTIVE流程时。会再定义一个10分钟的alarm。

然后系统进入light idle状态。这个状态会使不是白名单的应用禁止訪问网络,以及持wakelock锁。

2.deep idle

deep idle除了light idle的状态还会把非白名单中应用的alarm也禁止了。

 此时。系统中非白名单的应用将被禁止訪问网络。它们申请的Wakelock也会被disable。

从上面的代码能够看出,系统会周期性的退出Idle状态,进入到MAINTENANCE状态,集中处理相关的任务。

 一段时间后,会又一次再次回到IDLE状态。每次进入IDLE状态,停留的时间都会是上次的2倍,最大时间限制为6h。

当手机运动,或者点亮屏幕,插上电源等。系统都会又一次返回到ACTIVIE状态。

这里盗用别人的一样图。但不过deep idle的状态:

(这里特别说明下,alarm和wakelock都是由DeviceIdleController主动调用相关接口设置的,而网络是调用了DeviceIdleController的getAppIdWhitelist接口来获取应用的白名单的,从而禁止非白名单訪问网络。

网络我们不分析了,之前我们在Android6.0时分析过idle状态下alarm和wakelock。7.0略微有点不一样。以下两篇博客又一次分析下吧。

Android7.0 Doze模式分析(一)Doze介绍 &amp; DeviceIdleController的更多相关文章

  1. 高通android7.0刷机工具使用介绍

    刷机工具安装 1. 安装QPST.WIN.2.7 Installer-00448.3 2. 安装python2.7,并配置其环境变量 刷机方法 1.将编译后的刷机文件拷贝到如下目录:SC20_CE_p ...

  2. Android7.0 PowerManagerService 之亮灭屏(二) PMS 电源状态管理updatePowerStateLocked()

    本篇注意接着上篇[Android7.0 PowerManagerService 之亮灭屏(一)]继续分析量灭屏的流程,这篇主要分析PMS的状态计算和更新流程,也是PMS中最为重要和复杂的一部分电源状态 ...

  3. fir.im Weekly - 关于 Log Guru 开源、Xcode 探索和 Android7.0 适配

    本期 fir.im Weekly 整理了最近的一些技术分享,包括关于 Log Guru 开源.Xcode 探索. Android7.0 适配等等 iOS/Android 相关的工具.源码分享和技术文章 ...

  4. WmS简介(三)之Activity窗口是如何创建的?基于Android7.0源码

    OK,在前面两篇博客中我们分别介绍了WmS中的token,同时也向小伙伴们区分了Window和窗口的区别,并且按照type值的不同将Android系统中的窗口分为了三大类,那么本篇博客我们就来看看应用 ...

  5. WmS详解(二)之如何理解Window和窗口的关系?基于Android7.0源码

    上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们 ...

  6. Android7.0调用系统相机拍照、读取系统相册照片+CropImageView剪裁照片

    Android手机拍照.剪裁,并非那么简单 简书地址:[我的简书–T9的第三个三角] 前言 项目中,基本都有用户自定义头像或自定义背景的功能,实现方法一般都是调用系统相机–拍照,或者系统相册–选择照片 ...

  7. WmS具体解释(二)之怎样理解Window和窗体的关系?基于Android7.0源代码

    上篇博客(WmS具体解释(一)之token究竟是什么?基于Android7.0源代码)中我们简要介绍了token的作用,这里涉及到的概念非常多,当中出现频率最高的要数Window和窗体这一对搭档了,那 ...

  8. 下载安装APK(兼容Android7.0)

    我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载. 一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和 ...

  9. 《吐血整理》高级系列教程-吃透Fiddler抓包教程(26)-Fiddler如何抓取Android7.0以上的Https包-上篇

    1.简介 众所周知,假如设备是android 7.0+的系统同时应用设置targetSdkVersion >= 24的话,那么应用默认是不信任安装的Fiddler用户证书的,所以你就没法抓到应用 ...

随机推荐

  1. WPF学习总结1:INotifyPropertyChanged接口的作用

    在代码中经常见到这个接口,它里面有什么?它的作用是什么?它和依赖属性有什么关系? 下面就来总结回答这三个问题. 1.这个INotifyPropertyChanged接口里就一个PropertyChan ...

  2. Python——验证码识别 Pillow + tesseract-ocr

    至于安装教程在这里不再重复说了,可以参考博客,网上有大把的教程 https://blog.csdn.net/testcs_dn/article/details/78697730 要是别的验证码是如下类 ...

  3. 集群扩容的常规解决:一致性hash算法

    写这篇博客是因为之前面试的一个问题:如果memcached集群需要增加机器或者减少机器,那么其他机器上的数据怎么办? 最后了解到使用一致性hash算法可以解决,下面一起来学习下吧. 声明与致谢: 本文 ...

  4. Spring Security教程(四):自定义登录页

    在前面的例子中,登陆页面都是用的Spring Security自己提供的,这明显不符合实际开发场景,同时也没有退出和注销按钮,因此在每次测试的时候都要通过关闭浏览器来注销达到清除session的效果. ...

  5. IOS 设备备份文件详解 (二)

    这篇主要讲解如何解析Manifest.mbdb文件. 使用二进制工具打开这个文件,文件的头6个字节是固定的,相当于是文件的一种标识 后面的内容是一个一个的项,可以使用一个循环来读取文件,一个一个解析. ...

  6. [nginx]location语法

    location语法 location语法格式 location [=|~|~*|^~] uri { .... } location [=|~|~*|^~] uri {....} 指令 匹配标识 匹配 ...

  7. Python | 一行命令生成动态二维码

    当我看到别人的二维码都做的这么炫酷的时候,我心动了! 我也想要一个能够吸引眼球的二维码,今天就带大家一起用 Python 来做一个炫酷的二维码! 首先要安装工具 myqr: pip install m ...

  8. ssh以密钥的方式登录服务器时,只要有密钥可以登服务器,如果有密钥和公钥同时存在(在公钥没问题的情况下可以),但如果公钥有问题,就不能登录成功

    在~/.ssh/下如果只有密钥或公私同时存在时,都可以成功登录服务器,但!!!!!!如果公钥有换成别的服务器的公钥时,是无法登录远程的服务器!!!!

  9. express应用中常用中间件介绍

    var strftime = require('strftime'); 时间格式化中间件,功能和moment.js差不多 var methodOverride = require('method-ov ...

  10. java: 保留两位小数4种方法

    import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; public c ...