概述

Doze模式可以简单概括为:

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

Doze模式(低电耗模式),是Andoriod6.0增加的一项系统服务,主要目的是为了优化电池性能,增加电池续航时间,Doze模式又分两种模式:深度Doze模式(Deep Doze)和轻度Doze模式(Light Doze),如果用户长时间没有主动使用其设备,处于静止状态且屏幕已关闭,则系统会使设备进入Doze模式,也就是深度Doze模式。如果用户关闭设备屏幕但仍处于移动状态时,则设备进入轻度Doze模式,此外,轻度Doze模式只适合Android7.0及以上版本。

当用户长时间未使用设备时,设备进入Doze模式,Doze模式会延迟应用后台 CPU 和网络活动,从而延长电池续航时间。处于Doze模式的设备会定期进入维护时段,在此期间,应用可以完成待进行的活动。然后,Doze模式会使设备重新进入较长时间的休眠状态,接着进入下一个维护时段。在达到几个小时的休眠时间上限之前,平台会周而复始地重复Doze模式休眠/维护的序列,且每一次都会延长Doze模式时长。处于Doze模式的设备始终可以感知到动作,且会在检测到动作时立即退出Doze模式。整个Doze图示如下: 

上面这张图比较经典,基本上说明了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运行。

Deep Doze 和Light Doze模式对比如下:

DeviceIdleController的启动流程

开机时

SystemServer.java-->startOtherServices()

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

构造方法:

    public DeviceIdleController(Context context) {super(context);mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());// NOTE: Bug #627645 low power Feature BEG-->mPowerControllerHelper = new PowerControllerHelper();// <--NOTE: Bug #627645 low power Feature ENDmAbsDeviceIdleController = (AbsDeviceIdleController)PMSFactory.getInstance().createExtraDeviceIdleController(context);}

构造方法中,首先创建在/data/sytem下创建了deviceidle.xml文件,然后创建了一个Handler,并将BackgroundThread中Handler的Looper和该Handler进行绑定。BackgroundThread是一个单例模式实现的后台线程,可以用于任何一个进程。

DeviceIdleController.java-->onStart()

@Override
public void onStart() {final PackageManager pm = getContext().getPackageManager();synchronized (this) {//Light doze模式和Deep Doze是否可用,默认不可用mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(com.android.internal.R.bool.config_enableAutoPowerModes);//获取系统全局配置信息,如权限SystemConfig sysConfig = SystemConfig.getInstance();//从配置文件中读取省电模式下的白名单列表且不在在idle状态的白名单列表,即列表中的app能够在省电模式下在后台运行,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);//添加到Map中,表示这些应用在省电模式下可后台运行,但在Doze下后台不可运行mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);//添加到SpareArray中,表示这是处于powersave白名单但不处于doze白名单的系统应用                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);} catch (PackageManager.NameNotFoundException e) {}}//从配置文件中读取时能够在省电模式白名单列表,也可以在DOZE模式下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);mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);//添加到DIC中的白名单列表中mPowerSaveWhitelistApps.put(ai.packageName, appid);//添加到DIC中的系统应用白名单列表中mPowerSaveWhitelistSystemAppIds.put(appid, true);} catch (PackageManager.NameNotFoundException e) {}}//设置Contants内容观察者mConstants = new Constants(mHandler, getContext().getContentResolver());//读取/data/system/deviceidle.xml文件,将读取的app添加到表示用户设置的白名单中readConfigFileLocked();//更新白名单列表updateWhitelistAppIdsLocked();//网络是否连接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;mState = STATE_ACTIVE;//设备保持活动状态,深度Doze的初始值mLightState = LIGHT_STATE_ACTIVE;//设备保持活动状态,轻度Doze的初始值mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;}mBinderService = new BinderService();//发布远程服务publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);//发布本地服务publishLocalService(LocalService.class, new LocalService());
}

在onStart()方法中,首先通过SystemConfig读取了两类白名单列表:在低电量模式下后台允许运行的应用的白名单、在低电量模式和Doze模式下都允许后台运行的应用白名单;

    public ArraySet<String> getAllowInPowerSaveExceptIdle() {return mAllowInPowerSaveExceptIdle;}public ArraySet<String> getAllowInPowerSave() {return mAllowInPowerSave;}
             } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {String pkgname = parser.getAttributeValue(null, "package");if (pkgname == null) {Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "+ permFile + " at " + parser.getPositionDescription());} else {mAllowInPowerSaveExceptIdle.add(pkgname);}XmlUtils.skipCurrentTag(parser);continue;} else if ("allow-in-power-save".equals(name) && allowAll) {String pkgname = parser.getAttributeValue(null, "package");if (pkgname == null) {Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "+ parser.getPositionDescription());} else {mAllowInPowerSave.add(pkgname);}XmlUtils.skipCurrentTag(parser);continue;

我们再看下platform.xml文件

    <!-- These are the standard packages that are white-listed to always have internetaccess while in power save mode, even if they aren't in the foreground. --><allow-in-power-save package="com.android.providers.downloads" /><allow-in-power-save package="com.sprd.ImsConnectionManager" /><allow-in-power-save package="com.android.phone" /><allow-in-power-save package="com.spreadtrum.vowifi" />

onStart()调用updateWhitelistAppIdsLocked()更新白名单列表

private void updateWhitelistAppIdsLocked() {//处于省电模式白名单但不处于Idle状态白名单的appmPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);//处于所有的白名单的app,包括用户添加的mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);//用户添加的白名单列表应用mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);if (mLocalActivityManager != null) {//将适用于所有情况的白名单列表通知给AMSmLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);}if (mLocalPowerManager != null) {//将适用于所有情况的白名单列表通知给PMSmLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);}if (mLocalAlarmManager != null) {//将用户添加到白名单列表中的应用通知给AlarmManagerServicemLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);}
}

执行完onStart()方法后,就开始执行最后一个生命周期方法onBootPhase()

@Override
public void onBootPhase(int phase) {if (phase == PHASE_SYSTEM_SERVICES_READY) {synchronized (this) {mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);mBatteryStats = BatteryStatsService.getService();mLocalActivityManager = getLocalService(ActivityManagerInternal.class);mLocalPowerManager = getLocalService(PowerManagerInternal.class);mPowerManager = getContext().getSystemService(PowerManager.class);//申请一个wakelock锁保持CPU唤醒mActiveIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_maint");//设置wakelock锁为非计数锁,只要执行一次release()就能释所有非计数锁mActiveIdleWakeLock.setReferenceCounted(false);mGoingIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_going_idle");//设置wakelock锁为计数锁,一次申请对应一次释放mGoingIdleWakeLock.setReferenceCounted(true);mConnectivityService = (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));mSensorManager = (SensorManager)getContext().getSystemService(Context.SENSOR_SERVICE);//可用于自动省电模式时的传感器id,0表示没有可用传感器int sigMotionSensorId = getContext().getResources().getInteger(com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);if (sigMotionSensorId > 0) {//根据传感器id获取传感器mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);}//如果没有指定任一传感器&&在没有指定传感器情况下首选WristTilt//传感器配置为trueif (mMotionSensor == null && getContext().getResources().getBoolean(com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {//获取一个WristTilt传感器(手腕抖动)mMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_WRIST_TILT_GESTURE, true);}if (mMotionSensor == null) {//如果以上条件都不满足,则获取一个SMD传感器mMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION, true);}//是否在进入Doze模式时预先获取位置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;//用于检测设备是否已静止mAnyMotionDetector = new AnyMotionDetector((PowerManager) getContext().getSystemService(Context.POWER_SERVICE),mHandler, mSensorManager, this, angleThreshold);//用于Doze状态发生改变时发送广播mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY| Intent.FLAG_RECEIVER_FOREGROUND);//用于当轻度Doze状态发生改变时发送广播mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY| Intent.FLAG_RECEIVER_FOREGROUND);//注册监听电池状态改变的广播IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_BATTERY_CHANGED);getContext().registerReceiver(mReceiver, filter);//注册监听卸载应用的广播filter = new IntentFilter();filter.addAction(Intent.ACTION_PACKAGE_REMOVED);filter.addDataScheme("package");getContext().registerReceiver(mReceiver, filter);//注册监听网络连接改变的广播filter = new IntentFilter();filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);getContext().registerReceiver(mReceiver, filter);//注册监听亮灭屏的广播filter = new IntentFilter();filter.addAction(Intent.ACTION_SCREEN_OFF);filter.addAction(Intent.ACTION_SCREEN_ON);getContext().registerReceiver(mInteractivityReceiver, filter);//将适用于所有情况的白名单列表通知给AMS、PMS、AlarmManagerServicemLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray)//更新交互状态updateInteractivityLocked();}//更新网络连接状态updateConnectivityState(null);} else if (phase == PHASE_BOOT_COMPLETED) {}
}

updateInteractivityLocked()用于更新交互状态

void updateInteractivityLocked() {// The interactivity state from the power manager tells us whether the display is// in a state that we need to keep things running so they will update at a normal// frequency.//获取设备是否处于交互状态boolean screenOn = mPowerManager.isInteractive();if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);//表示当前不处于交互状态且上次处于交互状态if (!screenOn && mScreenOn) {mScreenOn = false;if (!mForceIdle) {//是否强制进入Idle//进入Idle模式的入口方法becomeInactiveIfAppropriateLocked();}} else if (screenOn) {mScreenOn = true;if (!mForceIdle) {//退出Idle模式becomeActiveLocked("screen", Process.myUid());}}
}

updateConnectivityState()用于更新网络状态

void updateInteractivityLocked() {// The interactivity state from the power manager tells us whether the display is// in a state that we need to keep things running so they will update at a normal// frequency.//获取设备是否处于交互状态boolean screenOn = mPowerManager.isInteractive();if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);//表示当前不处于交互状态且上次处于交互状态if (!screenOn && mScreenOn) {mScreenOn = false;if (!mForceIdle) {//是否强制进入Idle//进入Idle模式的入口方法becomeInactiveIfAppropriateLocked();}} else if (screenOn) {mScreenOn = true;if (!mForceIdle) {//退出Idle模式becomeActiveLocked("screen", Process.myUid());}}
}

DeviceIdleController的状态变化

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

............
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
...........  
                case Intent.ACTION_BATTERY_CHANGED: {synchronized (DeviceIdleController.this) {int plugged = intent.getIntExtra("plugged", 0);updateChargingLocked(plugged != 0);}} break;

DeviceIdleController.java-->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模式

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。

ight idle 和deep idle根据不同条件进入


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");}if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {mLightState = LIGHT_STATE_INACTIVE;if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");resetLightIdleManagementLocked();scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);EventLogTags.writeDeviceIdleLight(mLightState, "no activity");}}

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() {@Overridepublic 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;}if (DEBUG) Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + mLightState);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);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);mGoingIdleWakeLock.acquire();
//发送消息,这个消息处理就会关闭网络,禁止wakelock  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) {// 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);//醒一下(开启网络、恢复wakelock)mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);} 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;}}

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会被滞后(除非在白名单中)

  1. scheduleAlarmLocked(mNextIdleDelay, true);

而每隔一段时间会进入Maintenance window的时间,此时是通过发送MSG_REPORT_IDLE_OFF消息,来恢复网络和wakelock。而这个时候之前设置的mAlarmManager.setIdleUntil的alarm也到期了,因此其他alarm也恢复了。但是这个时间只有5分钟,重新设置了alarm再次进入deep idle状态。

Android 8.1 Doze模式分析(一)相关推荐

  1. Android 7.0 Doze模式分析

    白名单机制 Android6.0及更高版本还提供了Doze模式白名单列表,通过设置应用程序进入白名单 列表可逃脱Doze模式的各种限制.检测应用程序是否存在白名单list里面,可使用 PowerMan ...

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

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

  3. Android 8.1 Doze模式分析(五) Doze白名单及Debug方式

    1.Doze模式下豁免的应用和白名单列表 1.什么是白名单列表? 一系列应用包名的集合. 2.有什么作用? 处于白名单列表中的应用,不受Doze的影响,即Doze不会对该应用进行限制,如该应用的Job ...

  4. Android 8.1 Doze模式分析(三)——Deep Doze模式

    概述 Deep Doze,也就是Android的Doze模式了,表示深度Doze,比起LightDoze,它将进行更多的限制:无法进行网络访问和 GPS/WLAN 扫描.唤醒被忽略.闹钟和作业/同步被 ...

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

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

  6. Android 8.1 Doze模式分析(四)——Doze模式的退出

    概述 Doze的退出,说的更严格一点,就是当Doze模式的状态由其他状态变为ACTIVE状态.简而言之,退出Doze模式有三种情况:屏幕亮屏.插入充电器.设备有移动.下面就这三种情况进行下分析. 在前 ...

  7. Android Doze模式分析

    Android 6.0 Doze模式分析 Doze模式是Android6.0上新出的一种模式,是一种全新的.低能耗的状态,在后台只有部分任务允许运行,其他都被强制停止.当用户一段时间没有使用手机的时候 ...

  8. Android源码设计模式分析项目

    原文链接:https://github.com/simple-android-framework/android_design_patterns_analysis Android源码设计模式分析开源项 ...

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

     參考:http://blog.csdn.net/gaugamela/article/details/52981984 在Android M中,Google就引入了Doze模式.它定义了一种全新的 ...

最新文章

  1. iOS NSString中实用的方法
  2. POJ - 3700 Missile Defence System.(dfs+最优性剪枝)
  3. web前端常用知识点
  4. 随想录(软件逆向与OllyDbg)
  5. quarts集群 运维_精讲Elastic-job + Quartz实现企业级定时任务
  6. [GBA ROM列表]不断补完中……
  7. NC63重写导出功能
  8. 计算机网络拓扑图的描述,计算机网络拓扑结构 以下关于星型网络拓扑结构的描述正确的是______。 (多选题 )...
  9. redis:redis与lua
  10. NDK-r25交叉编译qemu-7.0.0 第651步报错
  11. PCIe4.0的Add-in-Card(AIC)金手指layout建议
  12. java 音频转换_java实现音频转换
  13. linux中的快捷键
  14. Unity中利用动画机(Animation) 制作物体的爆炸拆解
  15. resnet修改输出类别
  16. 时间复杂度o(n^0.5)_铁路轨道资料汇总(含轨道资料、论文、视频讲解)【更新时间2020.11.5】...
  17. TAQ服务器npc多久自动交物资,怀旧服GZS故意拖延TAQ开门,手段无下限?玩家:杀NPC举报也没用!...
  18. SAP FICO全解析之-公司代码
  19. 写给仿真软件研发的“一篇文章入门”系列(终)
  20. 最齐全的促销/横幅矢量图素材,速来收藏

热门文章

  1. L1-033 出生年 (15 分)
  2. cpu要和gpu搭配吗_显卡和CPU搭配有要求吗 CPU和显卡怎么搭配最好?
  3. 创立于使用指南的谷歌云GPU服务器
  4. 视频太大怎么在线压缩大小?
  5. 仿腾讯新闻客户端图片新闻幻灯片动画效果
  6. mac idea中添加JDK源码注释
  7. 利用css优雅的处理破损图片
  8. 汽车业芯片荒或倒逼手机涨价
  9. linux学习(一)--启动文件bootsect.s
  10. C语言判断字符是中文还是英文