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