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

科技的仿生學(xué)無(wú)處不在瓦侮,給予我們啟發(fā)异旧。為了延長(zhǎng)電池是使用壽命意述,google從蛇的冬眠中得到體會(huì),那就是在某種情況下也讓手機(jī)進(jìn)入類冬眠的情況吮蛹,從而引入了今天的主題荤崇,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就是給你呼吸的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菘汀!

狀態(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)系作者
  • 序言:七十年代末续担,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子活孩,更是在濱河造成了極大的恐慌物遇,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诱鞠,死亡現(xiàn)場(chǎng)離奇詭異挎挖,居然都是意外死亡这敬,警方通過(guò)查閱死者的電腦和手機(jī)航夺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)崔涂,“玉大人阳掐,你說(shuō)我怎么就攤上這事。” “怎么了缭保?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵汛闸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我艺骂,道長(zhǎng)诸老,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任钳恕,我火速辦了婚禮别伏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忧额。我一直安慰自己厘肮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布睦番。 她就那樣靜靜地躺著类茂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪托嚣。 梳的紋絲不亂的頭發(fā)上巩检,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音注益,去河邊找鬼碴巾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛丑搔,可吹牛的內(nèi)容都是我干的厦瓢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼啤月,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼煮仇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起谎仲,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浙垫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后郑诺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夹姥,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年辙诞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辙售。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飞涂,死狀恐怖旦部,靈堂內(nèi)的尸體忽然破棺而出祈搜,到底是詐尸還是另有隱情,我是刑警寧澤士八,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布容燕,位于F島的核電站,受9級(jí)特大地震影響婚度,放射性物質(zhì)發(fā)生泄漏蘸秘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一蝗茁、第九天 我趴在偏房一處隱蔽的房頂上張望秘血。 院中可真熱鬧,春花似錦评甜、人聲如沸灰粮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粘舟。三九已至,卻和暖如春佩研,著一層夾襖步出監(jiān)牢的瞬間柑肴,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工旬薯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晰骑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓绊序,卻偏偏與公主長(zhǎng)得像硕舆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骤公,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 本文主要圍繞如下問(wèn)題進(jìn)行知識(shí)收集整理: 待機(jī)抚官、睡眠與休眠的區(qū)別? Android開(kāi)發(fā)者官網(wǎng)當(dāng)中提到“idle...
    GrayMonkey閱讀 12,397評(píng)論 4 23
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評(píng)論 25 707
  • 從Android6.0(API23)開(kāi)始, Google為Android加入了兩種省電特性阶捆,通過(guò)管理Android...
    PANWCS閱讀 7,076評(píng)論 2 10
  • 今天本來(lái)心情低到很低凌节,一天也提不起精神來(lái),和低下員工說(shuō)話也沒(méi)超過(guò)十句話洒试。就像抑郁癥一樣倍奢,突然莫名的不想說(shuō)話不想過(guò)多...
    末葉木夕閱讀 185評(píng)論 0 0
  • 在生活中,你會(huì)發(fā)現(xiàn)有的人總有很多奇思妙想垒棋,他們觀點(diǎn)獨(dú)特卒煞,有思想,有深度……他們只做自己捕犬,勇于嘗試創(chuàng)新跷坝,簡(jiǎn)直就是人群...
    李新杰2017閱讀 247評(píng)論 0 4