Android Doze狀態源碼分析

1、概述

 Doze模式也稱低功耗模式,是google在Android6.0開始引入的,目的是爲了幫助用戶延長電池的使用時間,當處於該模式下的時候系統會限制應用Alarm、wakelock、wifi以及網絡等系統資源的使用。固然系統會按期退出Doze模式一小段時間,讓應用完成其延遲的活動。在此維護期內,系統會運行全部待處理的Alarm、Job以及wakelock等,並容許應用訪問網絡。這裏藉助官方文檔的一張圖。android

 從這張圖能夠發現,在系統滅屏一段時間以後,若是手機狀態沒有被改變,那麼系統會進入到Doze狀態(idle),並在一段時間以後退出Doze狀態進入到mantenance期,在每一個維護期結束時,系統會再次進入低電耗模式,暫停網絡訪問並推遲做業、wakelock和鬧鐘。隨着時間的推移,系統安排維護期的次數愈來愈少,處於Doze狀態的時間會愈來愈長,這有助於設備在未充電的狀況下長期處於不活動狀態時下降系統功耗。

 一旦用戶經過移動設備、打開屏幕或鏈接至充電器喚醒設備,系統就會當即退出低電耗模式,而且全部應用都會恢復正常活動。shell

一、分類

 即便是Doze模式,google也將其分爲了兩個大類:api

 (1)lightDoze模式:也就是輕度Doze狀態,系統處於滅屏而且沒有充電,可是設備可能處於移動;網絡

 (2)DeepDoze模式:系統處於滅屏狀態而且沒有充電同時還須要系統保持靜止狀態。併發

 其實根據Doze模式的狀態會發現和人睡覺的狀態很相似,當人處於睡眠狀態的時候(1)不會吃東西;(2)身體會保持必定的靜止狀態;(3)退出Doze狀態就相似於人在睡眠過程的呼吸了。ide

二、限制

 當系統處於Doze狀態的時候,會對應用進行以下的限制:函數

 (1)暫停網絡訪問;oop

 (2)系統忽略喚醒類wakelock;ui

 (3)標準的Alarm(包括setExact()和setWindow())將會被推遲到下一個維護階段。若是須要設置在設備處於低電耗模式時觸發的鬧鐘,可使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。使用 setAlarmClock() 設置的鬧鐘將繼續正常觸發,系統會在這些鬧鐘觸發以前不久退出低電耗模式;this

 (4)系統不執行wifi掃描;

 (5)系統不容許運行同步適配器;

 (6)系統不容許運行Job。

2、源碼講解

一、DeviceidleController介紹

 Doze模式是由DeviceIdleController這個類進行控制的,它和PowerManagerService等同樣都是屬於系統服務,而且在SystemServer中進行啓動註冊。因爲它不像PowerMaangerService等系統服務會提供一個代理類諸如PowerManager給應用使用;所以,在理解該類的過程當中則須要從SystemServer啓動DeviceIdleController開始。相關流程圖以下所示

1.一、onStart

 在onstart方法中主要作了以下事情:

(1)初始化部分變量;

(2)將配置文件中的白名單應用讀取出來存儲到列表中;

(3)註冊LocalService和Binder供系統使用,若是外界應用想要使用到註冊的Binder對象則必須經過反射的方式進行使用。

1.二、onBootPhase

 在onBootPhase方法中主要作了以下事情:

(1)首先判斷系統Service是否已經啓動完畢;

(2)初始化諸如AlarmManager、PowerManager以及ConnectivityService等系統服務;用於設置Alarm,獲取wakelock以及獲取是否有網絡等操做;

(3)初始化lightDoze以及Doze相關Intent,當這兩種狀態發生變化的時候用於通知系統中其餘模塊;

(4)初始化廣播接收器,其中包括亮滅屏、網絡變化、包移除以及電池狀態變化等廣播,用於改變系統當前所處狀態;

(5)更新當前網路狀態以及系統狀態。

二、核心源碼介紹

2.1 相關變量講解

 在正式介紹lightDoze模式和Doze模式下各個狀態之間的轉換以前,首先須要去了解一下其中所涉及到的一些變量的含義,否則看得過程當中老是雲裏霧裏的。

2.1.1 lightDoze相關變量介紹

在DeviceIdleController中定義了以下幾個和lightDoze模式相關聯的變量值:

boolean mForceIdle;//該變量值主要是經過adb shell方式進行賦值,例如adb shell dumpsys deviceidle force-idle deep/light,讓系統強制進入到Doze模式
LIGHT_STATE_ACTIVE = 0;//當前系統處於活動狀態,好比亮屏;
LIGHT_STATE_INACTIVE = 1;//當前系統處於非活動狀態,好比滅屏;
LIGHT_STATE_PRE_IDLE = 3;//當前系統還有沒有完成的任務,須要等待任務完成才能進入到idle狀態;
LIGHT_STATE_IDLE = 4;//系統當前處於lightDoze狀態,此時網絡、wakelock等都會受到限制;
LIGHT_STATE_WAITING_FOR_NETWORK = 5;//當前系統仍然處於lightDoze狀態,可是須要等待系統有網絡以後才能進入到維護狀態;
LIGHT_STATE_IDLE_MAINTENANCE = 6;//系統處於到了維護階段;
LIGHT_STATE_OVERRIDE = 7;//表示lightDoze狀態被覆蓋了,開始進入DeepDoze狀態
複製代碼

2.1.1 lightDoze相關變量介紹

在DeviceIdleController中定義了以下幾個和deepDoze模式相關聯的變量值:

//當前系統處於active狀態
private static final int STATE_ACTIVE = 0;
//系統處於inactive狀態,也就是滅屏且未充電狀態,這個時候系統等待進入deepIdle
private static final int STATE_INACTIVE = 1;
//表示系統剛結束inactive狀態,準備進入deepidle狀態
private static final int STATE_IDLE_PENDING = 2;
//表示系統正在感應設備是否被移動
private static final int STATE_SENSING = 3;
//表示設備正在定位,並獲取當前定位精度
private static final int STATE_LOCATING = 4;
//系統當前處於deepidle狀態
private static final int STATE_IDLE = 5;
//系統處於維護狀態
private static final int STATE_IDLE_MAINTENANCE = 6;
複製代碼

2.2 代碼講解

 對lightDoze和DeepDoze兩種模式下不一樣變量的含義有了清楚的認識以後那麼就能夠正式開始對系統是如何退出和進入Doze模式的代碼分析工做了。首先,咱們須要明白Doze模式的退出和進入都是根據系統自身行爲來進行控制的,而在前面咱們也瞭解到若是想要進入到lightDoze模式則須要系統滅屏而且未充電,進入DeepDoze模式除了滅屏和未充電以外還須要保持設備靜止。所以,咱們的入手點就是亮滅屏廣播接收器和電池變化廣播接受器了。

 在亮滅屏廣播接收器中會調用到updateInteractivityLocked函數中,在該函數中首先會獲取到屏幕狀態,而後根據屏幕狀態判斷是否須要開始進入Doze狀態,相關源碼以下(省略非必要代碼):

void updateInteractivityLocked() {
    //從PowerManager中獲取當前是否處於可交互狀態(亮屏或者屏保狀態)
    boolean screenOn = mPowerManager.isInteractive();
    //若是系統從亮屏狀態變爲滅屏狀態
    if (!screenOn && mScreenOn) {
        mScreenOn = false;
        //若是沒有強制進入到Doze狀態,
        if (!mForceIdle) {
            becomeInactiveIfAppropriateLocked();
        }
    } else if (screenOn) {
        mScreenOn = true;
        //若是沒有強制進入Doze狀態而且屏幕沒有被鎖定則將全部到數據從新初始化並cancel所設置到Alarm以及中止設備是否移動檢測
        if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
            becomeActiveLocked("screen", Process.myUid());
        }
    }
}
複製代碼

 該方式是將系統變爲inactive和active的入口而已,真正idle的各個狀態切換須要繼續看becomeInactiveIfAppropriateLocked函數;相關源碼以下:

/**
* 在講解方法以前首先了解一下下面兩個變量:
* (1)mDeepEnabled:是否容許進入到DeepDoze狀態;
* (2)mLightEnabled:是否容許進入到lightDoze狀態;
*  這兩個值均可以經過dump到方式進行修改,其實也就是至關於控制Doze模式到開關
**/
void becomeInactiveIfAppropriateLocked() {
    //若是系統滅屏而且沒有充電
    if ((!mScreenOn && !mCharging) || mForceIdle) {
        //系統上一個狀態處於可交互狀態而且容許進入深度睡眠
        if (mState == STATE_ACTIVE && mDeepEnabled) {
            //將系統狀態設置爲不可交互狀態
            mState = STATE_INACTIVE;
            //從新初始化各個標誌、取消所設置到相關Alarm以及取消sensing狀態、位置狀態、motion狀態檢測
            resetIdleManagementLocked();
            //設置Alarm,在30min以後檢測是否可以進入到Doze模式(idle狀態)
            scheduleAlarmLocked(mInactiveTimeout, false);
        }
        //系統上一個狀態處於可交互狀態,而且容許進入lightDoze狀態
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            //將系統狀態設置爲不可交互狀態
            mLightState = LIGHT_STATE_INACTIVE;
            //cancel掉和lightDoze相關到Alarm
            resetLightIdleManagementLocked();
            //設置Alarm,在5min以後檢測是否可以進入到lightDoze模式
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
        }
    }
}
複製代碼

 在該函數中,首先判斷系統是否處於滅屏且未充電狀態,若是是則:

(1)首先判斷系統是否知足進入DeepDoze模式,若是知足條件則將DeepDoze相關變量從新初始化並cancel掉原有Alarm,而後設置一個30min後觸發的Alarm用於檢測系統是否可以進入到DeepDoze狀態;

(2)而後還會判斷系統是否知足進入到lightDoze模式,若是知足條件則會cancel掉原油lightDoze相關Alarm並設置一個5min後觸發到Alarm用於檢測系統是否可以進入到lightDoze狀態;

 從該方法到實現來看,lightDoze的進入時間確定早於DeepDoze的進入時間,因此接下來首先對lightDoze相關代碼進行講解。

2.2.1 lightDoze代碼講解

 在系統滅屏5min以後,進入到lightDoze模式的Alarm將會被觸發,並在AlarmManagerService中回調到mLightAlarmListener中,繼而調用到stepLightIdleStateLocked函數中,stepLightIdleStateLocked函數相關源碼以下(省略掉部分可有可無代碼):

/**
*  該函數中涉及到了不少相關時間,能夠經過adb shell dumpsys
*  deviceidle進行查看
**/
void stepLightIdleStateLocked(String reason) {
    if (mLightState == LIGHT_STATE_OVERRIDE) {
        return;
    }
    switch (mLightState) {
        case LIGHT_STATE_INACTIVE:
            //維護時間段的預算時間1min
            mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
            //lightidle狀態的最小時間段5min
            mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
            //開始維護的時間
            mMaintenanceStartTime = 0;
            //若是當前有正在進行的任務(Alarm、job以及wakelock)
            if (!isOpsInactiveLocked()) {
                //表示當前有任務須要完成才能進入到lightidle狀態
                mLightState = LIGHT_STATE_PRE_IDLE;
                //設置10min後觸發到Alarm
                scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);
                break;
            }
        case LIGHT_STATE_PRE_IDLE:
        case LIGHT_STATE_IDLE_MAINTENANCE:
            //若是爲true,當前處於maintenance狀態即將退出該狀態
            if (mMaintenanceStartTime != 0) {
                //當前處於維護狀態的實際時間
                long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
                //若是處於維護狀態的實際時間小於1min,則下次的維護時間段將加上小於1min的時間段
                if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                    mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
                //若是大於等於1min,則下次的維護時間段將減去大於1min的時間段
                } else {
                    mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
                }
            }
            //從新初始化維護開始時間
            mMaintenanceStartTime = 0;
            //設置退出lightidle的alarm
            scheduleLightAlarmLocked(mNextLightIdleDelay);
            //保持lightidle狀態的最大時間爲15min
            mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                    (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
            //保持lightidle的時間最小爲5min
            if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
            }
            //表示系統當前處於lightidle狀態
            mLightState = LIGHT_STATE_IDLE;
            //系統處於idle狀態,開始對wakelock、job以及網絡等資源進行限制
            mGoingIdleWakeLock.acquire();
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
            break;
        case LIGHT_STATE_IDLE:
        case LIGHT_STATE_WAITING_FOR_NETWORK:
            //若是當前有網絡連接或者已經到達了等待網絡連接時間
            if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
                mActiveIdleOpCount = 1;
                //系統即將進入到維護狀態,所以須要持有wakelock鎖防止系統休眠
                mActiveIdleWakeLock.acquire();
                //記錄維護的開始時間
                mMaintenanceStartTime = SystemClock.elapsedRealtime();
                //系統處於維護狀態的時間最大爲5min最小爲1min
                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;
                }
                //設置定時alarm以退出維護狀態
                scheduleLightAlarmLocked(mCurIdleBudget);
                //標識當前系統處於維護階段
                mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
                //釋放掉wakelock、job以及網絡相關限制
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            } else {
               //表示當前須要等待網絡才能進入維護階段
                scheduleLightAlarmLocked(mNextLightIdleDelay);
                mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
            }
            break;
    }
}
複製代碼

 該方法實現了lighDoze模式相關各個狀態之間的相互轉換,以及處於維護階段和lightIdle狀態的時間計算。各個狀態之間關係的狀態轉換圖以下:

 處於維護階段的預算時間(mCurIdleBudget)最大值爲5min最小值爲1min,默認值爲1min。若是實際處於維護階段的時間(duration)不是1min,那麼下次處於維護階段的預算時間將加上1min-duration的值。

 處於lightidle狀態時間(mNextLightIdleDelay)的最大值爲15min最小值爲5min,默認值爲5min。下次處於lightidle狀態時間=上次處於lightidle狀態時間*2並與15min取最小值。

 也就是隨着處於lightidle狀態的時間愈來愈長,那麼處於lightIdle狀態的時間也會愈來愈長可是最大值爲15min,而處於maintenance狀態的時間基本就是在1min左右。

2.2.2 資源限制、放開代碼講解

 當進入退出idle狀態的時候會對系統資源諸如wakelock、alarm以及網絡等進行限制和放開,在該類中經過調用對應模塊所提供的api或者經過廣播的方式通知到相應的模塊,具體的資源限制實現並無在該類中。代碼(省略掉部分代碼)以下:

@Override public void handleMessage(Message msg) {
        switch (msg.what) {
            //將列表中的白名單應用信息寫入到文件中
            case MSG_WRITE_CONFIG: {
                handleWriteConfigFile();
            } break;
            //idle和lightidle狀態資源處理
            case MSG_REPORT_IDLE_ON:
            case MSG_REPORT_IDLE_ON_LIGHT: {
                final boolean deepChanged;
                final boolean lightChanged;
                //用於通知PowerManagerService如今系統已經處於idle狀態,並限制不符合要求的wakelock
                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 { 
                    //通知NetwaorkPolicyManagerService如今系統已經處於idle狀態,須要從新更新規則
                    mNetworkPolicyManager.setDeviceIdleMode(true);
                } catch (RemoteException e) {
                }
                //發送廣播給須要知道Doze模式狀態改變的模塊
                if (deepChanged) {
                    getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                }
                if (lightChanged) {
                    getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
                }
                //資源限制已經處理完成,釋放掉wakelock鎖
                mGoingIdleWakeLock.release();
            } break;
            //退出idle狀態進入到maintenance階段
            case MSG_REPORT_IDLE_OFF: {
                //放開wakelock資源限制
                final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                try {
                    //放開網路限制
                    mNetworkPolicyManager.setDeviceIdleMode(false);
                } catch (RemoteException e) {
                }
                //發送廣播給須要知道Doze模式狀態改變的模塊
                if (deepChanged) {
                    incActiveIdleOps();
                    getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
                            null, mIdleStartedDoneReceiver, null, 0, null, null);
                }
                if (lightChanged) {
                    incActiveIdleOps(); getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
                            null, mIdleStartedDoneReceiver, null, 0, null, null);
                }
                decActiveIdleOps();
            } break;
            //進入到active狀態
            case MSG_REPORT_ACTIVE: {
                //獲取進入active的緣由以及喚醒系統的uid
                String activeReason = (String)msg.obj;
                int activeUid = msg.arg1;
                //放開全部的資源限制併發送doze狀態變化廣播
                final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                try {
                    mNetworkPolicyManager.setDeviceIdleMode(false);
                    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);
                }
            } break;
          .................
    }
複製代碼

 對於lightDoze模式中各個狀態的切換流程以及是如何通知到系統對wakelock、job以及網絡等資源進行管控的大體代碼是講解的差很少。大體的文字敘述流程總結以下:

(1)當手機處於滅屏和未充電而且當前系統處於active狀態的時候,則會將系統狀態變動爲inactive狀態,同時將數據從新初始化爲原始數據並清空上次設置的相關Alarm,繼而設置一個5min中的alarm來判斷系統是否能夠進入到lightidle狀態;

(2)當系統處於滅屏狀態5min,而且在這之間沒有亮滅屏以及充電等操做,那麼系統就會經過調用stepLightIdleStateLocked方法來判斷系統是否能夠進入到lightidle狀態;

(3)首先會初始化處於mantenance狀態和ligthidle狀態的時間,而後會判斷當前是否有alarm、job等活動正在進行,若是存在則會10min(設置Alarm)以後再進入到lightidle狀態,不然直接進入到lightidle狀態;

(4)當進入到lightidle狀態以後,會經過發送廣播和調用相關服務api的方式來進行通知,各個模塊在接收到通知以後會對相關的資源作出必定的限制;

(5)若是當前時間是進入到mantenance狀態,可是並無網絡鏈接,那麼系統就會進入到等到網絡鏈接狀態(其實仍是lightidle狀態,只是將mLightstate變動爲了LIGHT_STATE_WAITING_FOR_NETWORK),在mNextLightIdleDelay(處於idle狀態到時間)以後無論是否有網絡鏈接都會進入到mantenance狀態;

(6)放開在lightidle狀態所限制到資源,通知各個模塊的方式也是調用對應模塊的api和發送廣播,固然在系統處於mantenance狀態的時候會持有wakelock鎖以防止系統休眠。當維護狀態的時間到達以後又會走到步驟(4)以此往復。

2.2.2 deepDoze代碼講解

 deepDoze相對於lightDoze而言,總體的狀態變化流程大體類似只是增長了位置變化監聽而已。

 在講解進入到lightDoze狀態過程當中講到了方法becomeInactiveIfAppropriateLocked,在該方法中首先會判斷系統是否處於滅屏而且未充電,若是知足條件則會先判斷系統是否知足進入到deepDoze狀態而後再判斷是否可以進入到lightDoze狀態,而當系統知足進入到deepDoze的條件時,首先會將相關變量從新初始化並cancel掉上次設置的alarm並從新設置一個30min後執行的alarm,當相關alarm時間到達以後會調用到方法stepIdleStateLocked,所以下面則圍繞該方法進行講解。

 在講解該方法以前首先須要瞭解一下關於設備位置變化監聽相關知識講解。在Android中有四種方式來獲取設備的位置相關信息,下面只介紹當前所使用到的兩種方式(一般也是這兩種方式配合使用來獲取設備的地理位置信息):

(1)經過GPS的方式獲取位置的經緯度信息,該方法獲取的地理位置信息精度高,可是速度慢而且只能在戶外使用;

(2)經過移動網路基站或者wifi來獲取地理位置信息,該方法定位速度快可是精確度不高;

 相關源碼(省略部分源碼)以下:

void stepIdleStateLocked(String reason) {
    final long now = SystemClock.elapsedRealtime();
    //若是在1h以內存在喚醒系統的Alarm,則重置進入到deepIdle狀態的時間
    if ((now+mConstants.MIN_TIME_TOLARM) > mAlarmManager.getNextWakeFromIdleTime()) {
        if (mState != STATE_ACTIVE) {
            becomeActiveLocked("alarm", Process.myUid());
            becomeInactiveIfAppropriateLocked();
        }
        return;
    }
    switch (mState) {
        case STATE_INACTIVE:
            //註冊sensor以監聽設備是否發生了位置變化
            startMonitoringMotionLocked();
            //系統處於STATE_IDLE_PENDING狀態爲30min
            scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
            //初始化處於維護狀態時間爲5min
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            //初始化處於deepidle狀態的時間1h
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            //當前系統處於STATE_IDLE_PENDING狀態
            mState = STATE_IDLE_PENDING;
            break;
        case STATE_IDLE_PENDING:
            //當前處於STATE_SENSING狀態,用於檢測當前設備是否有移動
            mState = STATE_SENSING;
            //設置一個4min後到alarm檢測當前是否仍然處於STATE_SENSING狀態,若是是則會再次調用becomeInactiveIfAppropriateLocked方法
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
            //取消監聽設備位置更新以及GPS位置更新
            cancelLocatingLocked();
            mNotMoving = false;
            mLocated = false;
            mLastGenericLocation = null;
            mLastGpsLocation = null;
            //開始檢測是否移動
            mAnyMotionDetector.checkForAnyMotion();
            break;
        case STATE_SENSING:
            //清除掉sensing狀態相關的alarm
            cancelSensingTimeoutAlarmLocked();
            //用於獲取當前定位精度
            mState = STATE_LOCATING;
            //獲取定位精度的時間爲30s,若是30s以後沒有獲取到定位精度或者獲取到的定位精度小於20m而且沒有移動則會直接進入到deepIdle狀態
            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;
            }
            //若是當前沒有任何定位則直接中止檢測並進入到deepIdle狀態,否在在30s以後再中止檢測
            if (mLocating) {
                break;
            }
        //中止定位和設備是否處於運動狀態檢測,可是並無中止sensing的檢測
        case STATE_LOCATING:
            cancelAlarmLocked();
            cancelLocatingLocked();
            mAnyMotionDetector.stop();
        //進入到idle狀態
        case STATE_IDLE_MAINTENANCE:
            //在1h以後退出deepidle狀態
            scheduleAlarmLocked(mNextIdleDelay, true);
            //下次處於deepidle狀態的時間是上次處於idle狀態時間的2倍
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
            //處於deepidle狀態的最大時間爲6h
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
            //處於deepidle狀態的最小時間爲1h
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            }
            //當前處於deepidle狀態
            mState = STATE_IDLE;
            //lightidle狀態已經被deepidle狀態覆蓋了,所以清除掉lightidle相關的alarm
            if (mLightState != LIGHT_STATE_OVERRIDE) {
                mLightState = LIGHT_STATE_OVERRIDE;
                cancelLightAlarmLocked();
            }
            //限制系統wakelock、job、alarm以及網絡等資源
            mGoingIdleWakeLock.acquire();
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
            break;
        //系統進入到mantenance狀態
        case STATE_IDLE:
            mActiveIdleOpCount = 1;
            mActiveIdleWakeLock.acquire();
            //處於mantenance狀態爲5min
            scheduleAlarmLocked(mNextIdlePendingDelay, false);
            mMaintenanceStartTime = SystemClock.elapsedRealtime();
            //下次處於維護狀態的時間爲上次的2倍,可是最大值爲10min
            mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                    (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
            //處於維護狀態的最小時間爲5min
            if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
                mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            }
            //當前系統處於維護狀態
            mState = STATE_IDLE_MAINTENANCE;
            //釋放因爲處於idle狀態而被限制的相關資源
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            break;
    }
}
複製代碼

 呼~,終於把進入到deepIdle的整個流程走完了,固然裏面還涉及到了一些方法在後面還須要繼續分析,在這裏對deepIdle的整個大體流程簡單的總結一下。

(1)在系統滅屏以後而且手機沒有充電那麼系統將清除上次進入到deepIdle相關的數據,而且設置一個30min以後的Alarm來檢測是否能夠進入到deepIdle狀態;

(2)若是系統滅屏了30min而且沒有充電,則會調用到stepIdleStateLocked方法判斷是否能夠進入到deepIdle狀態;

(3)若是在1h之內系統中存在能夠喚醒系統的Alarm則會直接return當前方法並從新初始化相關條件進入deepDoze狀態;若是沒有則會繼續走如下步驟;

(4)進入到STATE_IDLE_PENDING狀態,並設置Sensor傳感器檢測,用於判斷手機是否被移動,同時設置一個30min後觸發的alarm進入到STATE_SENSING狀態;

(5)在STATE_SENSING狀態檢測設備是否被移動,若是被移動了則會將當前狀態變動爲active狀態,並從新調用becomeInactiveIfAppropriateLocked方法,從頭開始進行狀態檢測;若是沒有被移動則會進入到STATE_LOCATING狀態;

(6)在STATE_LOCATING狀態系統經過LocationManager用於檢測移動網絡定位和gps定位的精度是否在20m之內,若是精度在20m之內而且在步驟(5)也沒有檢測到設備位置發生了變化,則清除移動數據網絡定位和gps定位以及中止設備移動檢測;並在接下來進入到deepIdle狀態;

(7)進入到deepidle狀態,並計算下一次處於deepIdle的時間,同時顯示job、wakelock、alarm以及網絡等資源限制。若是沒有其它行爲打斷這種狀態,那麼在設定的時間以後退出該狀態進入到mantenance狀態;

(8)進入到mantenance狀態,並釋放掉由於進入到deepIdle狀態而被限制的資源,在設定的時間以後退出維護狀態並重復步驟(7)。

 隨着處於deepIdle的次數愈來愈多,那麼處於deepIdle狀態的時間也會愈來愈長,最長爲6h;處於維護階段的時間也會愈來愈長,最長爲10min。而當sensor檢測到設備被移動了以後就會打破這種平衡,並返回到步驟(1)從新進行狀態轉換。也就是說當檢測到設備運動到時候就會打破全部到平衡已經狀態檢測過程,並從新開始。

2.2.3 運動檢測代碼講解

 當系統處於STATE_SENSING狀態的時候會調用AnyMotionDetector中的checkForAnyMotion方法,最後會回調到onAnyMotionResult方法中,代碼以下:

public void onAnyMotionResult(int result) {
    //若是返回-1,也就是不清楚設備是否移動,則會清除在sensing狀態所設置到alarm
    if (result != AnyMotionDetector.RESULT_UNKNOWN) {
        synchronized (this) {
            cancelSensingTimeoutAlarmLocked();
        }
    }
    //若是設備被移動了或者不知道是否被移動,則都會被看成設備被移動處理,具體的處理細節後面會講到
    if ((result == AnyMotionDetector.RESULT_MOVED) ||
        (result == AnyMotionDetector.RESULT_UNKNOWN)) {
        synchronized (this) {
            handleMotionDetectedLocked(mConstants.INACTIVE_TIMEOUT, "non_stationary");
        }
    //若是設備沒有被移動,好比一直被放在桌子上面
    } else if (result == AnyMotionDetector.RESULT_STATIONARY) {
        //判斷當前系統所處的狀態
        if (mState == STATE_SENSING) {
            synchronized (this) {
                mNotMoving = true;
                //繼續保持系統狀態並將系統轉換爲STATE_LOCATING狀態
                stepIdleStateLocked("s:stationary");
            }
        } else if (mState == STATE_LOCATING) {
            synchronized (this) {
                mNotMoving = true;
                //若是沒有移動設備,而且location位置檢測精度在20m之內
                if (mLocated) {
                    stepIdleStateLocked("s:stationary");
                }
            }
        }
    }
}
複製代碼

2.2.4 設備若是被移動代碼講解

 不論在檢測設備檢測到設備被移動仍是所設置到sensor檢測到了設備被移動了,最終都會調用到handleMotionDetectedLocked方法中,相關代碼以下:

void handleMotionDetectedLocked(long timeout, String type) {
    boolean becomeInactive = false;
    //判斷當前系統是否處於active狀態
    if (mState != STATE_ACTIVE) {
        //若是設備被移動了並不會影響到lightDoze狀態,所以須要排除lightDoze狀態
        boolean lightIdle = mLightState == LIGHT_STATE_IDLE
                || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK
                || mLightState == LIGHT_STATE_IDLE_MAINTENANCE;
        //若是當前不處於lightDoze狀態,則會釋放掉因deepDoze狀態而被限制到資源
        if (!lightIdle) {
            scheduleReportActiveLocked(type, Process.myUid());
        }
        //從新初始化數據
        mState = STATE_ACTIVE;
        mInactiveTimeout = timeout;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;
        becomeInactive = true;
    }
    //若是已經進入到了deepDoze狀態則須要從新初始化lightDoze狀態爲active狀態
    if (mLightState == LIGHT_STATE_OVERRIDE) {
        mLightState = LIGHT_STATE_ACTIVE;
        becomeInactive = true;
    }
    //從新設置alarm以進入到doze狀態
    if (becomeInactive) {
        becomeInactiveIfAppropriateLocked();
    }
}
複製代碼

2.2.5 獲取定位精度相關代碼講解

 當系統處於STATE_LOCATING狀態的時候會註冊移動數據網絡定位以及gps定位相關listener到LocationManagerService中,當有位置變化的時候會回調到相關listener中,它們的實現方式相似,這裏只講解移動數據網絡定位回調函數,代碼以下:

void receivedGenericLocationLocked(Location location) {
    //當前設備不處於STATE_LOCATING狀態,則取消掉相關alarm並直接返回
    if (mState != STATE_LOCATING) {
        cancelLocatingLocked();
        return;

    mLastGenericLocation = new Location(location);
    //若是定位的精度大於20m而且存在gps定位則直接返回,等待30s以後自動進入到deepDoze狀態
    if (location.getAccuracy() > mConstants.LOCATION_ACCURACY && mHasGps) {
        return;
    }
    mLocated = true;
    //若是在STATE_SENSING沒有檢測到設備移動下一步則讓系統進入到deepIdle狀態
    if (mNotMoving) {
        stepIdleStateLocked("s:location");
    }
}
複製代碼

二、退出Doze狀態源碼講解

 在文章開篇講到,若是系統要進入到Doze狀態必須知足三個條件,(1)滅屏(2)未充電(3)設備處於靜止不動;所以想要退出Doze模式只須要破壞這三種條件之一也就能夠了。當設備處於非靜止狀態是如何破壞deepDoze相關狀態在講解deepDoze相關代碼已經講解到了,所以這裏只須要關注條件(1)和(2)兩種狀況就好了。

 在文章開篇已經講到了,在onBootPhase函數中註冊了亮滅屏廣播以及電池變化接收廣播以監聽系統亮滅屏以及是否充電狀態,當監聽到系統亮屏或者充電到時候都會調用到becomeActiveLocked方法用於退出Doze模式;相關源碼以下:

void becomeActiveLocked(String activeReason, int activeUid) {
    if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
        //放開因Doze模式而被限制到wakelock、alarm、job以及網絡等資源
        scheduleReportActiveLocked(activeReason, activeUid);
        //將狀態變動爲active狀態
        mState = STATE_ACTIVE;
        mLightState = LIGHT_STATE_ACTIVE;
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;
        //從新初始化相關變量而且取消掉註冊到Alarm以及相關listener
        resetIdleManagementLocked();
        resetLightIdleManagementLocked();
    }
}
複製代碼

3、總結

 歷經一週左右的時間,系統是如何從active狀態進入到lightDoze模式,又是如何在lightDoze模式的基礎上進入到Doze模式的總體流程基本上來講是理完了,固然裏面還有不少細節部分並無講到好比說是如何添加Doze白名單的,都還須要去慢慢琢磨才行。文章中可能存在不少理解不對的地方或者沒有敘述清楚的地方,還但願你們多多指正。

官方文檔:developer.android.com/training/mo…

參考博客:blog.csdn.net/FightFightF…

google源碼:androidxref.com/

相關文章
相關標籤/搜索