Android Doze模式源碼分析,androiddoze
科技的仿生學無處不在,給予我們啟發。為了延長電池是使用壽命,google從蛇的冬眠中得到體會,那就是在某種情況下也讓手機進入類冬眠的情況,從而引入了今天的主題,Doze模式,Doze中文是打盹兒,打盹當然比活動節約能量了。
手機打盹兒的時候會怎樣呢?
按照google的官方說法,Walklocks,網路訪問,jobshedule,鬧鐘,GPS/WiFi掃描都會停止。這些停止後,將會節省30%的電量。
手機什麼時候才會開始打盹呢?
是Google的Doze時序,可以看出讓手機打盹要滿足三個條件
1.螢幕熄滅
2 .不插電
3.靜止不動
這個是不是很仿生學呢?螢幕熄滅->閉上雙眼,不插電->不吃東西,靜止不動->安靜地做個睡美人。生物不也是要滿足這些條件才能打盹嗎?妙,是在妙!
打盹總得呼吸吧?中的maintenance window就是給你呼吸的!!呼吸的時候Walklocks,網路訪問,jobshedule,鬧鐘,GPS/WiFi掃描這些都會恢複,來吧重重的吸一口新鮮空氣吧!隨著時間的推移,呼吸的間隔會越變越大,而每次呼吸的時間也會變長,當然,夥計,不會無限長!!最後都會歸於一個定值。下面分析源碼就知道了,biu!
沒源碼,說個球兒
下面以一台手機靜靜地放在案頭上,隨著時間的推移,進入doze模式的過程來分析源碼。
源碼路徑:/frameworks/base/services/core/java/com/android/server/DeviceIdleController.java
系統中用一個全域整形變數來表示當前doze的狀態
1 private int mState;
狀態值的可能取值有以下,一開始的狀態是STATE_ACTIVE。會依次經過1,2,3,4,狀態後進入5狀態,即STATE_IDLE
1 private static final int STATE_ACTIVE = 0;2 private static final int STATE_INACTIVE = 1;3 private static final int STATE_IDLE_PENDING = 2;4 private static final int STATE_SENSING = 3;5 private static final int STATE_LOCATING = 4;6 private static final int STATE_IDLE = 5;7 private static final int STATE_IDLE_MAINTENANCE = 6;
首先螢幕熄滅,回調熄屏處理函數
1 private final DisplayManager.DisplayListener mDisplayListener 2 = new DisplayManager.DisplayListener() { 3 @Override public void onDisplayAdded(int displayId) { 4 } 5 6 @Override public void onDisplayRemoved(int displayId) { 7 } 8 9 @Override public void onDisplayChanged(int displayId) {10 if (displayId == Display.DEFAULT_DISPLAY) {11 synchronized (DeviceIdleController.this) {12 updateDisplayLocked(); //螢幕狀態改變13 }14 }15 }16 };
進入updateDisplayLocked
1 void updateDisplayLocked() {2 ...3 becomeInactiveIfAppropriateLocked(); //看是否可以進入Inactive狀態4 ....5 }6 }
然後我們拔出usb,不充電,會回調充電處理函數
1 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 2 @Override public void onReceive(Context context, Intent intent) { 3 if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { 4 int plugged = intent.getIntExtra("plugged", 0); 5 updateChargingLocked(plugged != 0); //充電狀態改變 6 } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) { 7 synchronized (DeviceIdleController.this) { 8 stepIdleStateLocked(); 9 }10 }11 }12 };
進入updateChargingLocked
1 void updateChargingLocked(boolean charging) {2 ....3 becomeInactiveIfAppropriateLocked();//看是否可以進入Inactive狀態4 .....5 }
最後不插電和熄滅螢幕後都會進入becomeInactiveIfAppropriateLocked,狀態mState變成STATE_INACTIVE,並且開啟了一個定時器
1 void becomeInactiveIfAppropriateLocked() { 2 if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()"); 3 //不插電和螢幕熄滅的條件都滿足了 4 if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) { 5 ..... 6 mState = STATE_INACTIVE; 7 scheduleAlarmLocked(mInactiveTimeout, false); 8 ...... 9 }10 }11 12 定時時間長度為常量30分鐘13 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,14 !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
手機靜靜地躺在案頭上30分鐘後,定時器時間到達後,pendingintent會被發出,廣播接收器進行處理
1 Intent intent = new Intent(ACTION_STEP_IDLE_STATE) 2 .setPackage("android") 3 .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 4 mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); 5 6 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 7 @Override public void onReceive(Context context, Intent intent) { 8 if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { 9 int plugged = intent.getIntExtra("plugged", 0);10 updateChargingLocked(plugged != 0);11 } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {12 synchronized (DeviceIdleController.this) {13 stepIdleStateLocked(); //接收到廣播14 }15 }16 }17 };
進入stepIdleStateLocked,該函數是狀態轉換處理的主要函數
1 void stepIdleStateLocked() { 2 if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState); 3 EventLogTags.writeDeviceIdleStep(); 4 5 final long now = SystemClock.elapsedRealtime(); 6 if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) { 7 // Whoops, there is an upcoming alarm. We don't actually want to go idle. 8 if (mState != STATE_ACTIVE) { 9 becomeActiveLocked("alarm", Process.myUid());10 }11 return;12 }13 14 switch (mState) {15 case STATE_INACTIVE:16 // We have now been inactive long enough, it is time to start looking17 // for significant motion and sleep some more while doing so.18 startMonitoringSignificantMotion(); //觀察是否有小動作19 scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); //設定觀察小動作要觀察多久20 mState = STATE_IDLE_PENDING; //狀態更新為STATE_IDLE_PENDING21 break;22 case STATE_IDLE_PENDING: //小動作觀察結束,很厲害,一直都沒有小動作,會進入這裡23 mState = STATE_SENSING;//狀態更新為STATE_SENSING24 scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);//設定感應器感應時間長度25 mAnyMotionDetector.checkForAnyMotion(); //感應器感應手機有沒有動26 break;27 case STATE_SENSING: //感應器也沒發現手機動,就來最後一發,看GPS有沒有動28 mState = STATE_LOCATING;//狀態更新為STATE_LOCATING29 scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);//設定GPS觀察時間長度30 mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,31 mHandler.getLooper());//GPS開始感應32 break;33 case STATE_LOCATING: //GPS也發現沒動34 cancelSensingAlarmLocked();35 cancelLocatingLocked();36 mAnyMotionDetector.stop(); //這裡沒有break,直接進入下一個case37 case STATE_IDLE_MAINTENANCE:38 scheduleAlarmLocked(mNextIdleDelay, true);//設定打盹多久後進行呼吸39 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);//更新下次打盹多久後進行呼吸40 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);41 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);42 mState = STATE_IDLE; //噢耶 終於進入了STATE_IDLE43 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);44 break;45 case STATE_IDLE: //打盹完了,呼吸一下就是這裡了46 scheduleAlarmLocked(mNextIdlePendingDelay, false);47 mState = STATE_IDLE_MAINTENANCE; //狀態更新為STATE_IDLE_MAINTENANCE48 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,49 (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));50 //更新下次呼吸的時間51 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);52 break;53 }54 }
Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
這一句看到了嗎?取最小值,這裡就是保證了idle和視窗的時間不會變成無限大。
為了讓各位有個感官的體驗,上面的一些時間我直接列出來吧
熄屏不插電進入INACTIVE時間上面說了30分鐘觀察小動作的時間30分鐘IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT, !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);觀察感應器的時間4分鐘SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT, !DEBUG ? 4 * 60 * 1000L : 60 * 1000L);觀察GPS的時間30秒LOCATING_TIMEOUT = mParser.getLong(KEY_LOCATING_TIMEOUT, !DEBUG ? 30 * 1000L : 15 * 1000L);
所以進入idle的總時間為30分鐘+30分鐘+4分鐘+30s=1小時4分鐘30秒,哈哈哈哈!!
下面給張狀態轉換圖看看,沒到達idle狀態前,基本上有什麼風吹草動都會變回ACTIVE狀態。而變成IDLE狀態後,只能插電或者點亮螢幕才離開IDLE狀態。就像人入睡前,很容易被吵醒,而深度入眠後,估計只有鬧鐘能鬧醒你了!!
上面說了這麼多,跟我應用開發有什麼關係?
其實,沒多大關係,看下源碼不行噻。
不過作為一種新的機制,最好測試下你的應用在這幾種狀態下是否能夠正常運行,起碼不能掛掉啊。
google提供了adb的指令來強制變換狀態,這樣你就不用乾等著它狀態變化了。
1 adb shell dumpsys battery unplug //相當於不插電2 adb shell dumpsys device idle step //讓狀態轉換