Android7.0 Doze模式

article/2025/9/15 10:43:04

在Android M中,Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。
在该状态,后台只有部分任务被允许运行,其它任务都被强制停止。

本篇博客中,我们就来分析一下Android 7.0中Doze模式相关的流程。

一、基本原理
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运行。

更多基本信息的描述可以参考:
What is Doze mode in android 6.0 Marshmallow?

二、DeviceIdleController的初始化
Android中的Doze模式主要由DeviceIdleController来控制。

public class DeviceIdleController extends SystemServiceimplements AnyMotionDetector.DeviceIdleCallback {....................
}

可以看出DeviceIdleController继承自SystemService,是一个系统级的服务。
同时,继承了AnyMotionDetector定义的接口,便于检测到终端位置变化后进行回调。

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

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

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

1、构造函数

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

DeviceIdleController的构造函数比较简单,就是在创建data/system/deviceidle.xml对应的file文件,同时创建一个对应于后台线程的handler。

2、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,将形成SystemConfigSystemConfig 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区分了系统应用白名单、普通应用白名单等,因此上面进行了一些合并操作。

3、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中..............//如果配置文件表明:终端需要预获取位置信息//则构造LocationRequestif (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方法:
主要创建一些本地变量,然后根据配置文件初始化一些传感器,同时注册了一些广播接收器和回到接口,
最后更新屏幕显示和连接状态相关的信息。

三、DeviceIdleController定义的状态变化
根据前面提到的Doze模式的原理,我们知道手机进入Doze模式的条件是:未充电、手机位置不发生变化、屏幕熄灭。
因此,在DeviceIdleController中监听了这三个条件对应的状态,以决定终端是真正否进入到Doze模式。

对这三个条件的分析,最终都会进入到DeviceIdleController定义的状态变化流程。
因此我们就以充电状态的变化为例,看看DeviceIdleController进行了哪些处理。其余条件的分析,基本类似。

1、充电状态的处理
对于充电状态,在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值一般为falseif (!mForceIdle) {//判断是否进入Doze模式becomeInactiveIfAppropriateLocked();}} else if (charging) {//进入充电状态mCharging = charging;if (!mForceIdle) {//手机退出Doze模式becomeActiveLocked("charging", Process.myUid());}}
}

2、becomeActiveLocked
我们先看看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;mLightState = LIGHT_STATE_ACTIVE;mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;mCurIdleBudget = 0;mMaintenanceStartTime = 0;//2、重置一些事件resetIdleManagementLocked();resetLightIdleManagementLocked();addEvent(EVENT_NORMAL);}
}

2.1 scheduleReportActiveLocked

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

对应的处理流程:

.........
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);}
}
........

从上面的代码可以看出,scheduleReportActiveLocked函数最主要的工作是:
通知PMS等重新更新终端的状态;
通知NetworkPolicyManagerService不再限制应用上网。
发送Doze模式改变的广播。

2.2 resetIdleManagementLocked

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

从上面的代码可以看出,resetIdleManagementLocked的工作相对简单,就是停止进入Doze模式时启动的一些任务。

3、becomeInactiveIfAppropriateLocked
与becomeActiveLocked函数相比,becomeInactiveIfAppropriateLocked函数较为复杂。
因为调用becomeInactiveIfAppropriateLocked函数时,终端可能只是满足进入Doze模式的条件,离进入真正的Doze模式还有很长的“一段路”需要走。

我们看看becomeInactiveIfAppropriateLocked的代码:

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时间为30minscheduleAlarmLocked(mInactiveTimeout, false);...............}if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {mLightState = LIGHT_STATE_INACTIVE;.............resetLightIdleManagementLocked();scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);}}
}

从上面的代码可以看出,在DeviceIdleState中,用mState和mLightState来衡量终端是否真的进入了Doze模式。
我们目前仅关注mState变量的改变情况,mLightState的变化流程可类似分析。
此时,mState的状态为INACTIVE。

3.1 scheduleAlarmLocked
我们跟进一下scheduleAlarmLocked函数:

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的值为falsemAlarmManager.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() {@Overridepublic void onAlarm() {synchronized (DeviceIdleController.this) {//进入到stepIdleStateLocked函数stepIdleStateLocked("s:alarm");}}
};

此处没有什么多说的,直接调用了stepIdleStateLocked函数。
需要注意的是stepIdleStateLocked将决定DeviceIdleController状态之间的转移。
这种通过AlarmManager设定唤醒时间,然后通过回调接口来调用stepIdleStateLocked的方式,将被多次使用。

3.2 stepIdleStateLocked
此处没有什么多说的,直接来看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_PENDINGscheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);// Reset the upcoming idle delays.//mNextIdlePendingDelay为5minmNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;//mNextIdleDelay为60minmNextIdleDelay = mConstants.IDLE_TIMEOUT;//状态变为STATE_IDLE_PENDING mState = STATE_IDLE_PENDING;............break;case STATE_IDLE_PENDING://保持息屏、未充电、静止状态,经过30min后,进入此分支mState = STATE_SENSING;//保持Doze模式条件,4min后再次进入stepIdleStateLockedscheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);//停止定位相关的工作cancelLocatingLocked();mNotMoving = false;mLocated = false;mLastGenericLocation = null;mLastGpsLocation = null;//开始检测手机是否发生运动(这里应该是更细致的侧重于角度的变化)//若手机运动过,则重新变为active状态mAnyMotionDetector.checkForAnyMotion();break;case STATE_SENSING://上面的条件满足后,进入此分支,开始获取定位信息cancelSensingTimeoutAlarmLocked();mState = STATE_LOCATING;............//保持条件30s,再次调用stepIdleStateLockedscheduleAlarmLocked(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) {//无法定位则直接进入下一个casebreak;}case STATE_LOCATING://停止定位和运动检测,直接进入到STATE_IDLE_MAINTENANCEcancelAlarmLocked();cancelLocatingLocked();mAnyMotionDetector.stop();case STATE_IDLE_MAINTENANCE://进入到这个case后,终端开始进入Idle状态,也就是真正的Doze模式//定义退出Idle的时间此时为60minscheduleAlarmLocked(mNextIdleDelay, true);............//退出周期逐步递增,每次乘2mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);...........//周期有最大值6hmNextIdleDelay = 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;}
}

至此,stepIdleStateLocked的流程介绍完毕。
我们知道了,在DeviceIdleController中,为终端定义了7中状态,如下图所示:

手机被操作的时候为Active状态。
当手机关闭屏幕或者拔掉电源的时候,手机开始判断是否进入Doze模式。

经过一系列的状态后,最终会进入到IDLE状态,此时才算进入到真正的Doze模式,系统进入到了深度休眠状态。
此时,系统中非白名单的应用将被禁止访问网络,它们申请的Wakelock也会被disable。
从上面的代码可以看出,系统会周期性的退出Idle状态,进入到MAINTENANCE状态,集中处理相关的任务。
一段时间后,会重新再次回到IDLE状态。每次进入IDLE状态,停留的时间都会是上次的2倍,最大时间限制为6h。

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

四、总结
本篇博客中,我们分析了Doze模式对应的服务DeviceIdleController。

在了解DeviceIdleController的初始化过程后,我们重点分析了其定义的状态转移过程。
当然这些分析集中在了框架的源码分析上,至于Doze模式对App的影响,建议阅读下面的文章:
Android M新特性Doze and App Standby模式详解


http://chatgpt.dhexx.cn/article/Jvf0cCX9.shtml

相关文章

Android Doze模式和app Standby模式

对低电耗模式(app Standby)和应用待机模式(Doze)进行针对性优化 从 Android 6.0&#xff08;API 级别 23&#xff09;开始&#xff0c;Android 引入了两个省电功能&#xff0c;可通过管理应用在设备未连接至电源时的行为方式为用户延长电池寿命。低电耗模式通过在设备长时间处…

adb shell dumpsys deviceidle whitelist 打盹模式(Doze)白名单

Android 6.0开始引入了打盹模式(Doze)&#xff0c;若想使自己的App不受打盹模式的影响&#xff08;推迟Alarm唤起时间、限制CPU调用、限制网络请求等&#xff09;&#xff0c;需终端手机厂商为该App添加这个白名单。 Android developer 针对低电耗模式和应用待机模式进行优化&…

Android 8.1 Doze模式分析(二)——Light Doze模式

概述 LightDoze表示轻度doze模式&#xff0c;如果设备处于未充电且屏幕关闭状态&#xff0c;但未处于静止状态时&#xff0c;就会进入Light Doze模式&#xff0c;在LightDoze模式中&#xff0c;会定期进行维护&#xff0c;这种维护会持续N分钟&#xff0c;在维护状态(maintena…

Doze模式时序调研

Doze模式 机型 Light idle Deep idle 时序 定制白名单 AppStandby BLE Scanning管控 华为P20 Pro 开启 64.5分钟进入deep idle 有 开启 禁止BLE scanning OPPO F11 Pro 开启 30分钟进入deep idle 有 开启 允许BLE scanning 小米 MI 8 Pro 开启 64.5分钟进入…

Android7.0 Doze模式分析(一)Doze介绍 DeviceIdleController

参考&#xff1a;http://blog.csdn.net/gaugamela/article/details/52981984 在Android M中&#xff0c;Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。 在该状态&#xff0c;后台只有部分任务被允许运行&#xff0c;其它任务都被强制停止。 在之前的博客中分…

android 7.0低电耗Doze模式

从 Android 6.0&#xff08;API 级别 23&#xff09;开始&#xff0c;Android 引入了两个省电功能&#xff0c;可通过管理应用在设备未连接至电源时的行为方式为用户延长电池寿命。 低电耗(Doze)模式通过在设备长时间处于闲置状态时推迟应用的后台 CPU 和网络 Activity 来减少电…

Android O Doze模式的状态

现象 以下是BugReport日志 日志 STATE_ACTIVE 2,006: 11-17 10:24:59.876137 900 900 I DeviceIdleController: updateChargingLocked: chargingtrue 2,007: 11-17 10:24:59.876244 900 900 I DeviceIdleController: becomeActiveLocked, reason charging 2,25…

【Android】Doze模式识别与检测

从 Android 6.0&#xff08;API 级别 23&#xff09;开始&#xff0c;Android 引入了两个省电功能&#xff1a;Doze模式&#xff08;官方翻译为低电耗模式&#xff09;和 App Standby模式&#xff08;官方翻译为应用待机模式&#xff09;&#xff0c;可通过管理应用在设备未连接…

Android 8.1 Doze模式分析(一)——Doze简介和DeviceIdleController的启动

概述 Doze模式&#xff0c;官方翻译为低电耗模式&#xff0c;是Andoriod6.0增加的一项系统服务&#xff0c;主要目的是为了优化电池性能&#xff0c;增加电池续航时间&#xff0c;Doze模式又分两种模式&#xff1a;深度Doze模式(Deep Doze)和轻度Doze模式(Light Doze)&#xf…

Android 6.0的省电技术Doze作用影响以及避免方法

从android 6.0开始&#xff0c;谷歌引入了两项新的省电技术延长电池使用时间&#xff0c;分别是Doze&#xff08;休眠&#xff09;和App Standby&#xff08;app待命模式&#xff09;&#xff0c;只要app是运行在6.0&#xff08;api 23&#xff09;及以上的系统&#xff0c;无论…

Doze模式简介

Doze模式是自Android 6.0开始引入的两项省电功能的其中之一&#xff0c;还有一个就是appstandby&#xff0c;通过管理应用在设备未连接至电源时的行为方式&#xff0c;帮助用户延长电池寿命。当用户长时间未使用设备时&#xff0c;低电耗模式会延迟应用的后台 CPU 和网络活动&a…

Android中的Doze模式

概述 Android 6.0引入的Doze机制在于节省系统耗电量&#xff0c;保护电池&#xff0c;延长电池的使用时间。当设备未连接至电源&#xff0c;且长时间处于闲置状态时&#xff0c;系统会将应用进入Doze&#xff0c;置于App Standby模式。而最终的效果&#xff0c;能提升30%的电量…

Mock工具之Mockito实战

在实际项目中写单元测试的过程中我们会发现需要测试的类有很多依赖&#xff0c;这些依赖项又会有依赖&#xff0c;导致在单元测试代码里几乎无法完成构建&#xff0c;尤其是当依赖项尚未构建完成时会导致单元测试无法进行。为了解决这类问题我们引入了Mock的概念&#xff0c;简…

Mockito 入门

目录 1.什么是 Mock 测试&#xff1f;2.Mockito简介3.在 SpringBoot 单元测试中使用 Mockito3.1 Maven依赖&#xff1a;3.2 UserService.java3.3 User.java3.4 thenReturn系列方法&#xff08;测试桩&#xff09;3.5 thenThrow系列方法3.6 verify 系列方法 4.Spring中mock任何容…

Mock工具之Moco使用教程

目录 一、什么是Moco二、安装&配置三、接口配置&测试3.1 第一个简单的请求&#xff1a;3.2 Get请求&#xff1a;3.3 Post请求&#xff1a;3.4 返回值为Json格式的请求&#xff1a;3.5 带cookie信息才能访问的post请求&#xff1a;3.6 重定向请求&#xff1a;3.7 前后端…

mock功能

目标 了解mock的作用及使用场景&#xff1b; mock使用场景 mock&#xff1a;假的 前端程序员提到的mock数据的含义是&#xff1a;真的假数据 真的&#xff1a;符合接口规范要求的。 假数据&#xff1a;数据是人为创建出来的&#xff0c;不是真正的业务数据。 什么时候需…

mock的介绍

1.什么是mock mock测试是以可控的方式模拟真实的对象行为。程序员通常创造模拟对象来测试对象本身该具备的行为&#xff0c;很类似汽车设计者使用碰撞测试假人来模拟车辆碰撞中人的动态行为 2.为什么要使用mock 之所以使用mock测试&#xff0c;是因为真实场景很难实现或者短…

Mock介绍

mock的定义&#xff08;what&#xff09;&#xff1a; mock是在测试过程中&#xff0c;对于一些不容易构造/获取的对象&#xff0c;创建一个mock对象来模拟对象的行为 为什么要使用mock&#xff08;why&#xff09;&#xff1a; 在做单元测试过程中&#xff0c;经常会有以下的…

Mockito单元测试

文章目录 Mockito单元测试为什么要使用Mock?导入依赖import导入包使用Mock模拟测试某个类中的某个方法是否可以成功执行使用Mock模拟某个类的方法&#xff0c;自己给这个方法返回我们指定的值使用Mock模拟某个方法调用后会抛出指定的异常使用Mock模拟测试某个类中的某个方法(加…

javascript 中怎么判断为数字类型

javascript中判断变量是否为数字类型抄的方法有两种&#xff1a; 方法一&#xff1a; function isnum(s) { if(s!null){ var r,re; re /\d*/i; //\d表示数字,*表示匹配多个数字 r s.match(re); return (rs)?true:false; } return false; } 方法二&#xff1a; function isnu…