Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android7.0 Doze模式

Android7.0 Doze模式

編輯:關於Android編程

在Android M中,Google就引入了Doze模式。它定義了一種全新的、低能耗的狀態。
在該狀態,後台只有部分任務被允許運行,其它任務都被強制停止。

本篇博客中,我們就來分析一下Android 7.0中Doze模式相關的流程。

一、基本原理
Doze模式可以簡單概括為:
若判斷用戶在連續的一段時間內沒有使用手機,就延緩終端中APP後台的CPU和網絡活動,以達到減少電量消耗的目的。上面這張圖比較經典,基本上說明了Doze模式的含義。

圖中的橫軸表示時間,紅色部分表示終端處於喚醒的運行狀態,綠色部分就是Doze模式定義的休眠狀態。

從圖中的描述,我們可以看到:如果一個用戶停止充電(on battery: 利用電池供電),關閉屏幕(screen off),手機處於靜止狀態(stationary: 位置沒有發生相對移動),保持以上條件一段時間之後,終端就會進入Doze模式。一旦進入Doze模式,系統就減少(延緩)應用對網絡的訪問、以及對CPU的占用,來節省電池電量。

如圖所示,Doze模式還定義了maintenance window。
在maintenance window中,系統允許應用完成它們被延緩的動作,即可以使用CPU資源及訪問網絡。
從圖中我們可以看出,當進入Doze模式的條件一直滿足時,Doze模式會定期的進入到maintenance window,但進入的間隔越來越長。
通過這種方式,Doze模式可以使終端處於較長時間的休眠狀態。

需要注意的是:一旦Doze模式的條件不再滿足,即用戶充電、或打開屏幕、或終端的位置發生了移動,終端就恢復到正常模式。
因此,當用戶頻繁使用手機時,Doze模式幾乎是沒有什麼實際用處的。

具體來講,當終端處於Doze模式時,進行了以下操作:
1、暫停網絡訪問。
2、系統忽略所有的WakeLock。
3、標准的AlarmManager alarms被延緩到下一個maintenance window。
但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock時,alarms定義事件仍會啟動。
在這些alarms啟動前,系統會短暫地退出Doze模式。
4、系統不再進行WiFi掃描。
5、系統不允許sync adapters運行。
6、系統不允許JobScheduler運行。

二、DeviceIdleController的初始化
Android中的Doze模式主要由DeviceIdleController來控制。

public class DeviceIdleController extends SystemService
        implements AnyMotionDetector.DeviceIdleCallback {
    ....................
}

可以看出DeviceIdleController繼承自SystemService,是一個系統級的服務。
同時,繼承了AnyMotionDetector定義的接口,便於檢測到終端位置變化後進行回調。

接下來我們看看它的初始化過程。

private void startOtherServices() {
    .........
    mSystemServiceManager.startService(DeviceIdleController.class);
    .........
}

如上代碼所示,SystemServer在startOtherServices中啟動了DeviceIdleController,將先後調用DeviceIdleController的構造函數和onStart函數。

1、構造函數

public DeviceIdleController(Context context) {
    super(context);
    //deviceidle.xml用於定義idle模式也能正常工作的非系統應用
    //一般終端似乎並沒有定義deviceidle.xml
    mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
    mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}

DeviceIdleController的構造函數比較簡單,就是在創建data/system/deviceidle.xml對應的file文件,同時創建一個對應於後台線程的handler。

2、onStart

public void onStart() {
    final PackageManager pm = getContext().getPackageManager();

    synchronized (this) {
        //讀取配置文件,判斷Doze模式是否允許被開啟
        mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);

        //分析PKMS時提到過,PKMS掃描系統目錄的xml,將形成SystemConfig
        SystemConfig sysConfig = SystemConfig.getInstance();

        //獲取除了device Idle模式外,都可以運行的系統應用白名單
        ArraySet<string> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
        for (int i=0; i<allowpowerexceptidle.size(); string="" pkg="allowPowerExceptIdle.valueAt(i);" try="" applicationinfo="" ai="pm.getApplicationInfo(pkg," int="" appid="UserHandle.getAppId(ai.uid);" catch="" packagemanager.namenotfoundexception="" device=""> allowPower = sysConfig.getAllowInPowerSave();
        for (int i=0; i<allowpower.size(); i++)="" {="" string="" pkg="allowPower.valueAt(i);" try="" applicationinfo="" ai="pm.getApplicationInfo(pkg," packagemanager.match_system_only);="" int="" appid="UserHandle.getAppId(ai.uid);" these="" apps="" are="" on="" both="" the="" whitelist-except-idle="" as="" well="" full="" whitelist,="" so="" they="" apply="" in="" all="" cases.="" mpowersavewhitelistappsexceptidle.put(ai.packagename,="" appid);="" mpowersavewhitelistsystemappidsexceptidle.put(appid,="" true);="" mpowersavewhitelistapps.put(ai.packagename,="" mpowersavewhitelistsystemappids.put(appid,="" }="" catch="" (packagemanager.namenotfoundexception="" e)="" constants為deviceidlecontroller中的內部類,繼承contentobserver="" 監控數據庫變化,同時得到doze模式定義的一些時間間隔="" mconstants="new" constants(mhandler,="" getcontext().getcontentresolver());="" 解析deviceidle.xml,並將其中定義的package對應的app,加入到mpowersavewhitelistuserapps中="" readconfigfilelocked();="" 將白名單的內容給alarmmanagerservice和powermangerservice="" 例如:deviceidlecontroller判斷開啟doze模式時,會通知pms="" 此時除去白名單對應的應用外,pms會將其它所有的wakelock設置為disable狀態="" updatewhitelistappidslocked();="" 以下的初始化,都是假設目前處在進入doze模式相反的條件上="" mnetworkconnected="true;" mscreenon="true;" start="" out="" assuming="" we="" charging.="" if="" aren't,="" will="" at="" least="" get="" a="" battery="" update="" next="" time="" level="" drops.="" mcharging="true;" doze模式定義終端初始時為active狀態="" mstate="STATE_ACTIVE;" 屏幕狀態初始時為active狀態="" mlightstate="LIGHT_STATE_ACTIVE;" minactivetimeout="mConstants.INACTIVE_TIMEOUT;" 發布服務="" binderservice和localservice均為deviceidlecontroller的內部類="" mbinderservice="new" binderservice();="" publishbinderservice(context.device_idle_controller,="" mbinderservice);="" publishlocalservice(localservice.class,="" new="" localservice());="" }
除去發布服務外,DeviceIdleController在onStart函數中,主要是讀取配置文件更新自己的變量,思路比較清晰。

在這裡我們僅跟進一下updateWhitelistAppIdsLocked函數:

private void updateWhitelistAppIdsLocked() {
    //構造出除去idle模式外,可運行的app id數組 (可認為是系統和普通應用的集合)
    //mPowerSaveWhitelistAppsExceptIdle從系統目錄下的xml得到
    //mPowerSaveWhitelistUserApps從deviceidle.xml得到,或調用接口加入;
    //mPowerSaveWhitelistExceptIdleAppIds並未使用
    mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);

    //構造不受Doze限制的app id數組 (可認為是系統和普通應用的集合)
    //mPowerSaveWhitelistApps從系統目錄下的xml得到
    //mPowerSaveWhitelistAllAppIds並未使用
    mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);

    //構造不受Doze限制的app id數組(僅普通應用的集合)、
    //mPowerSaveWhitelistUserAppIds並未使用
    mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);

    if (mLocalPowerManager != null) {
        ...........
        //PMS拿到的是:系統和普通應用組成的不受Doze限制的app id數組 
        mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
    }

    if (mLocalAlarmManager != null) {
        ..........
        //AlarmManagerService拿到的是:普通應用組成的不受Doze限制的app id數組 
        mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
    }
}

updateWhitelistAppIdsLocked主要是將白名單交給PMS和AlarmManagerService。 注意Android區分了系統應用白名單、普通應用白名單等,因此上面進行了一些合並操作。

3、onBootPhase 與PowerManagerService一樣,DeviceIdleController在初始化的最後一個階段需要調用onBootPhase函數:

public void onBootPhase(int phase) {
    //在系統PHASE_SYSTEM_SERVICES_READY階段,進一步完成一些初始化
    if (phase == PHASE_SYSTEM_SERVICES_READY) {
        synchronized (this) {
            //初始化一些變量
            mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
            ..............

            mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
            //根據配置文件,利用SensorManager獲取對應的傳感器,保存到mMotionSensor中
            ..............

            //如果配置文件表明:終端需要預獲取位置信息
            //則構造LocationRequest
            if (getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
                mLocationManager = (LocationManager) getContext().getSystemService(
                        Context.LOCATION_SERVICE);
                mLocationRequest = new LocationRequest()
                    .setQuality(LocationRequest.ACCURACY_FINE)
                    .setInterval(0)
                    .setFastestInterval(0)
                    .setNumUpdates(1);
            }

            //根據配置文件,得到角度變化的門限
            float angleThreshold = getContext().getResources().getInteger(
                    com.android.internal.R.integer.config_autoPowerModeThresholdAngle) / 100f;
            //創建一個AnyMotionDetector,同時將DeviceIdleController注冊到其中
            //當AnyMotionDetector檢測到手機變化角度超過門限時,就會回調DeviceIdleController的接口
            mAnyMotionDetector = new AnyMotionDetector(
                    (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
                    mHandler, mSensorManager, this, angleThreshold);

            //創建兩個常用的Intent,用於通知Doze模式的變化
            mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
            mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
            mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);

            //監聽ACTION_BATTERY_CHANGED廣播(電池信息發生改變)
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
            getContext().registerReceiver(mReceiver, filter);

            //監聽ACTION_PACKAGE_REMOVED廣播(包被移除)
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addDataScheme("package");
            getContext().registerReceiver(mReceiver, filter);

            //監聽CONNECTIVITY_ACTION廣播(連接狀態發生改變)
            filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            getContext().registerReceiver(mReceiver, filter);

            //重新將白名單信息交給PowerManagerService和AlarmManagerService
            //這個工作在onStart函數中,已經調用updateWhitelistAppIdsLocked進行過了
            //到onBootPhase時,重新進行一次,可能:一是為了保險;二是,其它進程可能調用接口,更改了對應數據,於是進行更新
            mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);

            //監聽屏幕顯示相關的變化
            mDisplayManager.registerDisplayListener(mDisplayListener, null);

            //更新屏幕顯示相關的信息
            updateDisplayLocked();
        }
        //更新連接狀態相關的信息
        updateConnectivityState(null);
    }   
}

從代碼可以看出,onBootPhase方法: 主要創建一些本地變量,然後根據配置文件初始化一些傳感器,同時注冊了一些廣播接收器和回到接口, 最後更新屏幕顯示和連接狀態相關的信息。

三、DeviceIdleController定義的狀態變化 根據前面提到的Doze模式的原理,我們知道手機進入Doze模式的條件是:未充電、手機位置不發生變化、屏幕熄滅。 因此,在DeviceIdleController中監聽了這三個條件對應的狀態,以決定終端是真正否進入到Doze模式。

對這三個條件的分析,最終都會進入到DeviceIdleController定義的狀態變化流程。 因此我們就以充電狀態的變化為例,看看DeviceIdleController進行了哪些處理。其余條件的分析,基本類似。

1、充電狀態的處理 對於充電狀態,在onBootPhase函數中已經提到,DeviceIdleController監聽了ACTION_BATTERY_CHANGED廣播:

............
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
...........

我們看看receiver中對應的處理:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()) {
            .........
            case Intent.ACTION_BATTERY_CHANGED: {
                synchronized (DeviceIdleController.this) {
                    //從廣播中得到是否在充電的消息
                    int plugged = intent.getIntExtra("plugged", 0);
                    updateChargingLocked(plugged != 0);
                }
            } break;
        }
    }
};

根據上面的代碼,可以看出當收到電池信息改變的廣播後,DeviceIdleController將得到電源是否在充電的消息,然後調用updateChargingLocked函數進行處理。

void updateChargingLocked(boolean charging) {
    .........
    if (!charging && mCharging) {
        //從充電狀態變為不充電狀態
        mCharging = false;
        //mForceIdle值一般為false
        if (!mForceIdle) {
            //判斷是否進入Doze模式
            becomeInactiveIfAppropriateLocked();
        }
    } else if (charging) {
        //進入充電狀態
        mCharging = charging;
        if (!mForceIdle) {
            //手機退出Doze模式
            becomeActiveLocked("charging", Process.myUid());
        }
    }
}

2、becomeActiveLocked 我們先看看becomeActiveLocked函數:

//activeReason記錄的終端變為active的原因
void becomeActiveLocked(String activeReason, int activeUid) {
    ...........
    if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
        ............
        //1、通知PMS等Doze模式結束
        scheduleReportActiveLocked(activeReason, activeUid);

        //更新DeviceIdleController本地維護的狀態
        //在DeviceIdleController的onStart函數中,我們已經知道了
        //初始時,mState和mLightState均為Active狀態
        mState = STATE_ACTIVE;
        mLightState = LIGHT_STATE_ACTIVE;

        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;

        //2、重置一些事件
        resetIdleManagementLocked();
        resetLightIdleManagementLocked();

        addEvent(EVENT_NORMAL);
    }
}

2.1 scheduleReportActiveLocked

void scheduleReportActiveLocked(String activeReason, int activeUid) {
    //發送MSG_REPORT_ACTIVE消息
    Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
    mHandler.sendMessage(msg);
}

對應的處理流程:

.........
case MSG_REPORT_ACTIVE: {
    .........
    //通知PMS Doze模式結束,
    //於是PMS將一些Doze模式下,disable的WakeLock重新enable
    //然後調用updatePowerStateLocked函數更新終端的狀態
    final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
    final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);

    try {
        //通過NetworkPolicyManagerService更改Ip-Rule,不再限制終端應用上網
        mNetworkPolicyManager.setDeviceIdleMode(false);
        //BSS做好對應的記錄
        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);
    }
}
........

從上面的代碼可以看出,scheduleReportActiveLocked函數最主要的工作是: 通知PMS等重新更新終端的狀態; 通知NetworkPolicyManagerService不再限制應用上網。 發送Doze模式改變的廣播。

2.2 resetIdleManagementLocked

void resetIdleManagementLocked() {
    //復位一些狀態變量
    mNextIdlePendingDelay = 0;
    mNextIdleDelay = 0;
    mNextLightIdleDelay = 0;

    //停止一些工作,主要是位置檢測相關的
    cancelAlarmLocked();
    cancelSensingTimeoutAlarmLocked();
    cancelLocatingLocked();
    stopMonitoringMotionLocked();
    mAnyMotionDetector.stop();
}

從上面的代碼可以看出,resetIdleManagementLocked的工作相對簡單,就是停止進入Doze模式時啟動的一些任務。

3、becomeInactiveIfAppropriateLocked 與becomeActiveLocked函數相比,becomeInactiveIfAppropriateLocked函數較為復雜。 因為調用becomeInactiveIfAppropriateLocked函數時,終端可能只是滿足進入Doze模式的條件,離進入真正的Doze模式還有很長的“一段路”需要走。

我們看看becomeInactiveIfAppropriateLocked的代碼:

void becomeInactiveIfAppropriateLocked() {
    .................
    //屏幕熄滅,未充電
    if ((!mScreenOn && !mCharging) || mForceIdle) {
        // Screen has turned off; we are now going to become inactive and start
        // waiting to see if we will ultimately go idle.
        if (mState == STATE_ACTIVE && mDeepEnabled) {
            mState = STATE_INACTIVE;
            ...............
            //重置事件
            resetIdleManagementLocked();

            //開始檢測是否可以進入Doze模式的Idle狀態
            //若終端沒有watch feature, mInactiveTimeout時間為30min
            scheduleAlarmLocked(mInactiveTimeout, false);
            ...............
        }

        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            mLightState = LIGHT_STATE_INACTIVE;
            .............
            resetLightIdleManagementLocked();
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
        }
    }
}

從上面的代碼可以看出,在DeviceIdleState中,用mState和mLightState來衡量終端是否真的進入了Doze模式。 我們目前僅關注mState變量的改變情況,mLightState的變化流程可類似分析。 此時,mState的狀態為INACTIVE。

3.1 scheduleAlarmLocked 我們跟進一下scheduleAlarmLocked函數:

void scheduleAlarmLocked(long delay, boolean idleUntil) {
    if (mMotionSensor == null) {
        //在onBootPhase時,獲取過位置檢測傳感器
        //如果終端沒有配置位置檢測傳感器,那麼終端永遠不會進入到真正的Doze ilde狀態
        // If there is no motion sensor on this device, then we won't schedule
        // alarms, because we can't determine if the device is not moving.
        return;
    }

    mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
    if (idleUntil) {
        //此時IdleUtil的值為false
        mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
    } else {
        //30min後喚醒,調用mDeepAlarmListener的onAlarm函數
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
    }
}

需要注意的是,DeviceIdleController一直在監控屏幕狀態和充電狀態,一但不滿足Doze模式的條件,前面提到的becomeActiveLocked函數就會被調用。mAlarmManager設置的定時喚醒事件將被取消掉,mDeepAlarmListener的onAlarm函數不會被調用。

因此,我們知道了終端必須保持Doze模式的入口條件長達30min,才會進入mDeepAlarmListener.onAlarm:

private final AlarmManager.OnAlarmListener mDeepAlarmListener
        = new AlarmManager.OnAlarmListener() {
    @Override
    public void onAlarm() {
        synchronized (DeviceIdleController.this) {
            //進入到stepIdleStateLocked函數
            stepIdleStateLocked("s:alarm");
        }
    }
};

此處沒有什麼多說的,直接調用了stepIdleStateLocked函數。 需要注意的是stepIdleStateLocked將決定DeviceIdleController狀態之間的轉移。 這種通過AlarmManager設定喚醒時間,然後通過回調接口來調用stepIdleStateLocked的方式,將被多次使用。

3.2 stepIdleStateLocked 此處沒有什麼多說的,直接來看stepIdleStateLocked函數:

void stepIdleStateLocked(String reason) {
    ..........
    final long now = SystemClock.elapsedRealtime();
    //個人覺得,下面這段代碼,是針對Idle狀態設計的
    //如果在Idle狀態收到Alarm,那麼將先喚醒終端,然後重新判斷是否需要進入Idle態
    //在介紹Doze模式原理時提到過,若應用調用AlarmManager的一些指定接口,仍然可以在Idle狀態進行工作
    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());
            becomeInactiveIfAppropriateLocked();
        }
        return;
    }

    //以下是Doze模式的狀態轉變相關的代碼
    switch (mState) {
        case STATE_INACTIVE:
            // We have now been inactive long enough, it is time to start looking
            // for motion and sleep some more while doing so.
            //保持屏幕熄滅,同時未充電達到30min,進入此分支

            //注冊一個mMotionListener,檢測是否移動
            //如果檢測到移動,將重新進入到ACTIVE狀態
            //相應代碼比較直觀,此處不再深入分析
            startMonitoringMotionLocked();

            //再次調用scheduleAlarmLocked函數,此次的時間仍為30min
            //也就說如果不發生退出Doze模式的事件,30min後將再次進入到stepIdleStateLocked函數
            //不過屆時的mState已經變為STATE_IDLE_PENDING
            scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);

            // Reset the upcoming idle delays.
            //mNextIdlePendingDelay為5min
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            //mNextIdleDelay為60min
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;

            //狀態變為STATE_IDLE_PENDING 
            mState = STATE_IDLE_PENDING;
            ............
            break;
        case STATE_IDLE_PENDING:
            //保持息屏、未充電、靜止狀態,經過30min後,進入此分支
            mState = STATE_SENSING;

            //保持Doze模式條件,4min後再次進入stepIdleStateLocked
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);

            //停止定位相關的工作
            cancelLocatingLocked();
            mNotMoving = false;
            mLocated = false;
            mLastGenericLocation = null;
            mLastGpsLocation = null;

            //開始檢測手機是否發生運動(這裡應該是更細致的側重於角度的變化)
            //若手機運動過,則重新變為active狀態
            mAnyMotionDetector.checkForAnyMotion();
            break;
        case STATE_SENSING:
            //上面的條件滿足後,進入此分支,開始獲取定位信息
            cancelSensingTimeoutAlarmLocked();
            mState = STATE_LOCATING;
            ............
            //保持條件30s,再次調用stepIdleStateLocked
            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;
            }

            // If we have a location provider, we're all set, the listeners will move state
            // forward.
            if (mLocating) {
                //無法定位則直接進入下一個case
                break;
            }
        case STATE_LOCATING:
            //停止定位和運動檢測,直接進入到STATE_IDLE_MAINTENANCE
            cancelAlarmLocked();
            cancelLocatingLocked();
            mAnyMotionDetector.stop();

        case STATE_IDLE_MAINTENANCE:
            //進入到這個case後,終端開始進入Idle狀態,也就是真正的Doze模式

            //定義退出Idle的時間此時為60min
            scheduleAlarmLocked(mNextIdleDelay, true);
            ............
            //退出周期逐步遞增,每次乘2
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
            ...........
            //周期有最大值6h
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            }

            mState = STATE_IDLE;
            ...........
            //通知PMS、NetworkPolicyManagerService等Doze模式開啟,即進入Idle狀態
            //此時PMS disable一些非白名單WakeLock;NetworkPolicyManagerService開始限制一些應用的網絡訪問
            //消息處理的具體流程比較直觀,此處不再深入分析
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
            break;

        case STATE_IDLE:
            //進入到這個case時,本次的Idle狀態暫時結束,開啟maintenance window

            // We have been idling long enough, now it is time to do some work.
            mActiveIdleOpCount = 1;
            mActiveIdleWakeLock.acquire();

            //定義重新進入Idle的時間為5min (也就是手機可處於Maintenance window的時間)
            scheduleAlarmLocked(mNextIdlePendingDelay, false);

            mMaintenanceStartTime = SystemClock.elapsedRealtime();
            //調整mNextIdlePendingDelay,乘2(最大為10min)
            mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                    (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));

            if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
                    mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            }

            mState = STATE_IDLE_MAINTENANCE;
            ...........
            //通知PMS等暫時退出了Idle狀態,可以進行一些工作
            //此時PMS enable一些非白名單WakeLock;NetworkPolicyManagerService開始允許應用的網絡訪問
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            break;
    }
}

至此,stepIdleStateLocked的流程介紹完畢。 我們知道了,在DeviceIdleController中,為終端定義了7中狀態,如下圖所示:手機被操作的時候為Active狀態。

當手機關閉屏幕或者拔掉電源的時候,手機開始判斷是否進入Doze模式。

經過一系列的狀態後,最終會進入到IDLE狀態,此時才算進入到真正的Doze模式,系統進入到了深度休眠狀態。 此時,系統中非白名單的應用將被禁止訪問網絡,它們申請的Wakelock也會被disable。 從上面的代碼可以看出,系統會周期性的退出Idle狀態,進入到MAINTENANCE狀態,集中處理相關的任務。 一段時間後,會重新再次回到IDLE狀態。每次進入IDLE狀態,停留的時間都會是上次的2倍,最大時間限制為6h。

當手機運動,或者點亮屏幕,插上電源等,系統都會重新返回到ACTIVIE狀態。

四、總結 本篇博客中,我們分析了Doze模式對應的服務DeviceIdleController。

在了解DeviceIdleController的初始化過程後,我們重點分析了其定義的狀態轉移過程。 當然這些分析集中在了框架的源碼分析上

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved