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

Android Doze模式分析

編輯:關於Android編程

Doze模式是Android6.0上新出的一種模式,是一種全新的、低能耗的狀態,在後台只有部分任務允許運行,其他都被強制停止。當用戶一段時間沒有使用手機的時候,Doze模式通過延緩app後台的CPU和網絡活動減少電量的消耗。PowerManagerService中也有Doze模式,和此處的Doze模式不一樣,其實此處叫DeviceIdle模式更容易區分

如果一個用戶斷開了充電連接,關屏不動手機一段時間之後,設備進入Doze模式。在Doze模式中,系統嘗試去通過減少應用的網絡訪問和CPU敏感的服務來保護電池。它也阻止應用通過訪問網絡,並且延緩應用的任務、同步和標准alarms。

系統定期退出Doze模式(maintenancewindow)去讓app完成他們被延緩的動作。在maintenancewindow期間,系統運行所有掛起的同步、任務和alarms,同時也能訪問網絡

A

 

Doze模式的限制。

1.網絡接入被暫停

2.系統忽略wakelocks

3.標准的AlarmManageralarms被延緩到下一個maintenancewindow

4.如果你需要在Doze狀態下啟動設置的alarms,使用setAndAllowWhileIdle()或者setExactAndAllowWhileIdle()。

5.當有setAlarmClock()的alarms啟動時,系統會短暫退出Doze模式

6.系統不會掃描Wi-Fi

7.系統不允許syncadapters運行

8.系統不允許JobScheduler運行

Doze模式在系統中主要有DeviceIdleController來驅動。下面我們來分析下DeviceIdleController

 

DeviceIdleController的啟動和初始化

DeviceIdleController和PowerManagerService一樣都繼承自SystemService類,同樣是在SystemServer服務中啟動。

mSystemServiceManager.startService(DeviceIdleController.class);

同樣,在SystemServiceManager中的startService方法中利用反射的方法構造DeviceIdleController對象,然後調用DeviceIdleController的onStart方法來初始化。

 

DeviceIdleController的構造方法

 

public DeviceIdleController(Context context) {
        super(context);
        mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
        mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
    }

 

構造方法很簡單,只有兩步

1.創建一個deviceidle.xml文件,該文件位於data/system/目錄下。

2.創建了一個Handler用來處理消息

 

onStart方法

 

public void onStart() {
        ……
        synchronized (this) {
           //第1步,獲取Doze模式是否默認開啟
            mEnabled = getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_enableAutoPowerModes);
           //第2步,從systemConfig中讀取默認的系統應用的白名單
            SystemConfig sysConfig = SystemConfig.getInstance();
            ArraySet<string> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
            for (int i=0; i<allowpowerexceptidle.size(); string="" pkg="allowPowerExceptIdle.valueAt(i);" try="" applicationinfo="" ai="pm.getApplicationInfo(pkg," if="" int="" appid="UserHandle.getAppId(ai.uid);" catch="" packagemanager.namenotfoundexception=""> allowPower = sysConfig.getAllowInPowerSave();
            for (int i=0; i<allowpower.size(); string="" pkg="allowPower.valueAt(i);" try="" applicationinfo="" ai="pm.getApplicationInfo(pkg," if="" int="" appid="UserHandle.getAppId(ai.uid);" catch="" packagemanager.namenotfoundexception="" mconstants="new" mscreenon="true;" mcharging="true;" mstate="STATE_ACTIVE;" minactivetimeout="mConstants.INACTIVE_TIMEOUT;" new="" pre="">

這個方法中大致可以分為6部分

第1步:從配置文件中獲取Doze模式的開關值,默認為false

第2步:從SystemConfig中讀取Doze模式系統應用的白名單,這個白名單是已經在系統配置文件中配置好的,位於手機目錄system/ect/sysconfig中。

收集了配置的除了Idle模式都可以運行的白名單

第3步:從SystemConfig中讀取Doze模式的白名單

第2步和第3步主要用於讀取Doze模式下系統應用的白名單。

第4步:讀取deviceidle.xml文件,解析xml文件並將用戶應用的白名單讀入內存

第5步:設置Doze模式的白名單,通過updateWhitelistAppIdsLocked()方法將系統應用白名單和用戶應用的白名單合並,然後將白名單設置到PowerManagerService中

mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);

第6步:初始化一些變量,默認系統的屏幕為開啟,Doze模式默認為ACTIVE,默認為充電模式等

第7步:和PowerManagerService類似,將DeviceIdleController注冊到ServiceManager和LocalService中。

OnStart一共大致有以上7個步驟,主要作用是讀取系統白名單和應用白名單,並設置到PowerManagerService中。

下一步同樣和PowerManangerService一樣,當SystemSerivceReady之後回調onBootPhase方法,這個方法也比較簡單,主要是也是做一些初始化的功能。

OnBootPhase方法

public void onBootPhase(int phase) {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
            synchronized (this) {
                ……
                //初始化部分傳感器服務
                mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
                mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
                mLocationManager = (LocationManager) getContext().getSystemService(
                        Context.LOCATION_SERVICE);
                ……
                mAnyMotionDetector = new AnyMotionDetector(
                        (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
                        mHandler, mSensorManager, this);

                ……
                //注冊電池變化等廣播
                IntentFilter filter = new IntentFilter();
                filter.addAction(Intent.ACTION_BATTERY_CHANGED);
                filter.addAction(ACTION_STEP_IDLE_STATE);
                getContext().registerReceiver(mReceiver, filter);
                ……

               //更新白名單mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
                //注冊屏幕顯示的回調方法
                mDisplayManager.registerDisplayListener(mDisplayListener, null);
                updateDisplayLocked();
            }
        }
    }

可以看出onBootPhase方法主要是初始化一些傳感器,注冊了電池改變的廣播接收器,注冊了屏幕顯示變化的回調方法,最後調用updateDisplayLocked方法。

由於DeviceIdleController剛啟動,初始化的時候,screenOn默認為true且屏幕狀態未發生變化,最後直接調用becomeActiveLocked()方法,將Doze狀態設置為ACTIVE,並初始化一些值。

至此,DeviceIdleController就基本啟動和初始化完成了。邏輯比較簡單,下面我們將討論Doze模式的幾種狀態及切換邏輯

DeviceIdleController在onBootPhase方法中,注冊了一個ACTION_BATTERY_CHANGED廣播接收器,當電池狀態發生變化的時候觸發該廣播接收器的onReceive方法。

if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                int plugged = intent.getIntExtra("plugged", 0);
                updateChargingLocked(plugged != 0);
            }

當Battery狀態發生變化的時候,首先先根據Intent的參數判斷是否是充電狀態,然後調updateChargingLocked方法

void updateChargingLocked(boolean charging) {
        if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging);
        if (!charging && mCharging) {
            mCharging = false;
            if (!mForceIdle) {
                becomeInactiveIfAppropriateLocked();
            }
        } else if (charging) {
            mCharging = charging;
            if (!mForceIdle) {
                becomeActiveLocked("charging", Process.myUid());
            }
        }
    }

該方法中,根據當前的充電狀態,如果是由充電狀態變為未充電狀態的時候,調用becomeInactiveIfAppropriateLocked()方法,修改Doze模式的狀態為InActive狀態,如果是充電狀態則直接調用becomeActiveLocked方法修改Doze模式的狀態為Active狀態。

同時在onBootPhase方法中我們還設置了displayListener的監聽方法,當屏幕顯示狀態發生變化的時候,回調該接口。

public void onDisplayChanged(int displayId) {
            if (displayId == Display.DEFAULT_DISPLAY) {
                synchronized (DeviceIdleController.this) {
                    updateDisplayLocked();
                }
            }

當Display發生變化的時候,最終調用updateDisplayLocked來更新狀態

void updateDisplayLocked() {
        mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
        boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;
        if (!screenOn && mScreenOn) {
            mScreenOn = false;
            if (!mForceIdle) {
                becomeInactiveIfAppropriateLocked();
            }
        } else if (screenOn) {
            mScreenOn = true;
            if (!mForceIdle) {
                becomeActiveLocked("screen", Process.myUid());
            }
        }
    }

邏輯也比較簡單,首先獲得當前顯示狀態,如果顯示狀態是由亮屏到滅屏,那麼調用becomeInactiveIfAppropriateLocked()方法,將Doze模式的狀態設置為InActive,如果當前狀態是亮屏狀態,這直接調用becomeActiveLocked將Doze模式的狀態設置為Active。

Doze模式狀態轉換分析

becomeInactiveIfAppropriateLocked方法分析:

void becomeInactiveIfAppropriateLocked() {
        if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {
            mState = STATE_INACTIVE;
            resetIdleManagementLocked();
            scheduleAlarmLocked(mInactiveTimeout, false);
        }
    }

首先屏幕滅屏並且不是充電狀態,Doze狀態為Active的時候,

1.將狀態修改為InActice

2.重置一些變量及狀態

3.設置一個定時器,在一段時間mInactiveTimeout後觸發。時間mInactiveTimeout=30min

void resetIdleManagementLocked() {
        //重置Idle和maintance狀態中的時間間隔
        mNextIdlePendingDelay = 0;
        mNextIdleDelay = 0;
        //取消定時器
        cancelAlarmLocked();
        //取消傳感器和定位監測
        cancelSensingAlarmLocked();
        cancelLocatingLocked();
        stopMonitoringSignificantMotion();
        mAnyMotionDetector.stop();
    }

將mNextIdlePendingDelay和mNextIdleDelay兩個時間變量重置,取消定時器,停止定位和運動,位置檢測。

becomeActiveLocked方法分析:

void becomeActiveLocked(String activeReason, int activeUid) {
        if (mState != STATE_ACTIVE) {
            scheduleReportActiveLocked(activeReason, activeUid);
            mState = STATE_ACTIVE;
            mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
            resetIdleManagementLocked();
        }
    }

主要是將Doze的狀態設置為ACTIVE狀態,並重置相關的變量。執行scheduleReportActiveLocked方法,該方法發送了一個MESSAGE_REPORT_ACTIVE的消息給Handler。

case MSG_REPORT_ACTIVE: {
                    String activeReason = (String)msg.obj;
                    int activeUid = msg.arg1;
                    boolean needBroadcast = msg.arg2 != 0;
                    mLocalPowerManager.setDeviceIdleMode(false);
                    try {
                        mNetworkPolicyManager.setDeviceIdleMode(false);
                        mBatteryStats.noteDeviceIdleMode(false, activeReason, activeUid);
                    } catch (RemoteException e) {
                    }
                    if (needBroadcast) {
                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                    }
                } break

Handler處理邏輯,主要告訴PowerManagerService退出Doze模式,網絡和電量統計退出Doze模式。

該方法主要作用是修改Doze模式為Active,並通知相應的服務退出Doze模式。

下一步我們接著分析InActive狀態。當拔掉充電電源的時候或者屏幕滅屏的時候,調用becomeInactiveIfAppropriateLocked進入InActive狀態,當30min後,定時器觸發後,廣播接收器接收到ACTION_STEP_IDLE_STATE定時器觸發,並調用了setIdleStateLocked方法。

setIdleStateLocked方法關鍵代碼:

switch (mState) {
            case STATE_INACTIVE:
                startMonitoringSignificantMotion();
                scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);.
                mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;
                mState = STATE_IDLE_PENDING;
                break;
				……

1.調用startMonitoringSignificantNotion()方法,注冊SigMotion傳感器,監測重要的運動事件。

2.重新設置一個定時器,同樣在30min後觸發。

3.設置mNextIdlePendingDelay的時間為5min,mNextIdleDelay的時間為60min

4.修改當前的Doze狀態為IDLE_PENDING狀態

startMonitoringSignificantNotion()方法注冊了運動監測的傳感器,當傳感器觸發的時候,回調SigMotionListener接口,最終調用significantMotionLocked()方法來處理

void significantMotionLocked() {
        mSigMotionActive = false;
        handleMotionDetectedLocked(mConstants.MOTION_INACTIVE_TIMEOUT, "motion");
    }

    void handleMotionDetectedLocked(long timeout, String type) {
        if (mState != STATE_ACTIVE) {
            scheduleReportActiveLocked(type, Process.myUid());
            mState = STATE_ACTIVE;
            mInactiveTimeout = timeout;
            cancelSensingAlarmLocked();
            becomeInactiveIfAppropriateLocked();
        }
    }

處理邏輯,當前的狀態為Idle_pending,所以調用scheduleReportActiveLocked方法通知相關的服務退出Doze模式,將當前狀態修改為Active,最終調用becomeInactiveIfAppropriateLocked重新進入InActive狀態。根據此處邏輯可知,如果傳感器監測到有特殊的運動就隨時返回到InActive狀態。

如果沒有運動30min後觸發定時器,再次進入setIdleStateLocked方法

case STATE_IDLE_PENDING:
                mState = STATE_SENSING;
                scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);
                cancelLocatingLocked();
                mAnyMotionDetector.checkForAnyMotion();
                mNotMoving = false;
                mLocated = false;
                mLastGenericLocation = null;
                mLastGpsLocation = null;
                break;

此時將當前的Doze狀態設置為STATE_SENSING,同時設置了定時器,4min後觸發。同時開啟運動檢測,mAnyMotionDetector.checkForAnyMotion(),該方法利用傳感器,檢測手機是否移動,我們來看下關鍵代碼實現。

if (!mMeasurementInProgress && mAccelSensor != null) {
            if (mSensorManager.registerListener(mListener, mAccelSensor,
                    SAMPLING_INTERVAL_MILLIS * 1000)) {
                mWakeLock.acquire();
                mMeasurementInProgress = true;
                mRunningStats.reset();
            }

            Message msg = Message.obtain(mHandler, mMeasurementTimeout);
            msg.setAsynchronous(true);
            mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS);
        }

此處注冊了個傳感器,然後接受傳感器的數據,然後延遲3s執行方法。

首先,接收數據接口,數據接口會判斷當前的數據信息是否足夠,若足夠則調用status=stopOrientationMeasurementLocked(),根據當前傳感器的數據計算出當前手機的狀態,回調.onAnyMotionResult(status)方法。

或者,延遲3s再收集數據,調用status=stopOrientationMeasurementLocked(),根據當前傳感器的數據計算出當前手機的狀態,回調.onAnyMotionResult(status)方法。

public void onAnyMotionResult(int result) {
        if (result == AnyMotionDetector.RESULT_MOVED) {
            synchronized (this) {
                handleMotionDetectedLocked(mConstants.INACTIVE_TIMEOUT, "sense_motion");
            }
        } else if (result == AnyMotionDetector.RESULT_STATIONARY) {
            if (mState == STATE_SENSING) {
                synchronized (this) {
                    mNotMoving = true;
                    stepIdleStateLocked();
                }
            } 
……
    }

當檢測到手機的狀態後,根據狀態處理不同的邏輯

1.當手機處於MOVE狀態的時候,執行handleMotionDetectedLocked方法,將狀態重置為INACTIVE狀態,通知各個服務退出IDLE模式。

2.當手機固定不動的時候,講notMoveing變量置為true,同時執行

setIdleStateLocked方法,進入下一個狀態。

case STATE_SENSING:
                mState = STATE_LOCATING;
                scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);
                mLocating = true;
                mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,
                        mHandler.getLooper());
                if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
                    mHaveGps = true;
                    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
                            mGpsLocationListener, mHandler.getLooper());
                } else {
                    mHaveGps = false;
                }
                break;

當移動監測完成,或者4min後定時器觸發,當前狀態為STATE_LOCATION狀態。在設置30s的定時器,同時調用系統定位,當定位完成回調定位完成接口

void receivedGenericLocationLocked(Location location) {
       ……
        mLocated = true;
        if (mNotMoving) {
            stepIdleStateLocked();
        }
    }

    void receivedGpsLocationLocked(Location location) {
        ……
        mLocated = true;
        if (mNotMoving) {
            stepIdleStateLocked();
        }
    }

當接收到定位信息且當前手機位置沒有移動,就進入IDLE狀態。

      case STATE_LOCATING:
                cancelSensingAlarmLocked();
                cancelLocatingLocked();
                mAnyMotionDetector.stop();
            case STATE_IDLE_MAINTENANCE:
                scheduleAlarmLocked(mNextIdleDelay, true);
                mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                mState = STATE_IDLE;
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                break;

先取消上個狀態的沒有完成的定時器和定位,取消運動監測。設置下一個定時器mNextIdleDelay=60min後觸發。修改mNextIdleDelay的值為當前的2倍。最大值為6h,將狀態設置為IDLE狀態,發送IDLE的handler消息,接收到消息通知相關的服務進入IDLE狀態。

當60min過後,定時器觸發,進入IDLE_MAINTENANCE狀態。設置定時器5min後觸發,修改下次時間為2倍,最大時間為10min,發送MSG_REPORT_IDLE_OFF消息,在Handler中處理,通知各個服務退出IDLE狀態。

case STATE_IDLE:	
                scheduleAlarmLocked(mNextIdlePendingDelay, false);
                mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                        (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                mState = STATE_IDLE_MAINTENANCE;
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                break;

下次觸發再次進入IDLE狀態。

Doze模式的幾個狀態轉換基本分析完成,下面是各個狀態轉換的流程圖

V

Doze模式總共有7中狀態,手機被操作的時候為Active狀態,當手機關閉屏幕或者拔掉電源的時候,手機開始進入Doze模式,經過一系列的狀態後最終進入IDLE狀態,此時屬於深度休眠狀態,系統中的網絡,Wakelock等服務都會停止運行,當60min過後,系統進入IDLE_MAINTENANCE狀態,此時集中處理相關的任務,5min後再次進入IDLE狀態,每次進入IDLE狀態,時間都會是上次的2倍,最大時間限制為6h.

在這7中狀態中,隨時都會返回ACTIVE或者INACTIVE狀態。當手機運動,或者點亮屏幕,插上電源等,系統會返回到ACTIVIE和INACTIVE狀態。

PowerManagerService中Doze狀態的處理

以上分析完了Doze幾個狀態之間的轉換,下面我們分析下PowerManagerService中關於Doze的處理邏輯。

我們知道,當Doze模式轉換到Idle狀態之後,就會通知相關的服務進入IDLE狀態,其中PowerManangerService處理的方法為localPowerManager.setDeviceIdle(true)

void setDeviceIdleModeInternal(boolean enabled) {
        synchronized (mLock) {
            if (mDeviceIdleMode != enabled) {
                mDeviceIdleMode = enabled;
                updateWakeLockDisabledStatesLocked();
            }
        }
    }

將PowerManagerService中mDeviceIdleMode的值設置為true,然後調用updateWakeLockDisableStatesLocked()方法更新wakeLock的狀態。

private void updateWakeLockDisabledStatesLocked() {
        boolean changed = false;
        final int numWakeLocks = mWakeLocks.size();
        for (int i = 0; i < numWakeLocks; i++) {
            final WakeLock wakeLock = mWakeLocks.get(i);
            if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
                    == PowerManager.PARTIAL_WAKE_LOCK) {
                if (setWakeLockDisabledStateLocked(wakeLock)) {
                    changed = true;
                    if (wakeLock.mDisabled) {
                        //當前wakeLock的mDisabled變量為true,釋放掉該wakeLock
                        notifyWakeLockReleasedLocked(wakeLock);
                    } else {
                        notifyWakeLockAcquiredLocked(wakeLock);
                    }
                }
            }
        }
        if (changed) {
            mDirty |= DIRTY_WAKE_LOCKS;
            updatePowerStateLocked();
        }
    }

在更新WakeLock的信息時,遍歷所有的wakeLock,只處理wakeLock的類型為PARTIAL_WAKE_LOCK的wakeLock。調用setWakeLockDisabledStateLocked(wakeLock)方法更新wakeLock的disable的狀態值。在該方法中根據當前wakelock所有應用程序的appid來判斷該程序在IDLE狀態是是否可以正常運行,如果該appid合法且該appid不在設置的白名單中,該應用程序在IDLE狀態時是不能運行的,將該wakeLock的disabled的值置為true

如果該appid在白名單中,那麼該程序在IDLE狀態允許運行,將該wakeLock的disable狀態置為false.

private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) {
        if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
                == PowerManager.PARTIAL_WAKE_LOCK) {
            boolean disabled = false;
            if (mDeviceIdleMode) {
                final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
                if (appid >= Process.FIRST_APPLICATION_UID &&
                        Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
                        Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
                        mUidState.get(wakeLock.mOwnerUid,
                                ActivityManager.PROCESS_STATE_CACHED_EMPTY)
                                > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
                    disabled = true;
                }
            }
            if (wakeLock.mDisabled != disabled) {
                wakeLock.mDisabled = disabled;
                return true;
            }
        }
        return false;
    }

如果該wakeLock的disabled變量更新成功,當wakeLock的disabled的值為true,表示IDLE狀態次wakeLock不可用,調用notifyWakeLockReleasedLocked(wakelock)通知釋放該wakeLock。

當wakeLock的disabled的值為false,表示IDLE狀態此wakeLock可用,調用notifyWakeLockAcquiredLocked(wakeLock)通知獲取該wakeLock。

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