參考:http://blog.csdn.net/gaugamela/article/details/52981984
在Android M中,Google就引入了Doze模式。它定義了一種全新的、低能耗的狀態。
在該狀態,后台僅僅有部分任務被同意執行。其他任務都被強制停止。
在之前的博客中分析過Doze模式。就是device idle狀態。可能有的地方分析的不是非常具體,如今在android7.0上又一次分析下。
一、基本原理
Doze模式能夠簡單概括為:
若推斷用戶在連續的一段時間內沒有使用手機,就延緩終端中APP后台的CPU和網絡活動,以達到降低電量消耗的目的。

上面這張圖比較經典,基本上說明了Doze模式的含義。
圖中的橫軸表示時間。紅色部分表示終端處於喚醒的執行狀態,綠色部分就是Doze模式定義的休眠狀態。
從圖中的描寫敘述,我們能夠看到:假設一個用戶停止充電(on battery: 利用電池供電),關閉屏幕(screen off)。手機處於精巧狀態(stationary: 位置沒有發生相對移動)。保持以上條件一段時間之后,終端就會進入Doze模式。一旦進入Doze模式。系統就降低(延緩)應用對網絡的訪問、以及對CPU的占用,來節省電池電量。
如圖所看到的,Doze模式還定義了maintenance window。
在maintenance window中。系統同意應用完畢它們被延緩的動作。即能夠使用CPU資源及訪問網絡。
從圖中我們能夠看出。當進入Doze模式的條件一直滿足時,Doze模式會定期的進入到maintenance window,但進入的間隔越來越長。
通過這樣的方式,Doze模式能夠使終端處於較長時間的休眠狀態。
須要注意的是:一旦Doze模式的條件不再滿足。即用戶充電、或打開屏幕、或終端的位置發生了移動,終端就恢復到正常模式。
因此,當用戶頻繁使用手機時。Doze模式差點兒是沒有什么實際用處的。
詳細來講,當終端處於Doze模式時。進行了下面操作:
1、暫停網絡訪問。
2、系統忽略全部的WakeLock。
3、標准的AlarmManager alarms被延緩到下一個maintenance window。
但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock時,alarms定義事件仍會啟動。
在這些alarms啟動前,系統會短暫地退出Doze模式。
4、系統不再進行WiFi掃描。
5、系統不同意sync adapters執行。
6、系統不同意JobScheduler執行。
另外我在還有一篇博客中:http://blog.csdn.net/kc58236582/article/details/50554174也具體介紹了Doze模式。能夠參考下,上面有一些命令使用等。
二、DeviceIdleController
Android中的Doze模式主要由DeviceIdleController來控制。
public class DeviceIdleController extends SystemService
implements AnyMotionDetector.DeviceIdleCallback
能夠看出DeviceIdleController繼承自SystemService,是一個系統級的服務。
同一時候,繼承了AnyMotionDetector定義的接口,便於檢測到終端位置變化后進行回調。
2.1 DeviceIdleController的初始化
接下來我們看看它的初始化過程。
private void startOtherServices() {
.........
mSystemServiceManager.startService(DeviceIdleController.class);
.........
}
如上代碼所看到的,SystemServer在startOtherServices中啟動了DeviceIdleController,將先后調用DeviceIdleController的構造函數和onStart函數。
構造函數
public DeviceIdleController(Context context) {
super(context);
//deviceidle.xml用於定義idle模式也能正常工作的非系統應用
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
DeviceIdleController的構造函數比較簡單,就是在創建data/system/deviceidle.xml相應的file文件,同一時候創建一個相應於后台線程的handler。這里的deviceidle.xml能夠在設置中的電池選項那里。
有電池優化,能夠將一些應用放到白名單中,調用DeviceIdleController的addPowerSaveWhitelistApp方法。最后會寫入deviceidle.xml文件,然后在下次開機的時候DeviceIdleController會又一次讀取deviceidle.xml文件然后放入白名單mPowerSaveWhitelistUserApps中。
onStart函數
public void onStart() {
final PackageManager pm = getContext().getPackageManager();
synchronized (this) {
//讀取配置文件,推斷Doze模式是否同意被開啟
mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
//分析PKMS時提到過,PKMS掃描系統文件夾的xml,將形成SystemConfig
SystemConfig sysConfig = SystemConfig.getInstance();
//獲取除了device Idle模式外,都能夠執行的系統應用白名單
ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
for (int i=0; i<allowPowerExceptIdle.size(); i++) {
String pkg = allowPowerExceptIdle.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//獲取device Idle模式下,也能夠執行的系統應用白名單
ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
for (int i=0; i<allowPower.size(); i++) {
String pkg = allowPower.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
// These apps are on both the whitelist-except-idle as well
// as the full whitelist, so they apply in all cases.
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
mPowerSaveWhitelistApps.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIds.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//Constants為deviceIdleController中的內部類,繼承ContentObserver
//監控數據庫變化,同一時候得到Doze模式定義的一些時間間隔
mConstants = new Constants(mHandler, getContext().getContentResolver());
//解析deviceidle.xml,並將當中定義的package相應的app。增加到mPowerSaveWhitelistUserApps中
readConfigFileLocked();
//將白名單的內容給AlarmManagerService和PowerMangerService
//比如:DeviceIdleController推斷開啟Doze模式時,會通知PMS
//此時除去白名單相應的應用外,PMS會將其他全部的WakeLock設置為Disable狀態
updateWhitelistAppIdsLocked();
//下面的初始化,都是如果眼下處在進入Doze模式相反的條件上
mNetworkConnected = true;
mScreenOn = true;
// Start out assuming we are charging. If we aren't, we will at least get
// a battery update the next time the level drops.
mCharging = true;
//Doze模式定義終端初始時為ACTIVE狀態
mState = STATE_ACTIVE;
//屏幕狀態初始時為ACTIVE狀態
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
//公布服務
//BinderService和LocalService均為DeviceIdleController的內部類
mBinderService = new BinderService();
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
publishLocalService(LocalService.class, new LocalService());
}
除去公布服務外。DeviceIdleController在onStart函數中。主要是讀取配置文件更新自己的變量,思路比較清晰。
在這里我們僅跟進一下updateWhitelistAppIdsLocked函數:
private void updateWhitelistAppIdsLocked() {
//構造出除去idle模式外。可執行的app id數組 (可覺得是系統和普通應用的集合)
//mPowerSaveWhitelistAppsExceptIdle從系統文件夾下的xml得到
//mPowerSaveWhitelistUserApps從deviceidle.xml得到。或調用接口增加;
//mPowerSaveWhitelistExceptIdleAppIds並未使用
mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
//構造不受Doze限制的app id數組 (可覺得是系統和普通應用的集合)
//mPowerSaveWhitelistApps從系統文件夾下的xml得到
//mPowerSaveWhitelistAllAppIds並未使用
mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
//構造不受Doze限制的app id數組(僅普通應用的集合)、
//mPowerSaveWhitelistUserAppIds並未使用
mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
if (mLocalPowerManager != null) {
...........
//PMS拿到的是:系統和普通應用組成的不受Doze限制的app id數組
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalAlarmManager != null) {
..........
//AlarmManagerService拿到的是:普通應用組成的不受Doze限制的app id數組
mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
}
}
updateWhitelistAppIdsLocked主要是將白名單交給PMS和AlarmManagerService。
注意Android區分了系統應用白名單、普通應用白名單等,因此上面進行了一些合並操作。這里我們有沒有發現。systemConfig的app不會增加alarm的白名單,而在Settings中電池那邊設置的白名單,會增加Power wakelock的白名單。
onBootPhase函數
與PowerManagerService一樣,DeviceIdleController在初始化的最后一個階段須要調用onBootPhase函數:
public void onBootPhase(int phase) {
//在系統PHASE_SYSTEM_SERVICES_READY階段,進一步完畢一些初始化
if (phase == PHASE_SYSTEM_SERVICES_READY) {
synchronized (this) {
//初始化一些變量
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
..............
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
//依據配置文件,利用SensorManager獲取相應的傳感器,保存到mMotionSensor中
..............
//假設配置文件表明:終端須要預獲取位置信息
//則構造LocationRequest
if (getContext().getResources().getBoolean(
com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
mLocationManager = (LocationManager) getContext().getSystemService(
Context.LOCATION_SERVICE);
mLocationRequest = new LocationRequest()
.setQuality(LocationRequest.ACCURACY_FINE)
.setInterval(0)
.setFastestInterval(0)
.setNumUpdates(1);
}
//依據配置文件。得到角度變化的門限
float angleThreshold = getContext().getResources().getInteger(
com.android.internal.R.integer.config_autoPowerModeThresholdAngle) / 100f;
//創建一個AnyMotionDetector,同一時候將DeviceIdleController注冊到當中
//當AnyMotionDetector檢測到手機變化角度超過門限時。就會回調DeviceIdleController的接口
mAnyMotionDetector = new AnyMotionDetector(
(PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
mHandler, mSensorManager, this, angleThreshold);
//創建兩個經常使用的Intent。用於通知Doze模式的變化
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
//監聽ACTION_BATTERY_CHANGED廣播(電池信息發生改變)
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
//監聽ACTION_PACKAGE_REMOVED廣播(包被移除)
filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mReceiver, filter);
//監聽CONNECTIVITY_ACTION廣播(連接狀態發生改變)
filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getContext().registerReceiver(mReceiver, filter);
//又一次將白名單信息交給PowerManagerService和AlarmManagerService
//這個工作在onStart函數中,已經調用updateWhitelistAppIdsLocked進行過了
//到onBootPhase時。又一次進行一次,可能:一是為了保險。二是,其他進程可能調用接口,更改了相應數據,於是進行更新
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
//監聽屏幕顯示相關的變化
mDisplayManager.registerDisplayListener(mDisplayListener, null);
//更新屏幕顯示相關的信息
updateDisplayLocked();
}
//更新連接狀態相關的信息
updateConnectivityState(null);
}
}
從代碼能夠看出。onBootPhase方法:
主要創建一些本地變量。然后依據配置文件初始化一些傳感器,同一時候注冊了一些廣播接收器和回到接口。
最后更新屏幕顯示和連接狀態相關的信息。
2.2 DeviceIdleController的狀態變化
充電狀態的處理
對於充電狀態,在onBootPhase函數中已經提到。DeviceIdleController監聽了ACTION_BATTERY_CHANGED廣播:
............ IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); getContext().registerReceiver(mReceiver, filter); ...........
我們看看receiver中相應的處理:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
.........
case Intent.ACTION_BATTERY_CHANGED: {
synchronized (DeviceIdleController.this) {
//從廣播中得到是否在充電的消息
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0);
}
} break;
}
}
}。依據上面的代碼,能夠看出當收到電池信息改變的廣播后,DeviceIdleController將得到電源是否在充電的消息,然后調用updateChargingLocked函數進行處理。
void updateChargingLocked(boolean charging) {
.........
if (!charging && mCharging) {
//從充電狀態變為不充電狀態
mCharging = false;
//mForceIdle值一般為false,是通過dumpsys命令將mForceIdle改成true的
if (!mForceIdle) {
//推斷是否進入Doze模式
becomeInactiveIfAppropriateLocked();
}
} else if (charging) {
//進入充電狀態
mCharging = charging;
if (!mForceIdle) {
//手機退出Doze模式
becomeActiveLocked("charging", Process.myUid());
}
}
}
becomeInactiveIfAppropriateLocked函數是開始進入Doze模式,而becomeActiveLocked是退出Doze模式。
顯示狀態處理
DeviceIdleController中注冊了顯示變化的回調
mDisplayManager.registerDisplayListener(mDisplayListener, null);回調會調用updateDisplayLocked函數
private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
@Override public void onDisplayAdded(int displayId) {
}
@Override public void onDisplayRemoved(int displayId) {
}
@Override public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
synchronized (DeviceIdleController.this) {
updateDisplayLocked();
}
}
}
};
updateDisplayLocked函數和更新充電狀態的函數updateChargingLocked類似
void updateDisplayLocked() {
mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
// We consider any situation where the display is showing something to be it on,
// because if there is anything shown we are going to be updating it at some
// frequency so can't be allowed to go into deep sleeps.
boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;
if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
if (!screenOn && mScreenOn) {
mScreenOn = false;
if (!mForceIdle) {//開始進入Doze模式
becomeInactiveIfAppropriateLocked();
}
} else if (screenOn) {//屏幕點亮。退出Doze模式
mScreenOn = true;
if (!mForceIdle) {
becomeActiveLocked("screen", Process.myUid());
}
}
}
becomeActiveLocked函數退出Doze模式
我們先來看看becomeActiveLocked函數
//activeReason記錄的終端變為active的原因
void becomeActiveLocked(String activeReason, int activeUid) {
...........
if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
............
//1、通知PMS等Doze模式結束
scheduleReportActiveLocked(activeReason, activeUid);
//更新DeviceIdleController本地維護的狀態
//在DeviceIdleController的onStart函數中。我們已經知道了
//初始時,mState和mLightState均為Active狀態
mState = STATE_ACTIVE;//state是指設備通過傳感器推斷進入idle
mLightState = LIGHT_STATE_ACTIVE;//mLight是背光的狀態
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
mCurIdleBudget = 0;
mMaintenanceStartTime = 0;
//2、重置一些事件
resetIdleManagementLocked();
resetLightIdleManagementLocked();
addEvent(EVENT_NORMAL);
}
}
scheduleReportActiveLocked函數就是發送MSG_REPORT_ACTIVE消息
void scheduleReportActiveLocked(String activeReason, int activeUid) {
//發送MSG_REPORT_ACTIVE消息
Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
mHandler.sendMessage(msg);
}
我們再看下消息的處理,主要調用了PowerManagerService的setDeviceIdleMode函數來退出Doze狀態,然后又一次更新wakelock的enable狀態。 以及通知NetworkPolicyManagerService不再限制應用上網,還有發送Doze模式改變的廣播。
.........
case MSG_REPORT_ACTIVE: {
.........
//通知PMS Doze模式結束,
//於是PMS將一些Doze模式下。disable的WakeLock又一次enable
//然后調用updatePowerStateLocked函數更新終端的狀態
final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
try {
//通過NetworkPolicyManagerService更改Ip-Rule。不再限制終端應用上網
mNetworkPolicyManager.setDeviceIdleMode(false);
//BSS做好相應的記錄
mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
activeReason, activeUid);
} catch (RemoteException e) {
}
//發送廣播
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
}
}
........
resetIdleManagementLocked函數就是取消alarm。檢測等。
void resetIdleManagementLocked() {
//復位一些狀態變量
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
//停止一些工作,主要是位置檢測相關的
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
stopMonitoringMotionLocked();
mAnyMotionDetector.stop();
}
becomeInactiveIfAppropriateLocked函數開始進入Doze模式
becomeInactiveIfAppropriateLocked函數就是我們開始進入Doze模式的第一個步驟。以下我們就具體分析這個函數
void becomeInactiveIfAppropriateLocked() {
.................
//屏幕熄滅。未充電
if ((!mScreenOn && !mCharging) || mForceIdle) {
// Screen has turned off; we are now going to become inactive and start
// waiting to see if we will ultimately go idle.
if (mState == STATE_ACTIVE && mDeepEnabled) {
mState = STATE_INACTIVE;
...............
//重置事件
resetIdleManagementLocked();
//開始檢測能否夠進入Doze模式的Idle狀態
//若終端沒有watch feature, mInactiveTimeout時間為30min
scheduleAlarmLocked(mInactiveTimeout, false);
...............
}
if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
mLightState = LIGHT_STATE_INACTIVE;
.............
resetLightIdleManagementLocked();//重置事件
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
}
}
}
要進入Doze流程,就是調用這個函數,首先要保證屏幕滅屏然后沒有充電。這里還有mDeepEnable和mLightEnable前面說過是在配置中定義的,一般默認是關閉(也就是不開Doze模式)。這里mLightEnabled是相應禁止wakelock持鎖的,禁止網絡。
而mDeepEnabled相應是檢測設備是否精巧,除了禁止wakelock、禁止網絡、還會機制alarm。
light idle模式
我們先看light idle模式,這個模式下、會禁止網絡、wakelock。可是不會禁止alarm。
我們先看scheduleLightAlarmLocked函數。這里設置了一個alarm。delay是5分鍾。
到時間后調用mLightAlarmListener回調。
void scheduleLightAlarmLocked(long delay) {
if (DEBUG) Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + ")");
mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
mLightAlarmListener就是進入lightidle,調用stepLightIdleStateLocked函數
private final AlarmManager.OnAlarmListener mLightAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (DeviceIdleController.this) {
stepLightIdleStateLocked("s:alarm");
}
}
};
我們來看stepLightIdleStateLocked函數,這個函數會處理mLightState不同狀態。會依據不同狀態,然后設置alarm。到時間后繼續處理下個狀態。到LIGHT_STATE_IDLE_MAINTENANCE狀態處理時。會發送MSG_REPORT_IDLE_ON_LIGHT。這個消息的處理會禁止網絡、禁止wakelock。然后到LIGHT_STATE_WAITING_FOR_NETWORK,會先退出Doze狀態(這個時候網絡、wakelock恢復)。然后設置alarm。alarm時間到后,還是在LIGHT_STATE_IDLE_MAINTENANCE狀態。
和之前一樣(禁止網絡、wakelock)。
僅僅是設置的alarm間隔會越來越大。也就是僅僅要屏幕滅屏后。時間越長。設備會隔越來越長的時間才會退出Doze狀態。這也符合一個實際情況,可是會有一個上限值。
void stepLightIdleStateLocked(String reason) {
if (mLightState == LIGHT_STATE_OVERRIDE) {
// If we are already in deep device idle mode, then
// there is nothing left to do for light mode.
return;
}
EventLogTags.writeDeviceIdleLightStep();
switch (mLightState) {
case LIGHT_STATE_INACTIVE:
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
// Reset the upcoming idle delays.
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
mMaintenanceStartTime = 0;
if (!isOpsInactiveLocked()) {
// We have some active ops going on... give them a chance to finish
// before going in to our first idle.
mLightState = LIGHT_STATE_PRE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);//設置alarm,時間到后到下個步驟
break;
}
// Nothing active, fall through to immediately idle.
case LIGHT_STATE_PRE_IDLE:
case LIGHT_STATE_IDLE_MAINTENANCE:
if (mMaintenanceStartTime != 0) {
long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
// We didn't use up all of our minimum budget; add this to the reserve.
mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
} else {
// We used more than our minimum budget; this comes out of the reserve.
mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
}
}
mMaintenanceStartTime = 0;
scheduleLightAlarmLocked(mNextLightIdleDelay);
mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
(long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
}
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
mLightState = LIGHT_STATE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
addEvent(EVENT_LIGHT_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);//發送消息。這個消息處理就會關閉網絡,禁止wakelock
break;
case LIGHT_STATE_IDLE:
case LIGHT_STATE_WAITING_FOR_NETWORK:
if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
mMaintenanceStartTime = SystemClock.elapsedRealtime();
if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
} else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
}
scheduleLightAlarmLocked(mCurIdleBudget);
if (DEBUG) Slog.d(TAG,
"Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
addEvent(EVENT_LIGHT_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);//醒一下(開啟網絡、恢復wakelock)
} else {
// We'd like to do maintenance, but currently don't have network
// connectivity... let's try to wait until the network comes back.
// We'll only wait for another full idle period, however, and then give up.
scheduleLightAlarmLocked(mNextLightIdleDelay);
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
}
break;
}
}
可是這里僅僅是一個light idle。一旦進入deep idle,light idle設置的alarm會無效的(這個后面細說)。也就是說light idle一旦進入deep idle后無效了(由於idle step主要靠alarm驅動,而alarm無效后自然就驅動不了)。
deep idle模式
以下我們再來看deep idle模式,這個模式除了禁止網絡、wakelock還會禁止alarm。
我們再來看becomeInactiveIfAppropriateLocked函數中以下代碼。是關於deep idle的設置 這里的mInactiveTimeout是半小時
void becomeInactiveIfAppropriateLocked() {
if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
if ((!mScreenOn && !mCharging) || mForceIdle) {
// Screen has turned off; we are now going to become inactive and start
// waiting to see if we will ultimately go idle.
if (mState == STATE_ACTIVE && mDeepEnabled) {
mState = STATE_INACTIVE;
if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
resetIdleManagementLocked();
scheduleAlarmLocked(mInactiveTimeout, false);
EventLogTags.writeDeviceIdle(mState, "no activity");
}
我們來看下scheduleAlarmLocked函數,注意假設這里參數idleUntil是true會調用AlarmManager的setIdleUntil函數,調用這個函數后普通應用設置alarm將失效。
void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (mMotionSensor == null) {
//在onBootPhase時,獲取過位置檢測傳感器
//假設終端沒有配置位置檢測傳感器,那么終端永遠不會進入到真正的Doze ilde狀態
// If there is no motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving.
return;
}
mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
if (idleUntil) {
//此時IdleUtil的值為false
mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
} else {
//30min后喚醒,調用mDeepAlarmListener的onAlarm函數
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
}
}
須要注意的是,DeviceIdleController一直在監控屏幕狀態和充電狀態,一但不滿足Doze模式的條件。前面提到的becomeActiveLocked函數就會被調用。
mAlarmManager設置的定時喚醒事件將被取消掉,mDeepAlarmListener的onAlarm函數不會被調用。
因此,我們知道了終端必須保持Doze模式的入口條件長達30min,才會進入mDeepAlarmListener.onAlarm:
private final AlarmManager.OnAlarmListener mDeepAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (DeviceIdleController.this) {
//進入到stepIdleStateLocked函數
stepIdleStateLocked("s:alarm");
}
}
};
以下我們就來看下stepIdleStateLocked函數:
void stepIdleStateLocked(String reason) {
..........
final long now = SystemClock.elapsedRealtime();
//個人認為。以下這段代碼。是針對Idle狀態設計的
//假設在Idle狀態收到Alarm,那么將先喚醒終端,然后又一次推斷是否須要進入Idle態
//在介紹Doze模式原理時提到過,若應用調用AlarmManager的一些指定接口,仍然能夠在Idle狀態進行工作
if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
// Whoops, there is an upcoming alarm. We don't actually want to go idle.
if (mState != STATE_ACTIVE) {
becomeActiveLocked("alarm", Process.myUid());
becomeInactiveIfAppropriateLocked();
}
return;
}
//以下是Doze模式的狀態轉變相關的代碼
switch (mState) {
case STATE_INACTIVE:
// We have now been inactive long enough, it is time to start looking
// for motion and sleep some more while doing so.
//保持屏幕熄滅。同一時候未充電達到30min,進入此分支
//注冊一個mMotionListener,檢測是否移動
//假設檢測到移動,將又一次進入到ACTIVE狀態
//對應代碼比較直觀,此處不再深入分析
startMonitoringMotionLocked();
//再次調用scheduleAlarmLocked函數,此次的時間仍為30min
//也就說假設不發生退出Doze模式的事件。30min后將再次進入到stepIdleStateLocked函數
//只是屆時的mState已經變為STATE_IDLE_PENDING
scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
// Reset the upcoming idle delays.
//mNextIdlePendingDelay為5min
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
//mNextIdleDelay為60min
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
//狀態變為STATE_IDLE_PENDING
mState = STATE_IDLE_PENDING;
............
break;
case STATE_IDLE_PENDING:
//保持息屏、未充電、精巧狀態,經過30min后。進入此分支
mState = STATE_SENSING;
//保持Doze模式條件,4min后再次進入stepIdleStateLocked
scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
//停止定位相關的工作
cancelLocatingLocked();
mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
//開始檢測手機是否發生運動(這里應該是更仔細的側重於角度的變化)
//若手機運動過。則又一次變為active狀態
mAnyMotionDetector.checkForAnyMotion();
break;
case STATE_SENSING:
//上面的條件滿足后。進入此分支。開始獲取定位信息
cancelSensingTimeoutAlarmLocked();
mState = STATE_LOCATING;
............
//保持條件30s,再次調用stepIdleStateLocked
scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
//網絡定位
if (mLocationManager != null
&& mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
mLocationManager.requestLocationUpdates(mLocationRequest,
mGenericLocationListener, mHandler.getLooper());
mLocating = true;
} else {
mHasNetworkLocation = false;
}
//GPS定位
if (mLocationManager != null
&& mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
mHasGps = true;
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
mGpsLocationListener, mHandler.getLooper());
mLocating = true;
} else {
mHasGps = false;
}
// If we have a location provider, we're all set, the listeners will move state
// forward.
if (mLocating) {
//無法定位則直接進入下一個case
break;
}
case STATE_LOCATING:
//停止定位和運動檢測,直接進入到STATE_IDLE_MAINTENANCE
cancelAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.stop();
case STATE_IDLE_MAINTENANCE:
//進入到這個case后,終端開始進入Idle狀態,也就是真正的Doze模式
//定義退出Idle的時間此時為60min
scheduleAlarmLocked(mNextIdleDelay, true);
............
//退出周期逐步遞增,每次乘2
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
...........
//周期有最大值6h
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
}
mState = STATE_IDLE;
...........
//通知PMS、NetworkPolicyManagerService等Doze模式開啟。即進入Idle狀態
//此時PMS disable一些非白名單WakeLock。NetworkPolicyManagerService開始限制一些應用的網絡訪問
//消息處理的詳細流程比較直觀,此處不再深入分析
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE:
//進入到這個case時,本次的Idle狀態臨時結束,開啟maintenance window
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
mActiveIdleWakeLock.acquire();
//定義又一次進入Idle的時間為5min (也就是手機可處於Maintenance window的時間)
scheduleAlarmLocked(mNextIdlePendingDelay, false);
mMaintenanceStartTime = SystemClock.elapsedRealtime();
//調整mNextIdlePendingDelay。乘2(最大為10min)
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
}
mState = STATE_IDLE_MAINTENANCE;
...........
//通知PMS等臨時退出了Idle狀態。能夠進行一些工作
//此時PMS enable一些非白名單WakeLock;NetworkPolicyManagerService開始同意應用的網絡訪問
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}
上面的流程在凝視里面已經非常明確了,而我們在進入Deep idle時。發送了一個MSG_REPORT_IDLE_ON消息。我們看以下這個消息的處理和之前的MSG_REPORT_IDLE_ON_LIGHT一樣的。關閉網絡。禁止wakelock。
case MSG_REPORT_IDLE_ON:
case MSG_REPORT_IDLE_ON_LIGHT: {
EventLogTags.writeDeviceIdleOnStart();
final boolean deepChanged;
final boolean lightChanged;
if (msg.what == MSG_REPORT_IDLE_ON) {
deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
} else {
deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
}
try {
mNetworkPolicyManager.setDeviceIdleMode(true);
mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
? BatteryStats.DEVICE_IDLE_MODE_DEEP
: BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
} catch (RemoteException e) {
}
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
}
EventLogTags.writeDeviceIdleOnComplete();
} break;
而禁止alarm是通過調用例如以下函數,注意參數是true。參數是true會調用mAlarmManager.setIdleUntil函數。這樣其它的alarm會被滯后(除非在白名單中)
scheduleAlarmLocked(mNextIdleDelay, true);
而每隔一段時間會進入Maintenance window的時間,此時是通過發送MSG_REPORT_IDLE_OFF消息,來恢復網絡和wakelock。
而這個時候之前設置的mAlarmManager.setIdleUntil的alarm也到期了,因此其它alarm也恢復了。
可是這個時間僅僅有5分鍾,又一次設置了alarm再次進入deep idle狀態。
Idle總結
當手機關閉屏幕或者拔掉電源的時候。手機開始推斷是否進入Doze模式。
Doze模式分兩種,第一種是light idle:
1.light idle
light idle在手機滅屏且沒有充電狀態下。5分鍾開始進入light idle流程。
然后第一次進入LIGHT_STATE_INACTIVE流程時,會再定義一個10分鍾的alarm。然后系統進入light idle狀態。這個狀態會使不是白名單的應用禁止訪問網絡,以及持wakelock鎖。
2.deep idle
deep idle除了light idle的狀態還會把非白名單中應用的alarm也禁止了。
此時,系統中非白名單的應用將被禁止訪問網絡,它們申請的Wakelock也會被disable。
從上面的代碼能夠看出,系統會周期性的退出Idle狀態。進入到MAINTENANCE狀態。集中處理相關的任務。
一段時間后,會又一次再次回到IDLE狀態。
每次進入IDLE狀態,停留的時間都會是上次的2倍。最大時間限制為6h。
當手機運動。或者點亮屏幕,插上電源等。系統都會又一次返回到ACTIVIE狀態。
這里盜用別人的一樣圖,但不過deep idle的狀態:

(這里特別說明下,alarm和wakelock都是由DeviceIdleController主動調用相關接口設置的。而網絡是調用了DeviceIdleController的getAppIdWhitelist接口來獲取應用的白名單的,從而禁止非白名單訪問網絡。)
網絡我們不分析了,之前我們在Android6.0時分析過idle狀態下alarm和wakelock。7.0略微有點不一樣,以下兩篇博客又一次分析下吧。
