科技的仿生學(xué)無(wú)處不在瓦侮,給予我們啟發(fā)异旧。為了延長(zhǎng)電池是使用壽命意述,google從蛇的冬眠中得到體會(huì),那就是在某種情況下也讓手機(jī)進(jìn)入類冬眠的情況吮蛹,從而引入了今天的主題荤崇,Doze模式,Doze中文是打盹兒潮针,打盹當(dāng)然比活動(dòng)節(jié)約能量了术荤。
手機(jī)打盹兒的時(shí)候會(huì)怎樣呢?####
按照google的官方說(shuō)法每篷,Walklocks瓣戚,網(wǎng)絡(luò)訪問(wèn),jobshedule雳攘,鬧鐘,GPS/WiFi掃描都會(huì)停止枫笛。這些停止后吨灭,將會(huì)節(jié)省30%的電量。
手機(jī)什么時(shí)候才會(huì)開(kāi)始打盹呢刑巧?####
上圖是谷歌的Doze時(shí)序示意圖喧兄,可以看出讓手機(jī)打盹要滿足三個(gè)條件
1.屏幕熄滅
2 .不插電
3.靜止不動(dòng)
這個(gè)是不是很仿生學(xué)呢?屏幕熄滅->閉上雙眼啊楚,不插電->不吃東西吠冤,靜止不動(dòng)->安靜地做個(gè)睡美人。生物不也是要滿足這些條件才能打盹嗎恭理?妙拯辙,是在妙!
打盹總得呼吸吧?上圖中的maintenance window就是給你呼吸的Q谋!诉濒!呼吸的時(shí)候Walklocks,網(wǎng)絡(luò)訪問(wèn)夕春,jobshedule未荒,鬧鐘,GPS/WiFi掃描這些都會(huì)恢復(fù)及志,來(lái)吧重重的吸一口新鮮空氣吧片排!隨著時(shí)間的推移,呼吸的間隔會(huì)越變?cè)酱笏俪蓿看魏粑臅r(shí)間也會(huì)變長(zhǎng)率寡,當(dāng)然,伙計(jì)锌畸,不會(huì)無(wú)限長(zhǎng)S铝印!最后都會(huì)歸于一個(gè)定值潭枣。下面分析源碼就知道了比默,biu!
沒(méi)源碼盆犁,說(shuō)個(gè)球兒####
下面以一臺(tái)手機(jī)靜靜地放在桌面上命咐,隨著時(shí)間的推移,進(jìn)入doze模式的過(guò)程來(lái)分析源碼谐岁。
源碼路徑:/frameworks/base/services/core/java/com/android/server/DeviceIdleController.java
系統(tǒng)中用一個(gè)全局整形變量來(lái)表示當(dāng)前doze的狀態(tài)
private int mState;
狀態(tài)值的可能取值有以下,一開(kāi)始的狀態(tài)是STATE_ACTIVE醋奠。會(huì)依次經(jīng)過(guò)1,2,3,4,狀態(tài)后進(jìn)入5狀態(tài),即STATE_IDLE
private static final int STATE_ACTIVE = 0;
private static final int STATE_INACTIVE = 1;
private static final int STATE_IDLE_PENDING = 2;
private static final int STATE_SENSING = 3;
private static final int STATE_LOCATING = 4;
private static final int STATE_IDLE = 5;
private static final int STATE_IDLE_MAINTENANCE = 6;
首先屏幕熄滅伊佃,回調(diào)熄屏處理函數(shù)
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(); //屏幕狀態(tài)改變
}
}
}
};
進(jìn)入updateDisplayLocked
void updateDisplayLocked() {
...
becomeInactiveIfAppropriateLocked(); //看是否可以進(jìn)入Inactive狀態(tài)
....
}
}
然后我們拔出usb窜司,不充電,會(huì)回調(diào)充電處理函數(shù)
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0); //充電狀態(tài)改變
} else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
stepIdleStateLocked();
}
}
}
};
進(jìn)入updateChargingLocked
void updateChargingLocked(boolean charging) {
....
becomeInactiveIfAppropriateLocked();//看是否可以進(jìn)入Inactive狀態(tài)
.....
}
最后不插電和熄滅屏幕后都會(huì)進(jìn)入becomeInactiveIfAppropriateLocked航揉,狀態(tài)mState變成STATE_INACTIVE塞祈,并且開(kāi)啟了一個(gè)定時(shí)器
void becomeInactiveIfAppropriateLocked() {
if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
//不插電和屏幕熄滅的條件都滿足了
if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {
.....
mState = STATE_INACTIVE;
scheduleAlarmLocked(mInactiveTimeout, false);
......
}
}
定時(shí)時(shí)長(zhǎng)為常量30分鐘
INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
手機(jī)靜靜地躺在桌面上30分鐘后,定時(shí)器時(shí)間到達(dá)后帅涂,pendingintent會(huì)被發(fā)出,廣播接收器進(jìn)行處理
Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
.setPackage("android")
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0);
} else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
stepIdleStateLocked(); //接收到廣播
}
}
}
};
進(jìn)入stepIdleStateLocked,該函數(shù)是狀態(tài)轉(zhuǎn)換處理的主要函數(shù)
void stepIdleStateLocked() {
if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
EventLogTags.writeDeviceIdleStep();
final long now = SystemClock.elapsedRealtime();
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());
}
return;
}
switch (mState) {
case STATE_INACTIVE:
// We have now been inactive long enough, it is time to start looking
// for significant motion and sleep some more while doing so.
startMonitoringSignificantMotion(); //觀察是否有小動(dòng)作
scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); //設(shè)置觀察小動(dòng)作要觀察多久
mState = STATE_IDLE_PENDING; //狀態(tài)更新為STATE_IDLE_PENDING
break;
case STATE_IDLE_PENDING: //小動(dòng)作觀察結(jié)束议薪,很厲害,一直都沒(méi)有小動(dòng)作媳友,會(huì)進(jìn)入這里
mState = STATE_SENSING;//狀態(tài)更新為STATE_SENSING
scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);//設(shè)置傳感器感應(yīng)時(shí)長(zhǎng)
mAnyMotionDetector.checkForAnyMotion(); //傳感器感應(yīng)手機(jī)有沒(méi)有動(dòng)
break;
case STATE_SENSING: //傳感器也沒(méi)發(fā)現(xiàn)手機(jī)動(dòng)斯议,就來(lái)最后一發(fā),看GPS有沒(méi)有動(dòng)
mState = STATE_LOCATING;//狀態(tài)更新為STATE_LOCATING
scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);//設(shè)置GPS觀察時(shí)長(zhǎng)
mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,
mHandler.getLooper());//GPS開(kāi)始感應(yīng)
break;
case STATE_LOCATING: //GPS也發(fā)現(xiàn)沒(méi)動(dòng)
cancelSensingAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.stop(); //這里沒(méi)有break醇锚,直接進(jìn)入下一個(gè)case
case STATE_IDLE_MAINTENANCE:
scheduleAlarmLocked(mNextIdleDelay, true);//設(shè)置打盹多久后進(jìn)行呼吸
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);//更新下次打盹多久后進(jìn)行呼吸
if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
mState = STATE_IDLE; //噢耶 終于進(jìn)入了STATE_IDLE
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE: //打盹完了哼御,呼吸一下就是這里了
scheduleAlarmLocked(mNextIdlePendingDelay, false);
mState = STATE_IDLE_MAINTENANCE; //狀態(tài)更新為STATE_IDLE_MAINTENANCE
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
//更新下次呼吸的時(shí)間
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}
Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
這一句看到了嗎?取最小值,這里就是保證了idle和窗口的時(shí)間不會(huì)變成無(wú)限大艇搀。
為了讓各位有個(gè)感官的體驗(yàn)尿扯,上面的一些時(shí)間我直接列出來(lái)吧
熄屏不插電進(jìn)入INACTIVE時(shí)間上面說(shuō)了30分鐘
觀察小動(dòng)作的時(shí)間30分鐘
IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
觀察傳感器的時(shí)間4分鐘
SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
!DEBUG ? 4 * 60 * 1000L : 60 * 1000L);
觀察GPS的時(shí)間30秒
LOCATING_TIMEOUT = mParser.getLong(KEY_LOCATING_TIMEOUT,
!DEBUG ? 30 * 1000L : 15 * 1000L);
所以進(jìn)入idle的總時(shí)間為30分鐘+30分鐘+4分鐘+30s=1小時(shí)4分鐘30秒,哈哈哈哈Q娴瘛衷笋!
下面給張狀態(tài)轉(zhuǎn)換圖看看,沒(méi)到達(dá)idle狀態(tài)前矩屁,基本上有什么風(fēng)吹草動(dòng)都會(huì)變回ACTIVE狀態(tài)辟宗。而變成IDLE狀態(tài)后,只能插電或者點(diǎn)亮屏幕才離開(kāi)IDLE狀態(tài)吝秕。就像人入睡前泊脐,很容易被吵醒,而深度入眠后烁峭,估計(jì)只有鬧鐘能鬧醒你了H菘汀!
上面說(shuō)了這么多约郁,跟我應(yīng)用開(kāi)發(fā)有什么關(guān)系缩挑?####
其實(shí),沒(méi)多大關(guān)系鬓梅,看下源碼不行噻供置。
不過(guò)作為一種新的機(jī)制,最好測(cè)試下你的應(yīng)用在這幾種狀態(tài)下是否能夠正常運(yùn)行绽快,起碼不能掛掉啊芥丧。
google提供了adb的指令來(lái)強(qiáng)制變換狀態(tài),這樣你就不用干等著它狀態(tài)變化了坊罢。
adb shell dumpsys battery unplug //相當(dāng)于不插電
adb shell dumpsys device idle step //讓狀態(tài)轉(zhuǎn)換