參考: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略微有點不一樣,以下兩篇博客又一次分析下吧。