[Android Framework] 8.1 Doze模式分析(一)——Doze簡介和DeviceIdleController的啟動
概述
Doze模式,官方翻譯為低電耗模式,是Andoriod6.0增加的一項系統服務,主要目的是為了優化電池效能,增加電池續航時間,Doze模式又分兩種模式:深度Doze模式(Deep Doze)和輕度Doze模式(Light Doze),如果使用者長時間沒有主動使用其裝置,處於靜止狀態且螢幕已關閉,則系統會使裝置進入Doze模式,也就是深度Doze模式。如果使用者關閉裝置螢幕但仍處於移動狀態時,則裝置進入輕度Doze模式,此外,輕度Doze模式只適合Android7.0及以上版本。
當用戶長時間未使用裝置時,裝置進入Doze模式,Doze模式會延遲應用後臺 CPU 和網路活動,從而延長電池續航時間。處於Doze模式的裝置會定期進入維護時段,在此期間,應用可以完成待進行的活動。然後,Doze模式會使裝置重新進入較長時間的休眠狀態,接著進入下一個維護時段。在達到幾個小時的休眠時間上限之前,平臺會周而復始地重複Doze模式休眠/維護的序列,且每一次都會延長Doze模式時長。處於Doze模式的裝置始終可以感知到動作,且會在檢測到動作時立即退出Doze模式。整個Doze圖示如下:
Deep Doze 和Light Doze模式對比如下:
操作 | 低電耗模式 | 輕度低電耗模式 |
---|---|---|
觸發因素 | 螢幕關閉、電池供電、靜止 | 螢幕關閉、電池供電(未插電) |
時間 | 隨維護時段依次增加 | 隨維護時段反覆持續 N 分鐘 |
限制 | 無法進行網路訪問、喚醒鎖忽略、 GPS/WLAN無法掃描、鬧鐘和SyncAdapter/JobScheduler被延遲。 | 無法進行網路訪問、SyncAdapter/JobScheduler |
行為 | 僅接收優先順序較高的推送通知訊息。 | 接收所有實時訊息(即時訊息、來電等)。優先順序較高的推送通知訊息可以暫時訪問網路。 |
退出 | 裝置有移動、和使用者有互動、螢幕開啟、鬧鐘響鈴 | 螢幕開啟。 |
接下來就分析Doze的實現原理。Doze模式是通過DeviceIdleController來實現的。
1.DeviceIdleController的啟動流程
DeviceIdleController(以下簡稱DIC)繼承於SystemService,因此也是一個系統服務,在分析PMS的時候說過,繼承於SytemService的服務啟動有以下幾個共同點:
- 1.在SystemServer中例項化並啟動,啟動時會執行SytemService的生命週期方法:
Constructor()->onStart()->onBootPhase()
- 2.內部維護一個Binder和其他服務進行IPC通訊;
- 3.內部維護一個Internal類用於和System程序進行互動;
下面就從SystemServer開始分析DIC的啟動流程。
在SystemServer啟動其他服務時啟動DIC:
private void startOtherServices() {
.............
mSystemServiceManager.startService(DeviceIdleController.class);
............
}
在SystemServiceManager中,通過反射的方式獲取了DIC物件,並且呼叫了onStart()方法:
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
....................
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
final T service;
try {
//通過反射獲取例項
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
}
//呼叫同名過載方法
startService(service);
return service;
}
....................
}
public void startService(@NonNull final SystemService service) {
// 加入到mServices列表中
mServices.add(service);
long time = SystemClock.elapsedRealtime();
try {
//呼叫onStart()開始服務
service.onStart();
} catch (RuntimeException ex) {
}
}
執行到這裡,DIC的啟動就開始了,再來看看最後一個生命週期方法onBootPhase()
的呼叫,這個方法表示啟動服務的過程,在SystemServer中會呼叫多次,從而在不同的啟動階段完成不同的工作,程式碼如下:
private void startOtherServices() {
.............
mSystemServiceManager.startBootPhase(SystemService.
PHASE_LOCK_SETTINGS_READY);
.............
mSystemServiceManager.startBootPhase(SystemService.
PHASE_SYSTEM_SERVICES_READY);
.............
mSystemServiceManager.startBootPhase(
SystemService.PHASE_ACTIVITY_MANAGER_READY);
.............
mSystemServiceManager.startBootPhase(
SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
.............
}
在SystemServiceManager中的startBootPhase()
方法中,遍歷已啟動的服務列表,呼叫onBootPhase():
public void startBootPhase(final int phase) {
if (phase <= mCurrentPhase) {
throw new IllegalArgumentException("Next phase must be larger than previous");
}
mCurrentPhase = phase;
Slog.i(TAG, "Starting phase " + mCurrentPhase);
try {
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
long time = SystemClock.elapsedRealtime();
try {
//呼叫每個service的onBootPhase()
service.onBootPhase(mCurrentPhase);
} catch (Exception ex) {
}
}
}
}
當這個方法執行完畢後,DIC的啟動就完成了,下面我們看看DIC的生命週期方法中做了什麼。首先看其構造方法:
public DeviceIdleController(Context context) {
super(context);
//建立可以執行原子操作的檔案,/data/system/deviceidle.xml
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
//建立Handler,和BackgroundThread的Looper進行繫結
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
構造方法中,首先建立在/data/sytem下建立了deviceidle.xml檔案,然後建立了一個Handler,並將BackgroundThread中Handler的Looper和該Handler進行繫結。BackgroundThread是一個單例模式實現的後臺執行緒,可以用於任何一個程序。
再來看看DIC的onStart()方法:
@Override
public void onStart() {
final PackageManager pm = getContext().getPackageManager();
synchronized (this) {
//Light doze模式和Deep Doze是否可用,預設不可用
mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
//獲取系統全域性配置資訊,如許可權
SystemConfig sysConfig = SystemConfig.getInstance();
//從配置檔案中讀取省電模式下的白名單列表且不在在idle狀態的白名單列表,即列表中的app能夠在省電模式下在後臺執行,
ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
for (int i=0; i<allowPowerExceptIdle.size(); i++) {
String pkg = allowPowerExceptIdle.valueAt(i);
try {
//獲取白名單列表中的系統應用
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
//新增到Map中,表示這些應用在省電模式下可後臺執行,但在Doze下後臺不可執行
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
//新增到SpareArray中,表示這是處於powersave白名單但不處於doze白名單的系統應用
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//從配置檔案中讀取時能夠在省電模式白名單列表,即包括不在doze白名單中的應用,也包括所有的白名單上的應用
ArraySet<String> 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);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
//新增到DIC中的白名單列表中
mPowerSaveWhitelistApps.put(ai.packageName, appid);
//新增到DIC中的系統應用白名單列表中
mPowerSaveWhitelistSystemAppIds.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//設定Contants內容觀察者
mConstants = new Constants(mHandler, getContext().getContentResolver());
//讀取/data/system/deviceidle.xml檔案,將讀取的app新增到表示使用者設定的白名單中
readConfigFileLocked();
//更新白名單列表
updateWhitelistAppIdsLocked();
//網路是否連線
mNetworkConnected = true;
//螢幕是否保持常亮
mScreenOn = true;
// Start out assuming we are charging. If we aren't, we will at least get
// a battery update the next time the level drops.
//是否充電
mCharging = true;
mState = STATE_ACTIVE;//裝置保持活動狀態,深度Doze的初始值
mLightState = LIGHT_STATE_ACTIVE;//裝置保持活動狀態,輕度Doze的初始值
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
mBinderService = new BinderService();
//釋出遠端服務
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
//釋出本地服務
publishLocalService(LocalService.class, new LocalService());
}
在onStart()方法中,首先通過SystemConfig讀取了兩類白名單列表:在低電量模式下後臺允許執行的應用的白名單、在低電量模式和Doze模式下都允許後臺執行的應用白名單;
然後讀取配置檔案/data/system/deviceidle.xml
,將讀取的應用包名新增到mPowerSaveWhiteListUserApps這個Map中,表示使用者新增到白名單中的應用。接下來更新白名單列表,然後給成員變數進行初始化。
最後,釋出了遠端服務和本地服務,從而可以和其他服務進行互動。釋出後其他服務中就可以通過這種獲取到DIC的BinderService從而進行互動:
IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
獲取本地服務通過如下方式:
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
在onStart()方法中還呼叫了用來更新白名單列表的方法,來看下這個方法:
private void updateWhitelistAppIdsLocked() {
//處於省電模式白名單但不處於Idle狀態白名單的app
mPowerSaveWhitelistExceptIdleAppIdArray =
buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
//處於所有的白名單的app,包括使用者新增的
mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
//使用者新增的白名單列表應用
mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
if (mLocalActivityManager != null) {
//將適用於所有情況的白名單列表通知給AMS
mLocalActivityManager.setDeviceIdleWhitelist
(mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalPowerManager != null) {
//將適用於所有情況的白名單列表通知給PMS
mLocalPowerManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalAlarmManager != null) {
//將使用者新增到白名單列表中的應用通知給AlarmManagerService
mLocalAlarmManager.setDeviceIdleUserWhitelist
(mPowerSaveWhitelistUserAppIdArray);
}
}
在這個方法中,會將三類白名單列表中的應用id新增到一個int陣列中,該方法在新增應用到白名單列表、將應用移除白名單列表,都會呼叫該方法來更新白名單列表。
執行完onStart()方法後,就開始執行最後一個生命週期方法onBootPhase()
方法了,該方法如下:
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
synchronized (this) {
mAlarmManager = (AlarmManager) getContext().getSystemService
(Context.ALARM_SERVICE);
mBatteryStats = BatteryStatsService.getService();
mLocalActivityManager = getLocalService(ActivityManagerInternal.class);
mLocalPowerManager = getLocalService(PowerManagerInternal.class);
mPowerManager = getContext().getSystemService(PowerManager.class);
//申請一個wakelock鎖保持CPU喚醒
mActiveIdleWakeLock = mPowerManager.newWakeLock
(PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_maint");
//設定wakelock鎖為非計數鎖,只要執行一次release()就能釋所有非計數鎖
mActiveIdleWakeLock.setReferenceCounted(false);
mGoingIdleWakeLock = mPowerManager.newWakeLock
(PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_going_idle");
//設定wakelock鎖為計數鎖,一次申請對應一次釋放
mGoingIdleWakeLock.setReferenceCounted(true);
mConnectivityService = (ConnectivityService)ServiceManager.getService(
Context.CONNECTIVITY_SERVICE);
mLocalAlarmManager = getLocalService(AlarmManagerService.
LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService
(Context.NETWORK_POLICY_SERVICE));
mSensorManager = (SensorManager)getContext().getSystemService
(Context.SENSOR_SERVICE);
//可用於自動省電模式時的感測器id,0表示沒有可用感測器
int sigMotionSensorId = getContext().getResources().getInteger(
com.android.internal.R.integer.config_
autoPowerModeAnyMotionSensor);
if (sigMotionSensorId > 0) {
//根據感測器id獲取感測器
mMotionSensor = mSensorManager.getDefaultSensor
(sigMotionSensorId, true);
}
//如果沒有指定任一感測器&&在沒有指定感測器情況下首選WristTilt
//感測器配置為true
if (mMotionSensor == null && getContext().getResources().getBoolean(
com.android.internal.R.bool.config_
autoPowerModePreferWristTilt)) {
//獲取一個WristTilt感測器(手腕抖動)
mMotionSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_WRIST_TILT_GESTURE, true);
}
if (mMotionSensor == null) {
//如果以上條件都不滿足,則獲取一個SMD感測器
mMotionSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_SIGNIFICANT_MOTION, true);
}
//是否在進入Doze模式時預先獲取位置
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;
//用於檢測裝置是否已靜止
mAnyMotionDetector = new AnyMotionDetector(
(PowerManager) getContext().getSystemService
(Context.POWER_SERVICE),
mHandler, mSensorManager, this, angleThreshold);
//用於Doze狀態發生改變時傳送廣播
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE
_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
//用於當輕度Doze狀態發生改變時傳送廣播
mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_
DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
//註冊監聽電池狀態改變的廣播
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
//註冊監聽解除安裝應用的廣播
filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mReceiver, filter);
//註冊監聽網路連線改變的廣播
filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getContext().registerReceiver(mReceiver, filter);
//註冊監聽亮滅屏的廣播
filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
getContext().registerReceiver(mInteractivityReceiver, filter);
//將適用於所有情況的白名單列表通知給AMS、PMS、AlarmManagerService
mLocalActivityManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
mLocalAlarmManager.setDeviceIdleUserWhitelist(
mPowerSaveWhitelistUserAppIdArray)
//更新互動狀態
updateInteractivityLocked();
}
//更新網路連線狀態
updateConnectivityState(null);
} else if (phase == PHASE_BOOT_COMPLETED) {
}
}
首先,只有在SystemServer中啟動階段為PHASE_SYSTEM_SERVICE_READY
時,才會進入到onBootPhase()
方法,也就是說,DIC的onBootPhase()方法在到達這一啟動階段時才執行。在onBootPhase()方法中,獲取了一些系統服務管理類和用於System程序的本地服務,如AlarmManager、PowerManagerInternal等,用於和它們對應的系統服務進行互動;
其次獲取一個Sensor,具體獲取哪種型別的Sensor根據判斷條件而定;接下來獲取了一個AnyMotionDector物件,該物件用來檢測裝置是否處於禁止狀態;然後註冊了四種廣播的監聽,分別是:
- 1.電源狀態發生改變時的廣播:
Intent.ACTION_BATTERY_CHANGED
,由BatteryService中傳送。 - 2.解除安裝應用時的廣播:
Intent.ACTION_PACKAGE_REMOVED
,由PKMS傳送; - 3.網路連線狀態改變時的廣播:
ConnectivityManager.CONNECTIVITY_ACTION
; - 4亮滅屏時的廣播:
Intent.SCREEN_ON/SCREEN_OFF
,由PMS相關的Notifier傳送;
最後,呼叫了一個用於更新互動狀態的updateInteractivityLocked()
方法和用於更新網路狀態的updateConnectivityState()
方法。先看看updateInteractivityLocked()
方法:
void updateInteractivityLocked() {
// The interactivity state from the power manager tells us whether the display is
// in a state that we need to keep things running so they will update at a normal
// frequency.
//獲取裝置是否處於互動狀態
boolean screenOn = mPowerManager.isInteractive();
if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);
//表示當前不處於互動狀態且上次處於互動狀態
if (!screenOn && mScreenOn) {
mScreenOn = false;
if (!mForceIdle) {//是否強制進入Idle
//進入Idle模式的入口方法
becomeInactiveIfAppropriateLocked();
}
} else if (screenOn) {
mScreenOn = true;
if (!mForceIdle) {
//退出Idle模式
becomeActiveLocked("screen", Process.myUid());
}
}
}
這個方法在Android 8.0及以前,是updateDisplayLocked(),在8.1中,修改了名字。這個方法中通過裝置當前的互動狀態,決定是否進入Doze模式或者退出Doze模式,如果當前處於不互動狀態(PMS中分析過,wakefulness=asleep
或者wakefulness=doze
),且上次處於互動狀態,則會呼叫becomeInteractiveIfAppropriateLocked()
方法,開始準備進入Doze的工作;如果條件相反,呼叫becomeActiveLocked()
,做退出Doze模式的工作。這兩個方法在下面的內容中進行分析。
分析到這裡,DIC的啟動流程就完畢了。啟動流程圖及主要工作如下所示:
下篇文章中將分析Light Doze
模式的實現流程。