一、引言
DOZE是安卓系统从6.0(API级别23)开始引入的对低电和应用待机模式的优化。具体介绍可以参考developers官方文档。
阅读完官文后,思考几个问题:
- 6.0之前,APP怎么处理可以让系统不休眠?
- 6.0开始,系统如何解决APP不让系统休眠的问题?
二、问题简答
6.0之前,如果不希望APP运行过程系统休眠,可以通过在APP内申请PowerManager.WakeLock保证不会被CPU挂起。这么做的好处是保持应用的正常运行,缺点是没有考虑到设备处于低电状态的时候对于节电的需求可能会更加强烈。
6.0开始,设计了DOZE机制,主要思想是通过设置超时定时器、监测设备是否长期没发生动作、监控是否接收到GPS位置信息变化来决定要不要让设备进入空闲状态,当进入空闲状态的时候,所有APP申请的WakeLock锁将被设置无效。但是DOZE的意思是打盹,不是死亡,就是说设备进入空闲状态之后,APP不是一直被CPU挂起不执行指令,而是定时会退出IDLE状态让APP正常使用CPU和网络。
三、源码分析
下面我们通过源码学习一下DOZE的实现思路。
源码地址:/frameworks/base/services/core/java/com/android/server/DeviceIdleController.java
顾名思义,设备空闲控制器,用来控制设备进入退出空闲状态的管理者。首先查看方法 stepIdleStateLocked,意思是跳进空闲状态。被触发的地方有:
- mDeepAlarmListener:通过AlarmManager.setIdleUntil设置监听。原因是:“s:alarm”,表示由于alarm触发。
- onAnyMotionResult,原因是”s:stationary“,表示长期没有动作发生。
- exitMaintenanceEarlyIfNeededLocked,原因是:“s:early”
- receivedGenericLocationLocked,原因是"s:location"
- receivedGpsLocationLocked,原因是"s:gps"
- onShellCommand,原因是"s:shell",通过adb shell命令触发
void stepIdleStateLocked(String reason) {
if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
EventLogTags.writeDeviceIdleStep();
//计算当前系统时间
final long now = SystemClock.elapsedRealtime();
//MIN_TIME_TO_ALARM: !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L
//mAlarmManager.getNextWakeFromIdleTime()计算下一次唤醒的时间
if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
// Whoops, there is an upcoming alarm. We don't actually want to go idle.
//如果在计划休眠时刻之前有alarm到来,那么不会启动休眠策略。因此,如果当前mState是ACTIVE,
//则直接返回;否则,
if (mState != STATE_ACTIVE) {
becomeActiveLocked("alarm", Process.myUid());
becomeInactiveIfAppropriateLocked();
}
return;
}
//mState 在服务初始化的时候是STATE_ACTIVE:0,但是switch里面的选项没有STATE_ACTIVE,那么是在 //哪里改变mState?看下文的分析
switch (mState) {
...
}
}
mState 在服务初始化的时候是STATE_ACTIVE,但是switch里面的选项没有STATE_ACTIVE,那么是在哪里改变mState?查询调用发现是在becomeInactiveIfAppropriateLocked里面设置,代码如下:
//从服务初始化状态Activie变成InActive
void becomeInactiveIfAppropriateLocked() {
if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
//两个可能的条件:1.屏幕熄灭 且 没充电 2.人为强制设置
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");
}
}
becomeInactiveIfAppropriateLocked的意思是如果条件合适,则进入INACTIVE状态。检测条件包括:
- mScreenOn屏幕是否熄灭
- mCharging设备是否在充电
- mForceIdle是否人为强制设置
所以当
- 屏幕屏幕熄灭且没充电
- 人为强制设置INACTIVE
两个条件中一个满足,那么再检测当前mState是否为ACTIVE,如果成立则将mState设置为INACTIVE。
那么,becomeInactiveIfAppropriateLocked在什么条件下会触发呢?
- mSensingTimeoutAlarmListener,通过mAlarmManager.set设置
- updateDisplayLocked,在mDisplayListener的onDisplayChanged方法里面调用
- updateChargingLocked,在mReceiver注册的Intent.ACTION_BATTERY_CHANGED里面调用
- handleMotionDetectedLocked,检测是否有运动,如果发生运动,且此时设备没处于ACTIVE状态,那么将mState设置为ACTIVE状态,并设置INACTIVE的超时时间,然后等待下一次没有MOTION发生进入IDLE状态。
- onShellCommand,通过shell命令强制进入
- stepIdleStateLocked,这里回到上文,由于超时时间到来之前会有alarm到来,说明不会进入IDLE,因此这里会先进入ACTIVE状态,然后调用becomeInactiveIfAppropriateLocked检测是否满足进入INACTIVE的条件。
根据上面分析,我们可以推导一个设备从ACTIVE进入IDLE的完整过程:
-
假设一个设备开机之后,mState初始状态为ACTIVE。
-
按下电源按键熄灭屏幕,此时接收到onDisplayChanged,调用updateDisplayLocked,最终调用becomeInactiveIfAppropriateLocked,由于设备没有处于充电状态,因此会进入INACTIVE状态。
-
当长期没有动作响应,会收到onAnyMotionResult,此时会进入文章最初提到的stepIdleStateLocked(“s:stationary”)。
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. startMonitoringMotionLocked(); scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); // Reset the upcoming idle delays. mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT; mNextIdleDelay = mConstants.IDLE_TIMEOUT; mState = STATE_IDLE_PENDING; if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING."); EventLogTags.writeDeviceIdle(mState, reason); break;
首先开启对运动的监测,然后设置alarm超时计时器,最后将mState设置为STATE_IDLE_PENDING,意思是即将进入IDLE状态
-
当mDeepAlarmListener超时到点了,那么会调用stepIdleStateLocked(“s:alarm”)
case STATE_IDLE_PENDING: mState = STATE_SENSING; if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); EventLogTags.writeDeviceIdle(mState, reason); scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT); cancelLocatingLocked(); mNotMoving = false; mLocated = false; mLastGenericLocation = null; mLastGpsLocation = null; mAnyMotionDetector.checkForAnyMotion(); break;
由于此时mState为STATE_IDLE_PENDING,所以会将mState设置为STATE_SENSING,然后通过scheduleSensingTimeoutAlarmLocked设置一个超时计时器。
-
当mDeepAlarmListener超时到点了,那么会调用stepIdleStateLocked(“s:alarm”)
case STATE_SENSING: cancelSensingTimeoutAlarmLocked(); mState = STATE_LOCATING; if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING."); EventLogTags.writeDeviceIdle(mState, reason); 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; } 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) { break; } // Otherwise, we have to move from locating into idle maintenance. case STATE_LOCATING: cancelAlarmLocked(); cancelLocatingLocked(); mAnyMotionDetector.stop(); case STATE_IDLE_MAINTENANCE: scheduleAlarmLocked(mNextIdleDelay, true); if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay + " ms."); mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR); if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay); mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT); if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) { mNextIdleDelay = mConstants.IDLE_TIMEOUT; } mState = STATE_IDLE; if (mLightState != LIGHT_STATE_OVERRIDE) { mLightState = LIGHT_STATE_OVERRIDE; cancelLightAlarmLocked(); } EventLogTags.writeDeviceIdle(mState, reason); addEvent(EVENT_DEEP_IDLE); mGoingIdleWakeLock.acquire(); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON); break;
由于此时mState为STATE_SENSING,可以看到只有当mLocating为true的时候才会break,否则会执行下一个break之前的代码块,可以看到会将mState 设置为STATE_IDLE。至此,设备进入IDLE状态,然后通过handler发送MSG_REPORT_IDLE_ON消息。下面来看看都做了什么动作:
case MSG_REPORT_IDLE_ON_LIGHT: { // mGoingIdleWakeLock is held at this point EventLogTags.writeDeviceIdleOnStart(); final boolean deepChanged; final boolean lightChanged; if (msg.what == MSG_REPORT_IDLE_ON) { //更改WAKELOCK锁的状态 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(); mGoingIdleWakeLock.release(); } break;
- mLocalPowerManager.setDeviceIdleMode(true),最终实现在PowerManagerService
//PowerManagerService.java private void updateWakeLockDisabledStatesLocked() { boolean changed = false; final int numWakeLocks = mWakeLocks.size(); for (int i = 0; i < numWakeLocks; i++) { final WakeLock wakeLock = mWakeLocks.get(i); if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { //更新系统内所有APP申请的WAKELOCK状态,如果是设备处于IDLE状态 //则把WAKELOCK禁止,否则取消禁止 if (setWakeLockDisabledStateLocked(wakeLock)) { changed = true; if (wakeLock.mDisabled) { // This wake lock is no longer being respected. notifyWakeLockReleasedLocked(wakeLock); } else { notifyWakeLockAcquiredLocked(wakeLock); } } } } if (changed) { mDirty |= DIRTY_WAKE_LOCKS; updatePowerStateLocked(); } }
我们知道在doze模式出现之前,我们可以通过在应用端申请wakelock锁来避免系统进入休眠。这里发现doze的最核心的地方正是通过释放系统中所有被申请的wakelock锁来达到系统休眠的目的。
- mNetworkPolicyManager.setDeviceIdleMode(true)
public void setDeviceIdleMode(boolean enabled) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDeviceIdleMode"); try { synchronized (mUidRulesFirstLock) { if (mDeviceIdleMode == enabled) { return; } mDeviceIdleMode = enabled; if (mSystemReady) { // Device idle change means we need to rebuild rules for all // known apps, so do a global refresh. updateRulesForRestrictPowerUL(); } } if (enabled) { EventLogTags.writeDeviceIdleOnPhase("net"); } else { EventLogTags.writeDeviceIdleOffPhase("net"); } } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } }
针对所有app修改网络受限规则,具体规则可以继续跟踪updateRulesForRestrictPowerUL
- 步骤5设备已经开始进入了IDLE状态,但是DOZE的思想并不是让APP一直处于被限制的状态,而是定时让APP恢复常的使用状态。DOZE里面是怎么实现的呢?
case STATE_IDLE: // We have been idling long enough, now it is time to do some work. mActiveIdleOpCount = 1; mActiveIdleWakeLock.acquire(); scheduleAlarmLocked(mNextIdlePendingDelay, false); if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " + "Next alarm in " + mNextIdlePendingDelay + " ms."); mMaintenanceStartTime = SystemClock.elapsedRealtime(); 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; EventLogTags.writeDeviceIdle(mState, reason); addEvent(EVENT_DEEP_MAINTENANCE); //退出IDLE状态 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); break;
步骤5中设置的定时器到点后,走到stepIdleStateLocked,此时mState等于STATE_IDLE,此时会将mState设置为STATE_IDLE_MAINTENANCE,同时发送消息MSG_REPORT_IDLE_OFF,表示设备临时退出IDLE状态。与
MSG_REPORT_IDLE_ON刚好相反,MSG_REPORT_IDLE_OFF执行的动作是:
-
将系统中的WakeLock锁的状态还原到IDLE之前的状态
private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) { if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { boolean disabled = false; //如果处于IDLE状态,将白名单之外的应用申请的WAKELOCK的DISABLE设置为TRUE if (mDeviceIdleMode) { final int appid = UserHandle.getAppId(wakeLock.mOwnerUid); // If we are in idle mode, we will ignore all partial wake locks that are // for application uids that are not whitelisted. if (appid >= Process.FIRST_APPLICATION_UID && Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 && Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 && mUidState.get(wakeLock.mOwnerUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY) > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { disabled = true; } } //更新WAKELOCK锁的DISABLE状态:1.如果是idle状态,会统一改为true2.否则统一改为默认的 //false if (wakeLock.mDisabled != disabled) { wakeLock.mDisabled = disabled; return true; } } return false; }
-
修改网络使用规则,还原到IDLE之前的使用状态
四、总结
DOZE模式核心思想是通过设置定时器,监控设备是否发生动作,位置是否变化来决定要不要让设备进入IDLE状态。上文对实现源码做了简单的分析,里面还有一些细节没有说明,比如:
- 如果实现的监控设备长时间没有动作
- 监听GPS位置是做什么用的
- IDLE状态的网络受限规则具体如何
- IDLE状态下除了APP CPU挂起,网络受限,系统中还有哪些资源受限
- mGoingIdleWakeLock是用来做什么的
先留个疑问吧,后面接着一起学习