編輯:關於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的初始化過程後,我們重點分析了其定義的狀態轉移過程。 當然這些分析集中在了框架的源碼分析上
本篇是Activity啟動模式篇的基礎篇,介紹Activity四種啟動模式的基本概念、Intent Flag設置啟動模式以及應用場景。在介紹四種啟動模式之前,先介紹一下
上篇文章主要介紹了Java內存分配相關的知識以及在Android開發中可能遇見的各種內存洩露情況並給出了相對應的解決方案,如果你還沒有看過上篇文章,建議點擊這裡閱讀一下,
摘要:Android L平台在圖形渲染方面有一項重要的改進,它引入了一個專門的線程用於執行渲染工作,UI線程負責生成的顯示列表(DisplayList),渲染線程負責重放
我們經常遇到一個需求,就是給別人使用我們工程的時候,為了能夠屏蔽代碼,把代碼封裝成jar包提供給第三方使用,但是這樣我們的資源文件怎麼給對方用呢? 網上有很多方法,有用C