BatteryStatusService/BatteryService系统电量服务
Android框架层通过一个名为batterystatus的系统服务,实现了电量统计的功能。batterystatus获取电量的使用信息有两种方式:
被动(push):有些硬件模块(wifi, 蓝牙)在发生状态改变时,通知batterystatus记录状态变更的时间点
主动(pull):有些硬件模块(cpu)需要batterystatus主动记录时间点,譬如记录Activity的启动和终止时间,就能计算出Activity使用CPU的时间
电池的信息,电压,温度,充电状态等等,都是由BatteryService来提供的。BatteryService是跑在system_process当中,在系统初始化的时候启动,如下
在SystemServer.java中可以看到启动BatteryService的代码:
Log.i(TAG, "Starting Battery Service.");
BatteryService battery = new BatteryService(context);
ServiceManager.addService("battery", battery);
- 数据来源
BatteryService通过JNI(com_android_server_BatteryService.cpp)读取数据。BatteryService通过JNI注册的不仅有函数,还有变量。 如下:
//##############在BatteryService.java中声明的变量################
private boolean mAcOnline;
private boolean mUsbOnline;
private int mBatteryStatus;
private int mBatteryHealth;
private boolean mBatteryPresent;
private int mBatteryLevel;
private int mBatteryVoltage;
private int mBatteryTemperature;
private String mBatteryTechnology;
在BatteryService.java中声明的变量,在com_android_server_BatteryService.cpp中共用,即在com_android_server_BatteryService.cpp中其实操作的也是BatteryService.java中声明的变量
gFieldIds.mAcOnline = env->GetFieldID(clazz, "mAcOnline", "Z");
gFieldIds.mUsbOnline = env->GetFieldID(clazz, "mUsbOnline", "Z");
gFieldIds.mBatteryStatus = env->GetFieldID(clazz, "mBatteryStatus", "I");
gFieldIds.mBatteryHealth = env->GetFieldID(clazz, "mBatteryHealth", "I");
gFieldIds.mBatteryPresent = env->GetFieldID(clazz, "mBatteryPresent", "Z");
gFieldIds.mBatteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I");
gFieldIds.mBatteryTechnology = env->GetFieldID(clazz, "mBatteryTechnology", "Ljava/lang/String;");
gFieldIds.mBatteryVoltage = env->GetFieldID(clazz, "mBatteryVoltage", "I");
gFieldIds.mBatteryTemperature = env->GetFieldID(clazz, "mBatteryTemperature", "I");
上面这些变量的值,对应是从下面的文件中读取的,一只文件存储一个数值。
#define AC_ONLINE_PATH "/sys/class/power_supply/ac/online"
#define USB_ONLINE_PATH "/sys/class/power_supply/usb/online"
#define BATTERY_STATUS_PATH "/sys/class/power_supply/battery/status"
#define BATTERY_HEALTH_PATH "/sys/class/power_supply/battery/health"
#define BATTERY_PRESENT_PATH "/sys/class/power_supply/battery/present"
#define BATTERY_CAPACITY_PATH "/sys/class/power_supply/battery/capacity"
#define BATTERY_VOLTAGE_PATH "/sys/class/power_supply/battery/batt_vol"
#define BATTERY_TEMPERATURE_PATH "/sys/class/power_supply/battery/batt_temp"
#define BATTERY_TECHNOLOGY_PATH "/sys/class/power_supply/battery/technology"
在/sys/class/power_supply亦是Linux内核下面的目录。
- 数据传送
电池的这些信息是通过何种方式,被其他应用所获得的。可以想到的有两种方式,第一种,应用主动从BatteryService获得数据;第二种,BatteryService主动把数据传送给所关心的应用程序。
BatteryService采用的是第二种方式,所有的电池的信息数据是通过Intent传送出去的。在BatteryService.java中,Code如下:
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra("status", mBatteryStatus);
intent.putExtra("health", mBatteryHealth);
intent.putExtra("present", mBatteryPresent);
intent.putExtra("level", mBatteryLevel);
intent.putExtra("scale", BATTERY_SCALE);
intent.putExtra("icon-small", icon);
intent.putExtra("plugged", mPlugType);
intent.putExtra("voltage", mBatteryVoltage);
intent.putExtra("temperature", mBatteryTemperature);
intent.putExtra("technology", mBatteryTechnology);
ActivityManagerNative.broadcastStickyIntent(intent, null);
- 数据接收
应用如果想要接收到BatteryService发送出来的电池信息,则需要注册一个Intent为Intent.ACTION_BATTERY_CHANGED的BroadcastReceiver。
注册方法如下:
IntentFilter mIntentFilter = new IntentFilter();
mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(mIntentReceiver, mIntentFilter);
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction\(\);
if \(action.equals\(Intent.ACTION\_BATTERY\_CHANGED\)\) {
int nVoltage = intent.getIntExtra\("voltage", 0\);
if\(nVoltage!=0\){
mVoltage.setText\("V: " + nVoltage + "mV - Success..."\);
}
else{
mVoltage.setText\("V: " + nVoltage + "mV - fail..."\);
}
}
}
};
- 数据更新
电池的信息会随着时间不停变化,自然地,就需要考虑如何实时的更新电池的数据信息。在BatteryService启动的时候,会同时通过UEventObserver启动一个onUEvent Thread。
每一个Process最多只能有一个onUEvent Thread,即使这个Process中有多个UEventObserver的实例。当在一个Process中,第一次Call startObserving()方法后,这个UEvent thread就启动了。
而一旦这个UEvent thread启动之后,就不会停止。
//在BatteryService.java中
mUEventObserver.startObserving("SUBSYSTEM=power_supply");
private UEventObserver mUEventObserver = new UEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
update();
}
};
在UEvent thread中会不停调用 update()方法,来更新电池的信息数据。
BatteryStatusService成员变量:
BatteryStatsService创建
创建
BatteryStatsService
对象;用
BatteryStatsService.getActiveStatistics().readLocked()
和BatteryStatsService.getActiveStatistics().writeAsyncLocked()
操作日志文件;调用
BatteryStatsService.getActiveStatistics().setCallback()
设置回调,该回调也用于信息统计;调用
BatteryStatsService.publish()
注册到ServiceManager
,并从 /frameworks/base/core/res/res/xml/power_profile.xml 文件读取相关参数,该文件定义了和硬件相关的各种操作的耗电情况(以 mA.h 为单位);
BatteryStatsService
内部通过成员变量mStats
指向其子类BatteryStatsImpl
对象,它实现了Parcelable
接口;getStatistics()
方法:检查调用进程是否有
BATTERY_STATS
权限;将
BatteryStatsImpl
信息写入Parcel
包,序列化为一个 buffer,通过 Binder 传递;
BatteryStatus.Uid 统计各个模块耗电量
电量计算
BatteryStatsHelper.refreshStats()承载了电量计算的全部过程,在需要显示电量统计信息的地方,就可以通过BatteryStatsHelper这个类,来获取统计完成的电量信息。 Setting.apk就引用了这个类。电量计算大体可以分为两块:
AppUsage:应用程序耗电量计算,是指每一个应用程序使用硬件模块所产生的耗电量
MiscUsage:其他杂项耗电量计算,所谓杂项,其实就是用户比较关心的一大类,包括:待机的耗电量、亮屏的耗电量、通话的耗电量、Wifi的耗电量等
在BatteryStatsHelper.processAppUsage()这个方法中,实现了应用程序的电量计算(实际上统计的粒度是uid,不同的apk可以运行在同一个uid)。
首先,有一个统计时间段的概念,是通过统计类型mStatsType这个变量来表示的,有以下可选值:
// 统计从上一次充电以来至现在的耗电量
public static final int STATS_SINCE_CHARGED = 0;
// 统计系统启动以来到现在的耗电量
public static final int STATS_CURRENT = 1;
// 统计从上一次拔掉USB线以来到现在的耗电量
public static final int STATS_SINCE_UNPLUGGED = 2;
然后,我们来看一下这个函数体,它实现的是与APP耗电量计算的逻辑。
private void processAppUsage(SparseArray<UserHandle> asUsers) {
// 根据power\_profile.xml文件中的单位时间电流量定义,初始化一些计算参数 final int which = mStatsType; final int speedSteps = mPowerProfile.getNumSpeedSteps\(\); final double\[\] powerCpuNormal = new double\[speedSteps\]; final long\[\] cpuSpeedStepTimes = new long\[speedSteps\]; for \(int p = 0; p < speedSteps; p++\) { powerCpuNormal\[p\] = mPowerProfile.getAveragePower\(PowerProfile.POWER\_CPU\_ACTIVE, p\); } final double mobilePowerPerPacket = getMobilePowerPerPacket\(\); final double mobilePowerPerMs = getMobilePowerPerMs\(\); final double wifiPowerPerPacket = getWifiPowerPerPacket\(\); ... // 对一个UID进行电量统计, UID几乎可以等同于一个应用程序 SparseArray<? extends Uid> uidStats = mStats.getUidStats\(\); final int NU = uidStats.size\(\); for \(int iu = 0; iu < NU; iu++\) { // 1. 计算每一个UID中所有进程在CPU运算时的耗电量,比如应用程序在前台显示,或者后台有服务在占用CPU // CPU有不同的运行频率,每一个频率和该频率下的单位时间电流都在power\_profile.xml中有定义 // 2. 计算Wakelock占用的耗电量,Wakelock被占用,意味着CPU处于唤醒状态 // 在有些时候,并不需要进行CPU运算,但CPU仍处于唤醒状态 // 3. 计算使用数据网络的耗电量 // 应用程序使用数据网络上网时的耗电量,完成这部分通信的射频模块\(radio\) // 4. 计算使用wifi的耗电量 // wifi的使用又可以分为两个情况:扫描可用wifi\(SCAN\)和进行数据传输\(RUNNING\), // 这两种情况下的单位时间电流量是不同的 // 5. 计算使用传感器的耗电量 // GPS使用的耗电量计算也被包含在这里 }
}
最后,我们来总结一下应用程序的电量计算过程。Android通过一个名为BatteryStats.Uid的数据结构来维护一个应用程序的电量统计信息。 这个数据结构中,又包含很多子结构:
- Proc:表示属于Uid的进程,一个Uid中可能会有多个进程,每个进程都有CPU占用时间
- WakeLock:表示Uid持有的WakeLock锁的电量统计,一个Uid也可能会持有多个锁
- Mobile Raido:表示Uid使用数据流量的电量统计,譬如3G流量、4G流量
- Wifi:表示Uid使用wifi的电量统计
- Sendor:表示Uid使用传感器的电量统计
应用程序电量计算过程
Android会对每一个Uid进行电量计算,每次计算都会涉及到以上五个维度,每一个维度的计算几乎都要用到硬件模块在不同状态下单位时间的电流量,以及硬件模块在当前Uid下的使用时间。
杂项电量统计
实现了其他一些杂项的电量计算