遇到的问题:设备电量使用情况中,蓝牙的耗电量很高,都可以达到百分之90。
第一次尝试解决:
首先先查看了power_profile.xml,发现该文件没有配置bluetooth.active和bluetooth.on两项,以为自己找到了原因,就把这两项配置了一下,结果,还是不行,又把这两项全部改成0,还是不行,没办法了,只能分析代码了。
(1) BatteryStatsHelper
系统设置主要通过该类来获取电量使用情况,通过调用processAppUsage得到最终的电量使用情况,我们重点关注和蓝牙有关的逻辑:
private void processAppUsage(SparseArray asUsers) {
final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
mStatsPeriod = mTypeBatteryRealtimeUs;
BatterySipper osSipper = null;
final SparseArray extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
final double totalPower = app.sumPower();
if (DEBUG && totalPower != 0) {
Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
makemAh(totalPower)));
}
if (totalPower != 0 || u.getUid() == 0) {
final int uid = app.getUid();
final int userId = UserHandle.getUserId(uid);
if (uid == Process.WIFI_UID) {
mWifiSippers.add(app);
} else if (uid == Process.BLUETOOTH_UID) {
mBluetoothSippers.add(app);
} else if (!forAllUsers && asUsers.get(userId) == null
&& UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
List list = mUserSippers.get(userId);
if (list == null) {
list = new ArrayList<>();
mUserSippers.put(userId, list);
}
list.add(app);
} else {
mUsageList.add(app);
}
if (uid == 0) {
osSipper = app;
}
}
}
}
private void addBluetoothUsage() {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
mStatsType);
aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
if (bs.totalPowerMah > 0) {
mUsageList.add(bs);
}
}
可以看出主要通过mBluetoothPowerCalculator的calculateRemaining和calculateApp方法来计算应用的蓝牙耗电值和蓝牙的硬件耗电值。
(2)BluetoothPowerCalculator中的逻辑
public class BluetoothPowerCalculator extends PowerCalculator {
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private static final String TAG = "BluetoothPowerCalculator";
private final double mIdleMa;
private final double mRxMa;
private final double mTxMa;
private double mAppTotalPowerMah = 0;
private long mAppTotalTimeMs = 0;
public BluetoothPowerCalculator(PowerProfile profile) {
mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX);
}
@Override
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
final BatteryStats.ControllerActivityCounter counter = u.getBluetoothControllerActivity();
if (counter == null) {
return;
}
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
double powerMah = counter.getPowerCounter().getCountLocked(statsType)
/ (double)(1000*60*60);
if (powerMah == 0) {
powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
/ (1000*60*60);
}
app.bluetoothPowerMah = powerMah;
app.bluetoothRunningTimeMs = totalTimeMs;
app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType);
app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType);
mAppTotalPowerMah += powerMah;
mAppTotalTimeMs += totalTimeMs;
}
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
final BatteryStats.ControllerActivityCounter counter =
stats.getBluetoothControllerActivity();
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
double powerMah = counter.getPowerCounter().getCountLocked(statsType)
/ (double)(1000*60*60);
if (powerMah == 0) {
// Some devices do not report the power, so calculate it.
powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
/ (1000*60*60);
}
// Subtract what the apps used, but clamp to 0.
powerMah = Math.max(0, powerMah - mAppTotalPowerMah);
if (DEBUG && powerMah != 0) {
Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs)
+ " power=" + BatteryStatsHelper.makemAh(powerMah));
}
app.bluetoothPowerMah = powerMah;
app.bluetoothRunningTimeMs = Math.max(0, totalTimeMs - mAppTotalTimeMs);
}
@Override
public void reset() {
mAppTotalPowerMah = 0;
mAppTotalTimeMs = 0;
}
}
可以看出每个BatteryStats.Uid对象都可以通过getBluetoothControllerActivity方法获取到一个counter对象,这个对象可以获取到蓝牙的接收数据时长,发送数据时长,空闲时长,还可以获取到一个powerMah,如果这个powerMah为0,则根据上述三个时长计算耗电量。在BluetoothPowerCalculator的构造方法中,初始化了三个时长对应的电流,电流的值怎么获取的,代码很简单,直接说结论:PowerProfile中有一个map,PowerProfile通过读取power_profile.xml来更新这个map的内容,但是有个坑,PowerProfile定义了一个数组,如果,在power_profile.xml中没有找到该项定义,他就要去读取系统内置资源的值,这就是主要的坑:
int[] configResIds = new int[]{
com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
com.android.internal.R.integer.config_bluetooth_operating_voltage_mv,
com.android.internal.R.integer.config_wifi_idle_receive_cur_ma,
com.android.internal.R.integer.config_wifi_active_rx_cur_ma,
com.android.internal.R.integer.config_wifi_tx_cur_ma,
com.android.internal.R.integer.config_wifi_operating_voltage_mv,
};
String[] configResIdKeys = new String[]{
POWER_BLUETOOTH_CONTROLLER_IDLE,
POWER_BLUETOOTH_CONTROLLER_RX,
POWER_BLUETOOTH_CONTROLLER_TX,
POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
POWER_WIFI_CONTROLLER_IDLE,
POWER_WIFI_CONTROLLER_RX,
POWER_WIFI_CONTROLLER_TX,
POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE,
};
从上面的代码可以看出蓝牙电流的key值为
mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX);
而对应的字符串为
public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle";
public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx";
public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx";
可以看出,已经和bluetooth.active,bluetooth.on没有关系了,这就是为什么改这两个没有用的原因,那为什么蓝牙的耗电量极其之大呢?由上面的逻辑可以看出来,主要根据三个时常和一个powerMah,必然这几个值有的错了。我们找找,这几个值是在哪更新的,最终我们发现了问题:
final double opVolt = mPowerProfile.getAveragePower(
PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
if (opVolt != 0) {
// We store the power drain as mAms.
mBluetoothActivity.getPowerCounter().addCountLocked(
(long) (info.getControllerEnergyUsed() / opVolt));
}
可以看到上述代码获取了PowerProfile中的电压值,powerMah的值为所消耗的能量除以电压值,结果发现我们的电压值为3,正常的一般为3300,意味着powerMah大了1100倍,这就是蓝牙为什么耗电量极其大的原因