Android性能优化系列-腾讯matrix-电量优化之BatteryMonitorPlugin源码分析
前言
今天我们进入电量监控分析,matrix中电量监控相关的逻辑在BatteryMonitorPlugin类中。电量监控是一个很宽泛的概念,因为系统从启动之后,就在不停的消耗手机的电量,有些电量消耗是合理的,不需要过多关注,但有些电量消耗是异常的,这也是电量监控的目的,发现异常的电量消耗,从而进行优化,提升用户电池续航能力。
我们进入今天的代码分析,分析的对象是matrix中的BatteryMonitorPlugin,我们从它的几个关键方法入手。
- 构造方法
- start
- stop
构造方法
在构造BatteryMonitorPlugin的时候,会传递一个BatteryMonitorConfig,这个config除了包含一些常规的配置之外,还有一个关键点,就是它的enable方法,enable方法会传入一个Class<? extends MonitorFeature>
类型的class对象,并将它保存在features数组中。
public Builder enable(Class<? extends MonitorFeature> pluginClass) {
try {
config.features.add(pluginClass.newInstance());
} catch (Exception ignored) {
}
return this;
}
matrix中定义了很多的feature,我们看看都有哪些。
- MonitorFeature 接口
- AbsMonitorFeature 抽象类
- BatteryStatsFeature 用于记录电池温度,电池剩余电量等信息
- WifiMonitorFeature 用于记录wifi相关的操作
- BlueToothMonitorFeature 用于记录蓝牙相关的操作
- WakeLockMonitorFeature 用于记录亮屏相关的操作
- TrafficMonitorFeature 用于记录流量相关的操作
- NotificationMonitorFeature 用于记录通知相关的操作
- LooperTaskMonitorFeature 用于记录主线程消息队列相关的操作
- LocationMonitorFeature 用于记录定位相关的操作
- JiffiesMonitorFeature 用于记录jiffies相关的操作
- InternalMonitorFeature 用于记录内部状态相关的操作
- DeviceStatMonitorFeature 用于记录设备状态相关的操作
- CpuStatFeature 用于记录cpu状态相关的操作
- AppStatMonitorFeature 用于记录app状态相关的操作
- AlarmMonitorFeature 用于记录闹钟相关的操作
- CompositeMonitors 非AbsMonitorFeature子类,但是也比较关键
这里定义了所有有matrix框架认为的电量消耗大户,针对每一种耗电操作设计了一个feature类作为监听,这也是电量监控比其他类型的监控更复杂的地方,因为它涉及到的点非常多,后边我们会专项分析这些feature类,这里先有一个印象。
config配置完成,传入到BatteryMonitorPlugin的构造方法,构造方法中创建了一个BatteryMonitorCore对象,BatteryMonitorPlugin的核心实现,是通过这个对象完成的。
public BatteryMonitorPlugin(BatteryMonitorConfig config) {
mDelegate = new BatteryMonitorCore(config);
MatrixLog.i(TAG, "setUp battery monitor plugin with configs: " + config);
}
看一下它的构造方法,除了接收配置初始化配置之外,通过一个循环将所有的feature类进行了configure调用,将BatteryMonitorCore传递到每一个feature类中,于是feature持有了BatteryMonitorCore的引用。
@SuppressLint("VisibleForTests")
public BatteryMonitorCore(BatteryMonitorConfig config) {
for (MonitorFeature plugin : config.features) {
plugin.configure(this);
}
}
start
构造方法调用完成,接下来就要启动BatteryMonitorPlugin,我们继续来看start方法,调用的还是BatteryMonitorCore的start。
@Override
public void start() {
super.start();
mDelegate.start();
}
进入BatteryMonitorCore的start方法,这里有两个操作,首先启动所有的feature对象,调用它们的onTurnOn方法,然后通过BatteryEventDelegate开启监听,今天我们优先分析第二步,feature相关的操作太多,后边会通过单独的文章进行分析,现在我们只需要知道,所有的feature都是去拿不同场景下的信息即可。
public void start() {
synchronized (BatteryMonitorCore.class) {
if (!mTurnOn) {
for (MonitorFeature plugin : mConfig.features) {
plugin.onTurnOn();
}
mTurnOn = true;
}
if (BatteryEventDelegate.isInit()) {
BatteryEventDelegate.getInstance().attach(this).startListening();
}
}
}
startListening
广播注册
startListening主要是注册广播接收者,并监听对应的广播。方法很长,我们分步来看。 首先注册广播,我们关注一下它监听了哪些内容,见注释。
IntentFilter filter = new IntentFilter();
//电池状态改变的广播,电池温度改变等信息
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
//屏幕点亮
filter.addAction(Intent.ACTION_SCREEN_ON);
//屏幕熄灭
filter.addAction(Intent.ACTION_SCREEN_OFF);
//开始充电
filter.addAction(Intent.ACTION_POWER_CONNECTED);
//从充电到断电
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
//电池已经从低电量恢复为正常
filter.addAction(Intent.ACTION_BATTERY_OKAY);
//电池已经从正常变为低电量状态
filter.addAction(Intent.ACTION_BATTERY_LOW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//省电模式
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//低电耗模式(跟省电模式类似)
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
}
}
mContext.registerReceiver(new BroadcastReceiver());
ACTION_BATTERY_CHANGED监听
首先是频率的限制,每一分钟监听一次
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
long currMs = System.currentTimeMillis();
if (mLastBatteryChangedHandleMs > 0 && currMs - mLastBatteryChangedHandleMs < ONE_MIN) {
limited = true;
}
if (!limited) {
...
}
}
当满足条件时,进入子线程执行下面的逻辑。
mCore.getHandler().post(new Runnable() {
@Override
public void run() {
// 拿到电量剩余百分比
int currPct = BatteryCanaryUtil.getBatteryPercentage(mContext);
if (currPct >= 0 && currPct <= 1000) {
//当电量比上次检测变化超过BATTERY_POWER_GRADUATION(5)时,达到记录的条件
if (Math.abs(currPct - mLastBatteryPowerPct) >= BATTERY_POWER_GRADUATION) {
//注意这里,从core中获取BatteryStatsFeature的信息,
//BatteryStatsFeature记录的就是电量信息,拿到电量值,设置到
//BatteryStatsFeature中,外界可以通过拿到BatteryStatsFeature
//对象获取到实时变化的电量值信息
BatteryStatsFeature feat = mCore.getMonitorFeature(BatteryStatsFeature.class);
if (feat != null) {
feat.statsBatteryEvent(currPct);
}
}
}
// 拿到电池温度信息
int currTemp = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
if (currTemp >= 0 && currPct <= 1000) {
//温度变化超过15,就开始记录温度
if (Math.abs(currTemp - mLastBatteryTemp) >= BATTERY_TEMPERATURE_GRADUATION) {
//从mCore中拿到BatteryStatsFeature对象,将温度信息设置进去,
//外界可以通过拿到BatteryStatsFeature对象获取到实时变化的温度信息
BatteryStatsFeature feat = mCore.getMonitorFeature(BatteryStatsFeature.class);
if (feat != null) {
feat.statsBatteryTempEvent(currTemp);
}
}
}
}
});
ps:获取电量信息的方式是通过注册一个接受者为空的广播,此时会直接返回一个intent,从intent中取出的EXTRA_LEVEL是当前的电量,从0-100,EXTRA_SCALE为最大电量,通常是100,若电池用了几年,消耗了不少,可能会低于100
public static int getBatteryPercentageImmediately(Context context) {
Intent batIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batIntent != null) {
int level = batIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
if (scale > 0) {
return level * 100 / scale;
}
}
return -1;
}
其他广播的监听
收到除ACTION_BATTERY_CHANGED之外的广播时,会将其记录到对应的变量中,用于标明当前状态的变化,并将此变化值记录到BatteryStatsFeature对象中,所以其实可以看到BatteryStatsFeature就是一个电量信息的载体,并且是实时记录,因此外界只要拿到BatteryStatsFeature就可以获取到实时变化的电量信息。
int devStat = -1;
boolean notifyStateChanged = false, notifyBatteryLowChanged = false;
switch (action) {
case Intent.ACTION_SCREEN_ON:
devStat = AppStats.DEV_STAT_SCREEN_ON;
notifyStateChanged = true;
break;
case Intent.ACTION_SCREEN_OFF:
devStat = AppStats.DEV_STAT_SCREEN_OFF;
notifyStateChanged = true;
break;
case Intent.ACTION_POWER_CONNECTED:
devStat = AppStats.DEV_STAT_CHARGING;
notifyStateChanged = true;
break;
case Intent.ACTION_POWER_DISCONNECTED:
devStat = AppStats.DEV_STAT_UN_CHARGING;
notifyStateChanged = true;
break;
case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
devStat = BatteryCanaryUtil.isDeviceOnIdleMode(context) ? AppStats.DEV_STAT_DOZE_MODE_ON : AppStats.DEV_STAT_DOZE_MODE_OFF;
notifyStateChanged = true;
break;
case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
devStat = BatteryCanaryUtil.isDeviceOnPowerSave(context) ? AppStats.DEV_STAT_SAVE_POWER_MODE_ON : AppStats.DEV_STAT_SAVE_POWER_MODE_OFF;
notifyStateChanged = true;
break;
case Intent.ACTION_BATTERY_OKAY:
case Intent.ACTION_BATTERY_LOW:
notifyStateChanged = true;
notifyBatteryLowChanged = true;
break;
default:
break;
}
// 2. 记录状态变化
if (devStat != -1) {
if (mCore != null) {
BatteryStatsFeature feat = mCore.getMonitorFeature(BatteryStatsFeature.class);
if (feat != null) {
feat.statsDevStat(devStat);
}
}
}
// 3. 通知外界状态改变
if (notifyStateChanged) {
onSateChangedEvent(intent);
}
//电量低,记录低电量状态
if (notifyBatteryLowChanged) {
if (mCore != null) {
BatteryStatsFeature feat = mCore.getMonitorFeature(BatteryStatsFeature.class);
if (feat != null) {
feat.statsBatteryEvent(action.equals(Intent.ACTION_BATTERY_LOW));
}
}
onBatteryChangeEvent(intent);
}
至此start方法除feature逻辑外,都分析完了。
stop
stop方法也是调用的BatteryMonitorCore的stop,这里所做的就是将多个feature关闭,调用其onTurnOff方法。
public void stop() {
synchronized (BatteryMonitorCore.class) {
if (mTurnOn) {
//移除所有消息
mHandler.removeCallbacksAndMessages(null);
//关闭feature
for (MonitorFeature plugin : mConfig.features) {
plugin.onTurnOff();
}
mTurnOn = false;
}
}
}
电量信息的获取
前边分析的内容其实是一个电量信息存储的过程,那么存起来之后怎么取出来呈现呢?就是通过下面这个方法,并且获取到的都是实时的信息。如果要获取其他信息比如wifi,则通过此方法传入WifiMonitorFeature即可。
BatteryCanary.getMonitorFeature(BatteryStatsFeature.class, new Consumer<BatteryStatsFeature>() {
@Override
public void accept(final BatteryStatsFeature batteryStatsFeature) {
procTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
}
});
}
});
总结
今天我们简单分析了一下BatteryMonitorPlugin的源码作为电量优化分析的起点,可以大概的了解到,BatteryMonitorPlugin的工作机制大致可以分为两种,一种是通过各个feature类进行的不同场景下的信息统计;另一种是通过监听如电量、亮屏灭屏等广播来获取信息。今天的分析重点在于第二种,这种处理方式较简单,监听广播,更新信息。对于第一种不同类型的feature监听这一块,限于篇幅原因,不能过多的深入,BatteryMonitorPlugin包含的feature类型范围很广,无法在一篇文章中穷尽,所以需要分几篇专题文章来进行逐个分析,后边会陆陆续续更新。
转载自:https://juejin.cn/post/7292199621299699723