編輯:關於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中,每個應用程序都有自己的進程,當需要在不同的進程之間傳遞對象時,該如何實現呢?顯然, Java中是不支持跨進程內存共享的。因此要傳遞對象,需要把
Android 自定義橫向滾動條。當你的橫向字段或者表格很多時候,顯示不下內容,可以使用很想滾動條進行滾動。豎向方面我添加了listview進行添加數據。兩者滾動互不干擾
最近項目開發,碰到一個ListView的需求。 向上滑動,隱藏Header。向下滑動,迅速顯示Header。 在GitHub中,找到了QuickReturn
一個手機號可以注冊兩個微信嗎?很多人還不知道一個手機號怎麼注冊2個甚至多個微信號,下面小編就跟大家分享一下方法吧!一個手機號怎麼注冊兩個微信: 登錄你(已