Android 8.1 Doze模式分析
概述
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 END
mAbsDeviceIdleController = (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 internet
access 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狀態白名單的app
mPowerSaveWhitelistExceptIdleAppIdArray =
buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
//處於所有的白名單的app,包括使用者新增的
mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
//使用者新增的白名單列表應用
mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
if (mLocalActivityManager != null) {
//將適用於所有情況的白名單列表通知給AMS
mLocalActivityManager.setDeviceIdleWhitelist
(mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalPowerManager != null) {
//將適用於所有情況的白名單列表通知給PMS
mLocalPowerManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalAlarmManager != null) {
//將使用者新增到白名單列表中的應用通知給AlarmManagerService
mLocalAlarmManager.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
//感測器配置為true
if (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、AlarmManagerService
mLocalActivityManager.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() {
@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;
}
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會被滯後(除非在白名單中)
- scheduleAlarmLocked(mNextIdleDelay, true);
而每隔一段時間會進入Maintenance window的時間,此時是通過傳送MSG_REPORT_IDLE_OFF訊息,來恢復網路和wakelock。而這個時候之前設定的mAlarmManager.setIdleUntil的alarm也到期了,因此其他alarm也恢復了。但是這個時間只有5分鐘,重新設定了alarm再次進入deep idle狀態。