1. 功能概述
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的时候
备注:打开省电模式,还有一种方式就是通过SystemUI 下拉Toolbox点击开启
//packages/apps/Settings/src/com/android/settings/fuelgauge/BatterySaverSettings.java
private void trySetPowerSaveMode(boolean mode) {
if (!mPowerManager.setPowerSaveMode(mode)) {
…
}
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>
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);
}
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;
}
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);
}
});
}
}
接下来讲的是对系统实际的影响
2.3 Power Save Mode对系统的实际影响
2.3.1 屏幕亮度将会变成原来的50%
我们在设置了LOW_POWER_MODE省电模式的时候,PowerManagerService本身会对其进行监听,监听器改变后会进行显示设备状态更新handleSettingsChangedLocked ---> updateDisplayPowerStateLocked
private final class UserSwitchedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
handleSettingsChangedLocked();
}
}
}
private final class SettingsObserver extends ContentObserver {
public SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
synchronized (mLock) {
handleSettingsChangedLocked();
}
}
}
private void handleSettingsChangedLocked() {
updateSettingsLocked();
updatePowerStateLocked();
}
private void updatePowerStateLocked() {
if (!mSystemReady || mDirty == 0) {
return;
}
if (!Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
try {
// Phase 0: Basic state updates.
updateIsPoweredLocked(mDirty);
updateStayOnLocked(mDirty);
updateScreenBrightnessBoostLocked(mDirty);
// Phase 1: Update wakefulness.
// Loop because the wake lock and user activity computations are influenced
// by changes in wakefulness.
final long now = SystemClock.uptimeMillis();
int dirtyPhase2 = 0;
for (;;) {
int dirtyPhase1 = mDirty;
dirtyPhase2 |= dirtyPhase1;
mDirty = 0;
updateWakeLockSummaryLocked(dirtyPhase1);
updateUserActivitySummaryLocked(now, dirtyPhase1);
if (!updateWakefulnessLocked(dirtyPhase1)) {
break;
}
}
// Phase 2: Update display power state.
boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
// Phase 3: Update dream state (depends on display ready signal).
updateDreamLocked(dirtyPhase2, displayBecameReady);
// Phase 4: Send notifications, if needed.
finishWakefulnessChangeIfNeededLocked();
// Phase 5: Update suspend blocker.
// Because we might release the last suspend blocker here, we need to make sure
// we finished everything else first!
updateSuspendBlockerLocked();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
private boolean updateDisplayPowerStateLocked(int dirty) {
……
//传入省电模式
updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest);
……
mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
mRequestWaitForNegativeProximity);//更新显示设备的状态请求
……
}
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);
}
……
}
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 -->
图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);
}
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;
}
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;
}
//……
} }
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;
}
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);
}
}
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;
}
//......
}
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);//将动画设置更新到系统中去
}
}
}