Android 8.1 之省電模式分析
1. 功能概述
Battery saver是Google在Android L上新增的選項,這個功能是在Setting -> Battery (–> more (androidO以前的路徑)) –> Battery saver,這個功能主要是為了在相同電量下能夠更長時間的使用手機,簡稱:“省電助手”。
開啟之後手機將處於省電模式,省電模式下電池使用量將大大降低,一些不必要的耗電操作將禁止。屬於一個很實用的功能。
2. 實現原理分析
下面我們來介紹一下Battery saver的實現原理與其做了什麼電源管理型別的優化。
2.1 Battery saver介面顯示與功能
圖2.1 Battery Saver介面
從圖2.1我們可以知道Battery saver有一個switch開關,還有一個選項(是否自動開啟battery saver)
1、先來看一下Battery saver的switch開關,當我們開啟Battery saver的時候
//packages/apps/Settings/src/com/android/settings/fuelgauge/BatterySaverSettings.java
private void trySetPowerSaveMode(boolean mode) {
if (!mPowerManager.setPowerSaveMode(mode)) {
…
}
- 1
- 2
- 3
- 4
- 5
2、先來看一下Battery saver的switch開關,當我們開啟Battery saver的時候
//frameworks/base/core/java/android/provider/Settings.java
public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";//電源保護模式的觸發門檻值
//packages/apps/Settings/src/com/android/settings/fuelgauge/BatterySaverSettings.java
// battery_saver_trigger_values是電源保護觸發門檻值的可選值,’0’代表關閉,’5’代表5%電量的時候自動進入電源保護模式,’15’代表15%電量的時候自動進入電源保護模式
mTriggerPref = new SettingPref(SettingPref.TYPE_GLOBAL, KEY_TURN_ON_AUTOMATICALLY,
Global.LOW_POWER_MODE_TRIGGER_LEVEL,
0, /*default*/
getResources().getIntArray(R.array.battery_saver_trigger_values))
<!-- Battery saver mode: allowable trigger threshold levels. -->
<integer-array name="battery_saver_trigger_values" translatable="false" >
<item>0</item>
<item>5</item>
<item>15</item>
</integer-array>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
2.2 進入Power Save Mode的條件
1、當應用呼叫了PMS的setPowerSaveMode進行電源保護模式,會進入PowerManagerService.java,裡面主要包括許可權檢查和進一步設定
//PowerManagerService.java
public boolean setPowerSaveMode(boolean mode) {
//這個操作需要呼叫者用有DEVICE_POWER許可權
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
//......
//進行電源保護模式內部設定
return setLowPowerModeInternal(mode);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2、電源保護模式內部設定setLowPowerModeInternal,裡面包含電源保護標誌位LOW_POWER_MODE的設定,電源保護模式mLowPowerModeSetting的設定
private boolean setLowPowerModeInternal(boolean mode) {
……
//此時會設定電源保護標誌位LOW_POWER_MODE設定
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE, mode ? 1 : 0);
mLowPowerModeSetting = mode;//電源保護模式設定
……
updateLowPowerModeLocked();//電源保護
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3、更新電源保護相關設定updateLowPowerModeLocked,該函式包括了大部分電源保護的邏輯處理。
1) 如果在充電中或者”非低電而且沒有開完機”情況下是不允許進入電源保護模式
2) 自動開啟電源保護的條件是:不在充電狀態、設定了自動進入電源保護、使用者沒有主動在低電下關閉低電模式、低電狀態
3) 只要手動設定電源保護mLowPowerModeSetting,或者自動進入電源保護,那麼電源保護模式lowPowerModeEnabled將會給開啟
4) 進行Power HAL的設定,android預設的default Power HAL是power.default.so,裡面的內容預設都是置空,也就是沒有任何操作。
5) 進行電源保護模式切換前會發生一個ACTION_POWER_SAVE_MODE_CHANGING的廣播,然後回撥所有監聽了電源保護的監聽器,最後切換成功後會發生ACTION_POWER_SAVE_MODE_CHANGED的廣播。
6) 會將mLowPowerModeListeners低電模式的監聽onLowPowerModeChanged全部回撥一遍
void updateLowPowerModeLocked() {
//如果在充電中是不允許進入電源保護模式,其中在androidO上新增在非低電量
//(當電量低於15%屬於低電模式,config_lowBatteryWarningLevel可以配置)
//而且還沒開完機的時候也是不允許進入該模式的
if ((mIsPowered || !mBatteryLevelLow && !mBootCompleted) && mLowPowerModeSetting) {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE, 0);
mLowPowerModeSetting = false;
}
// autoLowPowerModeEnabled代表是否需要自動開啟電源保護,條件是不在充電狀態、設定了自動進入電源保護、使用者沒有主動在低電下關閉低電模式、低電狀態
final boolean autoLowPowerModeEnabled = !mIsPowered && mAutoLowPowerModeConfigured
&& !mAutoLowPowerModeSnoozing && mBatteryLevelLow;
//只要手動設定電源保護mLowPowerModeSetting,或者自動進入電源保護,那麼電源保護模式lowPowerModeEnabled將會給開啟
final boolean lowPowerModeEnabled = mLowPowerModeSetting || autoLowPowerModeEnabled;
if (mLowPowerModeEnabled != lowPowerModeEnabled) {//電源保護模式進行了改變
mLowPowerModeEnabled = lowPowerModeEnabled;//設定當前是否處於電源保護模式
//進行Power HAL的設定,android預設的default Power HAL是power.default.so裡面是做空的
powerHintInternal(POWER_HINT_LOW_POWER, lowPowerModeEnabled ? 1 : 0);
//在androidO上是在開機完成後在進行設定
postAfterBootCompleted( new Runnable() {
@Override
public void run() {
Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
.putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, mLowPowerModeEnabled)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
//傳送ACTION_POWER_SAVE_MODE_CHANGING電源保護正在切換的廣播
mContext.sendBroadcast(intent);
……
for (int i=0; i<listeners.size(); i++) {
final PowerManagerInternal.LowPowerModeListener listener = listeners.get(i);
final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy(
listener.getServiceType(), lowPowerModeEnabled);
//回撥監聽了電源保護模式的listeners,androidO在此處回撥的方法有更加多的內容,包含多種策略
listener.onLowPowerModeChanged(result);
}
intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
//新增flag ACTION_POWER_SAVE_MODE_CHANGED),這個廣播在androidN以後就只會給動態註冊者接收
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
// 傳送ACTION_POWER_SAVE_MODE_CHANGED電源保護切換成功後的廣播
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
// 需要帶有相應許可權才會受到的ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL廣播,這個目前原始碼沒有使用
intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
Manifest.permission.DEVICE_POWER);
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
接下來講的是對系統實際的影響
2.3 Power Save Mode對系統的實際影響
2.3.1 螢幕亮度將會變成原來的50%
我們在設定了LOW_POWER_MODE電源保護模式的時候,PowerManagerService本身會對其進行監聽,監聽器改變後會進行顯示裝置狀態更新updateDisplayPowerStateLocked
private boolean updateDisplayPowerStateLocked(int dirty) {
……
//傳入電源保護模式
updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest);
……
mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
mRequestWaitForNegativeProximity);//更新顯示裝置的狀態請求
……
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
void updatePowerRequestFromBatterySaverPolicy(DisplayPowerRequest displayPowerRequest) {
PowerSaveState state = mBatterySaverPolicy.
getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS, mLowPowerModeEnabled);
//設定低電模式和螢幕亮度降低的比例,一般都是降低50%
displayPowerRequest.lowPowerMode = state.batterySaverEnabled;
displayPowerRequest.screenLowPowerBrightnessFactor = state.brightnessFactor;
}
顯示裝置狀態更新的控制器DisplayPowerController.java
private void updatePowerState() {
……
// 只要開啟電源保護模式,螢幕亮度減半
// 當然啦,需要大於最小亮度值
if (mPowerRequest.lowPowerMode) {
if (brightness > mScreenBrightnessRangeMinimum) {
// brightnessFactor就是剛才設定的降低亮度的比例,預設是50%
final int lowPowerBrightness = (int) (brightness * brightnessFactor);
brightness = Math.max(lowPowerBrightness, mScreenBrightnessRangeMinimum);
//預設最小亮度值mScreenBrightnessRangeMinimum一般都是1
brightness = Math.max(brightness / 2, mScreenBrightnessRangeMinimum);
}
……
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
2.3.2 systemui會對此操作進行一下介面上的更新
圖2.2 開啟電源保護後systemui介面
Ps:上述是androidO以前的介面,androidO的狀態列是紅色的。
該橙色/紅色是一種status bar的警告Warning的標識(androidO使用的是error標識是紅色),代表正處於電源保護模式(在傳送ACTION_POWER_SAVE_MODE_CHANGING的時候就會通知status狀態列更新)
//StatusBar.java
private void checkBarMode(int mode, int windowState, BarTransitions transitions) {
final boolean powerSave = mBatteryController.isPowerSave();//check whether you are in power mode
……
if (powerSave && getBarState() == StatusBarState.SHADE) {
mode = MODE_WARNING;//change mode to power mode,when in status bar normol state
}
transitions.transitionTo(mode, anim);
}
//BarTransitions.java
public void draw(Canvas canvas) {
int targetGradientAlpha = 0, targetColor = 0;
if (mMode == MODE_WARNING) {
targetColor = mWarning;//繪製bar的時候,將warning顏色賦值給status bar顯示
mWarning = Utils.getColorAttr(context, android.R.attr.colorError);
./res/values/themes.xml
<item name="colorError">@color/red</item>
./res/res/values/colors.xml
//androidO使用的是紅色,RGB只有R有顏色
<color name="red">#ffff0000</color>
//f4 51 1e切換成十進位制的話244 81 30,androidO以前使用的顏色
<color name="battery_saver_mode_color">#fff4511e</color><!-- deep orange 600 -->
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
圖2.3 battery_saver_mode_color的顏色值
紅色就沒必要查看了,只有紅色分量肯定是紅色。
2.3.3 modem進入低電模式
DeviceStateMonitor.java通過接收ACTION_POWER_SAVE_MODE_CHANGED的廣播將modem設定成低電模式。(AndroidO新增,說明google也意識到了功耗屬於android的通病,在modem這一塊也進行優化)
private void updateDeviceState(int eventType, boolean state) {
//..….
case EVENT_POWER_SAVE_MODE_CHANGED:
if (mIsPowerSaveOn == state) return;
mIsPowerSaveOn = state;
//設定裝置進入低電模式
sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn);
break;
//..….
private void sendDeviceState(int type, boolean state) {
// mPhone一般都是telcom通訊相關,mCi是RIL物件,modem相關設定
mPhone.mCi.sendDeviceState(type, state, null);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2.3.4 語音互動功能將關閉
SoundTriggerHelper.java會接收POWER_SAVE_MODE_CHANGED的廣播,語音互動voiceinteraction功能將關閉
//啟動語音識別
private int startRecognitionLocked(ModelData modelData, boolean notify) {
//……
if (!isRecognitionAllowed()) {//判斷是否執行識別
//……
MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
return STATUS_OK;
}
//……
private boolean isRecognitionAllowed() {
//在打電話、該服務禁止掉或者省電模式中不允許語言識別功能
return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2.3.5 地理位置資訊進行限制訪問
GnssLocationProvider.java會接收POWER_SAVE_MODE_CHANGED的廣播,限制gps使用,滅屏後會關閉gps
private void updateLowPowerMode() {
// 如果裝置是深度睡眠,不再允許使用GPS
boolean disableGps = mPowerManager.isDeviceIdleMode();
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.GPS);
switch (result.gpsMode) {//如果有相關GPS設定,一般都有
case BatterySaverPolicy.GPS_MODE_DISABLED_WHEN_SCREEN_OFF:
// 預設情況是低電模式在滅屏情況不允許GPS
disableGps |= result.batterySaverEnabled && !mPowerManager.isInteractive();
break;
}
//……
} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
2.3.6 震動將取消
震動將取消(原來如果有震動的話)的操作是在VibratorService.java
當處於電池保護模式下,除了來電顯示的震動(有條件的震動),其它都將關閉震動
//啟動震動的函式
private void startVibrationLocked(final Vibration vib) {
//判斷是否允許震動,關於低電模式是在這裡判斷
if (!isAllowedToVibrate(vib)) {
//……
//在鈴聲靜音(且設定了來電震動)或者不在震動模式(且沒有設定來電震動時)的情況下不允許通知型別的鈴聲震動
if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
!shouldVibrateForRingtone()) {
//……
//當然,如果你沒有許可權,什麼都不行
if (mode != AppOpsManager.MODE_ALLOWED) {
//……
}
//是否允許震動的函式
private boolean isAllowedToVibrate(Vibration vib) {
//如果是非低電模式下是允許震動的
if (!mLowPowerMode) {//onLowPowerModeChanged時會進行設定
return true;
}
//低電模式對於鈴聲震動不受限制
if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
return true;
}
//是否允許在低電模式下震動,一般這個值預設是false
if (!mAllowPriorityVibrationsInLowPowerMode) {
return false;
}
//如果允許在低電模式下震動,僅支援alarm,助手、網路電話進行震動
if (vib.mUsageHint == AudioAttributes.USAGE_ALARM ||
vib.mUsageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
return true;
}
//其它預設是不允許震動的
return false;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
2.3.7 對於網路資料鏈接會進行限制接入
NetworkPolicyManagerService.java會進行相應的限制接入處理,專門設定了一套FIREWALL_CHAIN_POWERSAVE低電模式的防火牆體系.
1、更新白名單範圍(允許訪問的網路的範圍),範圍之外的將停止socket網路連結
// 更新防火牆規則,低電模式下enabled==true,chain==FIREWALL_CHAIN_POWERSAVE此模式下防火牆屬於白名單模式,除了白名單以外的都不執行訪問網路
private void updateRulesForWhitelistedPowerSaveUL(boolean enabled, int chain,
SparseIntArray rules) {
if (enabled) {
// 更新臨時白名單、白名單、除了idleapp之外的白名單都將允許網路訪問
for (int ui = users.size() - 1; ui >= 0; ui--) {
UserInfo user = users.get(ui);
updateRulesForWhitelistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
updateRulesForWhitelistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
if (chain == FIREWALL_CHAIN_POWERSAVE) {
updateRulesForWhitelistedAppIds(uidRules,
mPowerSaveWhitelistExceptIdleAppIds, user.id);
}
}
// 如果是程序優先順序是前臺服務以上的允許網路訪問(前臺服務一般是PERCEPTIBLE使用者可感知的程序優先順序以上的服務)
for (int i = mUidState.size() - 1; i >= 0; i--) {
if (isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.valueAt(i))) {
uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
}
}
setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
} else {
setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
2、更新所有應用的訪問規則,除了前臺程序和在白名單內的程序,都將不允許訪問網路
// 更新應用在低電模式下所有app的訪問規則
private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
//當uid是media或者drm型別的不需要,或者之前已經授權INTERNET網路訪問的app,不許用更新
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
}
//是否處於空閒態
final boolean isIdle = !paroled && isUidIdle(uid);
//如果是低電模式會將進入受限模式
final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
//是否前臺程序,和上面isProcStateAllowedWhileIdleOrPowerSaveMode類似
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
//是否處在電源白名單之內
final boolean isWhitelisted = isWhitelistedBatterySaverUL(uid, mDeviceIdleMode);
final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
int newRule = RULE_NONE;
//......
if (isForeground) {
if (restrictMode) {
//如果是前臺程序,就算是受限模式下也會允許訪問網路
newRule = RULE_ALLOW_ALL;
}
} else if (restrictMode) {
//其它程序,非白名單將設定成拒絕訪問RULE_REJECT_ALL
newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
}
//......
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
2.3.8 WindowManagerService的動畫將關閉
WindowManagerService.java會將動畫關閉(onLowPowerModeChanged),但是此處由於一些第三方應用如果沒有考慮省電模式關閉動畫的話,可能會出現一些問題(設計時需要考慮動畫在低電模式關閉後的效果)。
public void onLowPowerModeChanged(PowerSaveState result) {
synchronized (mWindowMap) {
final boolean enabled = result.batterySaverEnabled;
//mAllowAnimationsInLowPowerMode代表是否在允許在低電模式下繼續使用動畫(一般是false,就是不允許),如果在低電模式下會把WMS的動畫都關閉
if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
mAnimationsDisabled = enabled;//是否關閉動畫mAnimationsDisabled
dispatchNewAnimatorScaleLocked(null);//將動畫設定更新到系統中去
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3. 具體使用
我們如果要使用這個新功能還是比較簡單的,
1、在AndroidManifest.xml加上許可權
<uses-permission android:name="android.permission.DEVICE_POWER" />
- 1
2、在原始碼中呼叫
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mPowerManager.setPowerSaveMode(mode)//需要DEVICE_POWER許可權
- 1
- 2
4. 實際效果
實驗步驟
1、電量是100%
2、登入QQ,然後按home鍵切換到後臺
3、開啟或者關閉省電模式
4、設定螢幕超時30分鐘,1個小時後觀察結果(就是半個小時亮屏半個小時滅屏)
5、開啟關閉省電模式各自取一個結果
=>關閉省電模式的結果
圖4.1 關閉省電模式的結果
=>開啟省電模式的結果