手機(jī)也打盹兒之Doze模式源碼分析

科技的仿生學(xué)無(wú)處不在,給予我們啟發(fā)。為了延長(zhǎng)電池是使用壽命,google從蛇的冬眠中得到體會(huì),那就是在某種情況下也讓手機(jī)進(jìn)入類(lèi)冬眠的情況,從而引入了今天的主題,Doze模式,Doze中文是打盹兒,打盹當(dāng)然比活動(dòng)節(jié)約能量了。

手機(jī)打盹兒的時(shí)候會(huì)怎樣呢?####

Doze.png

按照google的官方說(shuō)法,Walklocks,網(wǎng)絡(luò)訪問(wèn),jobshedule,鬧鐘,GPS/WiFi掃描都會(huì)停止。這些停止后,將會(huì)節(jié)省30%的電量。

手機(jī)什么時(shí)候才會(huì)開(kāi)始打盹呢?####

Doze時(shí)序圖.png

上圖是谷歌的Doze時(shí)序示意圖,可以看出讓手機(jī)打盹要滿足三個(gè)條件

1.屏幕熄滅
2 .不插電
3.靜止不動(dòng)

這個(gè)是不是很仿生學(xué)呢?屏幕熄滅->閉上雙眼,不插電->不吃東西,靜止不動(dòng)->安靜地做個(gè)睡美人。生物不也是要滿足這些條件才能打盹嗎?妙,是在妙!

打盹總得呼吸吧?上圖中的maintenance window就是給你呼吸的??!呼吸的時(shí)候Walklocks,網(wǎng)絡(luò)訪問(wèn),jobshedule,鬧鐘,GPS/WiFi掃描這些都會(huì)恢復(fù),來(lái)吧重重的吸一口新鮮空氣吧!隨著時(shí)間的推移,呼吸的間隔會(huì)越變?cè)酱?,而每次呼吸的時(shí)間也會(huì)變長(zhǎng),當(dāng)然,伙計(jì),不會(huì)無(wú)限長(zhǎng)?。∽詈蠖紩?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秒,哈哈哈哈??!

下面給張狀態(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ì)只有鬧鐘能鬧醒你了??!

狀態(tài)轉(zhuǎn)換圖

上面說(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)換
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容