編輯:關於Android編程
Doze模式是Android6.0上新出的一種模式,是一種全新的、低能耗的狀態,在後台只有部分任務允許運行,其他都被強制停止。當用戶一段時間沒有使用手機的時候,Doze模式通過延緩app後台的CPU和網絡活動減少電量的消耗。PowerManagerService中也有Doze模式,和此處的Doze模式不一樣,其實此處叫DeviceIdle模式更容易區分
如果一個用戶斷開了充電連接,關屏不動手機一段時間之後,設備進入Doze模式。在Doze模式中,系統嘗試去通過減少應用的網絡訪問和CPU敏感的服務來保護電池。它也阻止應用通過訪問網絡,並且延緩應用的任務、同步和標准alarms。
系統定期退出Doze模式(maintenancewindow)去讓app完成他們被延緩的動作。在maintenancewindow期間,系統運行所有掛起的同步、任務和alarms,同時也能訪問網絡
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和PowerManagerService一樣都繼承自SystemService類,同樣是在SystemServer服務中啟動。
mSystemServiceManager.startService(DeviceIdleController.class);
同樣,在SystemServiceManager中的startService方法中利用反射的方法構造DeviceIdleController對象,然後調用DeviceIdleController的onStart方法來初始化。
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用來處理消息
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。
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模式的幾個狀態轉換基本分析完成,下面是各個狀態轉換的流程圖

Doze模式總共有7中狀態,手機被操作的時候為Active狀態,當手機關閉屏幕或者拔掉電源的時候,手機開始進入Doze模式,經過一系列的狀態後最終進入IDLE狀態,此時屬於深度休眠狀態,系統中的網絡,Wakelock等服務都會停止運行,當60min過後,系統進入IDLE_MAINTENANCE狀態,此時集中處理相關的任務,5min後再次進入IDLE狀態,每次進入IDLE狀態,時間都會是上次的2倍,最大時間限制為6h.
在這7中狀態中,隨時都會返回ACTIVE或者INACTIVE狀態。當手機運動,或者點亮屏幕,插上電源等,系統會返回到ACTIVIE和INACTIVE狀態。
以上分析完了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。
Android中如何利用AIDL機制調用遠程服務
在Android中,每個應用程序都有自己的進程,當需要在不同的進程之間傳遞對象時,該如何實現呢?顯然, Java中是不支持跨進程內存共享的。因此要傳遞對象,需要把
Android 自定義橫向滾動條
Android 自定義橫向滾動條。當你的橫向字段或者表格很多時候,顯示不下內容,可以使用很想滾動條進行滾動。豎向方面我添加了listview進行添加數據。兩者滾動互不干擾
Android開源項目QuickReturnHeader分析
最近項目開發,碰到一個ListView的需求。 向上滑動,隱藏Header。向下滑動,迅速顯示Header。 在GitHub中,找到了QuickReturn
一個手機號能注冊多個微信嗎 手機號注冊兩個微信號方法
一個手機號可以注冊兩個微信嗎?很多人還不知道一個手機號怎麼注冊2個甚至多個微信號,下面小編就跟大家分享一下方法吧!一個手機號怎麼注冊兩個微信: 登錄你(已