5.5.2 BatteryStatsService分析
BatteryStatsService(以后简称BSS)主要功能是收集系统中各模块和应用进程用电量情况。抽象地说,BSS就是一块“电表”,不过这块“电表”不只是显示总的耗电量,而是分门别类地显示耗电量,力图做到更为精准。
和其他服务不太一样的是,BSS的创建和注册是在ActivityManagerService中进行的,相关代码如下:
[—>ActivityManagerService.java:ActivityManagerService]
private ActivityManagerService(){
……//创建BSS对象,传递一个File对象,指向/data/system/batterystats.bin
mBatteryStatsService=new BatteryStatsService(new File(
systemDir,"batterystats.bin").toString());
}
BBS发布的代码如下:
[—>ActivityManagerService.java:main]
//调用BSS的publish函数,在内部将其注册到ServiceManager
m.mBatteryStatsService.publish(context);
下面来分析BSS的构造函数。
1.BatteryStatsService介绍
让人大跌眼镜的是,BSS其实只是一个壳,具体功能委托BatteryStatsImpl(以后简称BSImpl)来实现。BatteryStatsService的代码如下:
[—>BatteryStatsService.java:BatteryStatsService]
BatteryStatsService(String filename){
mStats=new BatteryStatsImpl(filename);
}
图5-2展示了BSS及BSImpl的家族图谱。
图 5-2 BSS及BSImpl家族图谱
由图5-2可知:
BSS通过成员变量mStats指向一个BSImpl类型的对象。
BSImpl从BatteryStats类派生。更重要的是,该类实现了Parcelable接口,由此可知,BSImpl对象的信息可以写到Parcel包中,从而可通过Binder在进程间传递。实际上,在Android手机的设置中查到的用电信息就是来自BSImpl的。
BSS的getStatistics函数提供了查询系统用电信息的接口,该函数的代码如下:
[—>BatteryStatsService:getStatistics]
public byte[]getStatistics(){
mContext.enforceCallingPermission(//检查调用进程是否有BATTERY_STATS权限
android.Manifest.permission.BATTERY_STATS, null);
Parcel out=Parcel.obtain();
mStats.writeToParcel(out,0);//将BSImpl信息写到数据包中
byte[]data=out.marshall();//序列化为一个buffer,然后通过Binder传递
out.recycle();
return data;
}
由此可以看出,电量统计的核心类是BSImpl,下面就来分析它。
2.初识BSImpl
BSImpl功能是进行电量统计,那么是否存在计量工具呢?答案是肯定的,并且BSImpl使用了不止一种计量工具。
(1)计量工具和统计对象介绍
BSImpl一共使用了4种计量工具,如图5-3所示。
图 5-3 计量工具图例
由图5-3可知:
一共有两大类计量工具,Counter用于计数,Timer用于计时。
BSImpl实现了StopwatchTimer(秒表)、SamplingTimer(抽样计时)、Counter(计数器)和SamplingCounter(抽样计数)等4个具体的计量工具。
BSImpl中定义了一个Unpluggable接口。当手机插上USB线充电(不论是由交流电还是由USB供电)时,该接口的plug函数都被调用。反之,当拔去USB线时,该接口的unplug函数被调用。设置这个接口的目的是为了满足BSImpl对各种情况下系统用电量的统计要求。关于Unpluggable接口的作用,在后续内容中能见到。
虽然只有4种计量工具(笔者觉得已经相当多了),但是可以在很多地方使用它们。下面先来认识部分被挂牌要求统计用电量的对象,如表5-5所示。
表5-5中的电量统计项已经够多了吧?还不止这些,为了做到更精确,Android还希望能统计每个进程在各种情况下的耗电量。这是一项庞大的工程,怎么做到的呢?来看下一节的内容。
(2)BatteryStats. Uid介绍
在Android 4.0中,和进程相关的用电量统计并非以单个PID为划分单元,而是以Uid为组,相关类结构如图5-4所示。
由图5-4可知:
Wakelock用于统计该Uid对应进程使用WakeLock的用电情况。
Proc用于统计Uid中某个进程的电量使用情况。
Pkg用于统计某个特定Package的使用情况,其内部类Serv用于统计该Pkg中Service的用电情况。
Sensor用于统计传感器用电情况。
图 5-4 BatteryStats.Uid家族
基于以上的了解,以后分析将会轻松很多,下面来分析它的代码。
3.BSImpl流程分析
(1)构造函数分析
先分析构造函数,代码如下:
[—>BatteryStatsImpl.java:BatteryStatsImpl]
public BatteryStatsImpl(String filename){
//JournaledFile为日志文件对象,内部包含两个文件,原始文件和临时文件。目的是双备份,
//以防止在读写过程中文件信息丢失或出错
mFile=new JournaledFile(new File(filename),new File(filename+".tmp"));
mHandler=new MyHandler();//创建一个Handler对象
mStartCount++;
//创建表5-5中的用电统计项对象
mScreenOnTimer=new StopwatchTimer(null,-1,null, mUnpluggables);
for(int i=0;i<NUM_SCREEN_BRIGHTNESS_BINS;i++){
mScreenBrightnessTimer[i]=new StopwatchTimer(null,-100-i, null,
mUnpluggables);
}
mInputEventCounter=new Counter(mUnpluggables);
……
mOnBattery=mOnBatteryInternal=false;//设置这两位成员变量为false
initTimes();//①初始化统计时间
mTrackBatteryPastUptime=0;
mTrackBatteryPastRealtime=0;
mUptimeStart=mTrackBatteryUptimeStart=
SystemClock.uptimeMillis()*1000;
mRealtimeStart=mTrackBatteryRealtimeStart=
SystemClock.elapsedRealtime()*1000;
mUnpluggedBatteryUptime=getBatteryUptimeLocked(mUptimeStart);
mUnpluggedBatteryRealtime=getBatteryRealtimeLocked(mRealtimeStart);
mDischargeStartLevel=0;
mDischargeUnplugLevel=0;
mDischargeCurrentLevel=0;
initDischarge();//②初始化和电池level有关的成员变量
clearHistoryLocked();//③删除用电统计的历史记录
}
要看懂这段代码比较困难,主要原因是变量太多,并且没有注释说明。只能根据名字来推测了。在以上代码中除了计量工具外,还出现了三大类成员变量:
用于统计时间的成员变量,例如mUptimeStart、mTrackBatteryPastUptime等。这些参数的初始化函数为initTimes。注意,系统时间分为uptime和realtime。uptime和realtime的时间起点都从系统启动开始算(since the system was booted),但是uptime不包括系统休眠时间,而realtime包括系统休眠时间[1]。
用于记录各种情况下用于表电池电量的成员变量,如mDischargeStartLevel、mDischarge-CurrentLevel等,这些成员变量的初始化函数为initDischarge。
用于保存历史记录的HistroryItem,在clearHistoryLocked函数中初始化,主要有mHistory、mHistoryEnd等成员变量(这些成员变量在clearHistoryLocked函数中出现)。
上述这些成员变量的具体作用,只有通过后文的分析才能弄清楚。这里先介绍StopwacherTimer。
//调用方式
mPhoneSignalScanningTimer=new StopwatchTimer(null,-200+1,
null, mUnpluggables);
//mUnpluggables类型为ArrayList<Unpluggable>,用于保存插拔USB线时需要对应更新用电
//信息的统计对象
//StopwatchTimer的构造函数
StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer>timerPool,
ArrayList<Unpluggable>unpluggables){
//在本例中,uid为0,type为负数,timerPool为空,unpluggables为mUnpluggables
super(type, unpluggables);
mUid=uid;
mTimerPool=timerPool;
}
//Timer的构造函数
Timer(int type, ArrayList<Unpluggable>unpluggables){
mType=type;
mUnpluggables=unpluggables;
unpluggables.add(this);
}
在StopwatchTimer中比较难理解的就是unpluggables,根据注释说明,当插拔USB线时,需要更新用电统计的对象,应该将其加入到mUnpluggables数组中。
在启动秒表时,调用它的startRunningLocked函数,并传入BSImpl实例,代码如下:
[—>BatteryStatsImpl.java:StopwatchTimer.startRuningLocked]
void startRunningLocked(BatteryStatsImpl stats){
if(mNesting++==0){//嵌套调用控制
//getBatteryRealtimeLocked函数返回总的电池使用时间
mUpdateTime=stats.getBatteryRealtimeLocked(
SystemClock.elapsedRealtime()*1000);
if(mTimerPool!=null){//不讨论这种情况
}
mCount++;
mAcquireTime=mTotalTime;//计数控制,请读者阅读相关注释说明
}
}
当停用秒表时,调用它的stopRunningLocked函数,代码如下:
[—>BatteryStatsImpl.java:StopwatchTimer.stopRunningLocked]
void stopRunningLocked(BatteryStatsImpl stats){
if(mNesting==0){
return;//嵌套控制
}
if(—mNesting==0){
if(mTimerPool!=null){//不讨论这种情况
}else{
final long realtime=SystemClock.elapsedRealtime()*1000;
//计算此次启动/停止周期的时间
final long batteryRealtime=stats.getBatteryRealtimeLocked(realtime);
mNesting=1;
//mTotalTime代表从启动开始该秒停表一共记录的时间
mTotalTime=computeRunTimeLocked(batteryRealtime);
mNesting=0;
}
if(mTotalTime==mAcquireTime)mCount—;
}
}
在StopwatchTimer中定义了很多的时间参数,这些参数用于记录各种时间,例如总耗时、最近一次工作周期的耗时等。如果不是工作需要(例如研究Settings应用中和BatteryInfo相关的内容),读者仅需了解它的作用即可。
(2)ActivityManagerService和BSS交互
ActivityManagerService创建BSS后,还要进行几项操作,具体代码分别如下:
[—>ActivityManagerService. java:ActivityManagerService构造函数]
mBatteryStatsService=new BatteryStatsService(new File(
systemDir,"batterystats.bin").toString());
//操作通过BSImpl创建JournaledFile文件
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
//BSImpl的getIsOnBattery返回mOnBattery变量,初始化值为false
mOnBattery=DEBUG_POWER?true
:mBatteryStatsService.getActiveStatistics().getIsOnBattery();
//设置回调,该回调也用于信息统计,留到介绍ActivityManagerService时再来分析
mBatteryStatsService.getActiveStatistics().setCallback(this);
[—>ActivityManagerService.java:main]
m.mBatteryStatsService.publish(context);
[—>BatteryStatsService.java:publish]
public void publish(Context context){
mContext=context;
//注意,BSS服务称为batteryinfo,而BatteryService服务称为battery
ServiceManager.addService("batteryinfo",asBinder());
//PowerProfile见下文解释
mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());
//设置通信信号扫描超时时间
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout)
*1000L);
}
在以上代码中,比较有意思的是PowerProfile类,它将解析Android 4.0源代码/frameworks/base/core/res/res/xml/power_profile.xml文件。此XML文件存储的是各种操作(和硬件相关)的耗电情况,如图5-5所示。
图 5-5 PowerProfile文件示例
由图5-5可知,该文件保存了各种操作的耗电情况,以mA h(毫安时)为单位。Power-Profile的getNumSpeedSteps将返回CPU支持的频率值,目前在该XML中只定义了一个值,即400MHz。
注意 在编译时,各厂家会将特定硬件平台的power_profile.xml复制到输出目录。此处展示的power_profile.xml和硬件平台无关。
(3)BatteryService和BSS交互
BatteryService在它的processValues函数中和BSS交互,processValues函数的代码如下:
[—>BatteryService.java:processValues]
private void processValues(){
……
mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, mPlugType,
mBatteryLevel, mBatteryTemperature, mBatteryVoltage);
}
BSS的工作由BSImpl来完成,BsImpl的setBatteryState函数的代码如下:
[—>BatteryStatsImpl.java:setBatteryState]
public void setBatteryState(int status, int health, int plugType, int level,
int temp, int volt){
synchronized(this){
boolean onBattery=plugType==BATTERY_PLUGGED_NONE;//判断是否为电池供电
int oldStatus=mHistoryCur.batteryStatus;
……
if(onBattery){
//mDischargeCurrentLevel记录当前使用电池供电时的电池电量
mDischargeCurrentLevel=level;
mRecordingHistory=true;//mRecordingHistory表示需要记录一次历史值
}
//此时,onBattery为当前状态,mOnBattery为历史状态
if(onBattery!=mOnBattery){
mHistoryCur.batteryLevel=(byte)level;
mHistoryCur.batteryStatus=(byte)status;
mHistoryCur.batteryHealth=(byte)health;
……//更新mHistoryCur中的电池信息
setOnBatteryLocked(onBattery, oldStatus, level);
}else{
boolean changed=false;
if(mHistoryCur.batteryLevel!=level){
mHistoryCur.batteryLevel=(byte)level;
changed=true;
}
……//判断电池信息是否发生变化
if(changed){//如果发生变化,则需要增加一次历史记录
addHistoryRecordLocked(SystemClock.elapsedRealtime());
}
}
if(!onBattery&&status==BatteryManager.BATTERY_STATUS_FULL){
mRecordingHistory=false;
}
}
}
setBatteryState函数的工作如下:判断当前供电状态是否发生变化,由onBattery和mOnBattery进行比较决定。其中onBattery用于判断当前是否为电池供电,mOnBattery为上次调用该函数时得到的判断值。如果供电状态发生变化(其实就是经历一次USB拔插过程),则调用setOnBatteryLocked函数。如果供电状态未发生变化,则需要判断电池信息是否发生变化,例如电量和电压等。如果发生变化,则调用addHistoryRecordLocked。该函数用于添加一次历史记录。
接下来看setOnBatteryLocked函数的代码:
[—>BatteryStatsImpl.java:setOnBatteryLocked]
void setOnBatteryLocked(boolean onBattery, int oldStatus, int level){
boolean doWrite=false;
//发送一个消息给mHandler,将在内部调用ActivityManagerService设置的回调函数
Message m=mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
m.arg1=onBattery?1:0;
mHandler.sendMessage(m);
mOnBattery=mOnBatteryInternal=onBattery;
long uptime=SystemClock.uptimeMillis()*1000;
long mSecRealtime=SystemClock.elapsedRealtime();
long realtime=mSecRealtime*1000;
if(onBattery){
//关于电量信息统计,有一个值得注意的地方:当oldStatus为满电状态,或当前电量
//大于90,或mDischargeCurrentLevel小于20并且当前电量大于80时,要清空统计
//信息,以开始新的统计。也就是说在满足特定条件的情况下,电量使用统计信息会清零并重
//新开始。读者不妨用自己手机试一试
if(oldStatus==BatteryManager.BATTERY_STATUS_FULL||level>=90
||(mDischargeCurrentLevel<20&&level>=80)){
doWrite=true;
resetAllStatsLocked();
mDischargeStartLevel=level;
}
//读取/proc/wakelock文件,该文件反映了系统Wakelock的使用状态,
//感兴趣的读者可自行研究
updateKernelWakelocksLocked();
mHistoryCur.batteryLevel=(byte)level;
mHistoryCur.states&=~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
//添加一条历史记录
addHistoryRecordLocked(mSecRealtime);
//mTrackBatteryUptimeStart表示使用电池的开始时间,由uptime表示
mTrackBatteryUptimeStart=uptime;
//mTrackBatteryRealtimeStart表示使用电池的开始时间,由realtime表示
mTrackBatteryRealtimeStart=realtime;
//mUnpluggedBatteryUptime记录总的电池使用时间(不论中间插拔多少次)
mUnpluggedBatteryUptime=getBatteryUptimeLocked(uptime);
//mUnpluggedBatteryRealtime记录总的电池使用时间
mUnpluggedBatteryRealtime=getBatteryRealtimeLocked(realtime);
//记录电量
mDischargeCurrentLevel=mDischargeUnplugLevel=level;
if(mScreenOn){
mDischargeScreenOnUnplugLevel=level;
mDischargeScreenOffUnplugLevel=0;
}else{
mDischargeScreenOnUnplugLevel=0;
mDischargeScreenOffUnplugLevel=level;
}
mDischargeAmountScreenOn=0;
mDischargeAmountScreenOff=0;
//调用doUnplugLocked函数
doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
}else{
……//处理使用USB充电的情况,请读者在上面讨论的基础上自行分析
}
……//记录信息到文件
}
}
doUnplugLocked函数将更新对应信息,该函数比较简单,无须赘述。另外,addHistory-RecordLocked函数用于增加一条历史记录(由HistoryItem表示),读者也可自行研究。
从本节的分析可知,Android将电量统计分得非常细,例如由电池供电的情况要统计,由USB/AC充电的情况也要统计,因此有了setBatteryState函数。
(4)PowerManagerService和BSS交互
PMS和BSS交互是最多的,此处以noteScreenOn和noteUserActivity为例,来介绍BSS到底是如何统计电量的。
先来看noteScreenOn函数。当开启屏幕时,PMS会调用BSS的noteScreenOn以通知屏幕开启,该函数在内部调用BSImpl的noteScreenOnLocked,其代码如下:
[—>BatteryStatsImpl. java:noteScreenOnLocked]
public void noteScreenOnLocked(){
if(!mScreenOn){
mHistoryCur.states|=HistoryItem.STATE_SCREEN_ON_FLAG;
//增加一条历史记录
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mScreenOn=true;
//启动mScreenOnTime秒停表,其工作就是记录时间,读者可自行研究其内部实现
mScreenOnTimer.startRunningLocked(this);
if(mScreenBrightnessBin>=0)//启动对应屏幕亮度的秒停表(参考表5-5)
mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);
//屏幕开启也和内核WakeLock有关,所以这里一样要更新WakeLock的用电统计
noteStartWakeLocked(-1,-1,"dummy",WAKE_TYPE_PARTIAL);
if(mOnBatteryInternal)
updateDischargeScreenLevelsLocked(false, true);
}
}
再来看noteUserActivity,当有输入事件触发PMS的userActivity时,该函数被调用,代码如下:
[—>BatteryStatsImpl.java:noteUserActivityLocked]
//BSS的noteUserActivity将调用BSImpl的noteUserActivityLocked
public void noteUserActivityLocked(int uid, int event){
getUidStatsLocked(uid).noteUserActivityLocked(event);
}
先是调用getUidStatsLocked以获取一个Uid对象,如果该Uid是首次出现的,则要在内部创建一个Uid对象。下面直接介绍Uid的noteUserActivityLocked函数:
[—>BatteryStatsImpl.java:Uid:noteUserActivityLocked]
public void noteUserActivityLocked(int type){
if(mUserActivityCounters==null){
initUserActivityLocked();
}
if(type<0)type=0;
else if(type>=NUM_USER_ACTIVITY_TYPES)
type=NUM_USER_ACTIVITY_TYPES-1;
//noteUserActivityLocked只是调用对应type的Counter的stepAtomic函数
//每个Counter内部都有个计数器,stepAtomic使该计数器增1
mUserActivityCounters[type].stepAtomic();
}
mUserActivityCounters为一个7元Counter数组,该数组对应7种不同的输入事件类型,在代码中,由BSImpl的成员变量USER_ACTIVITY_TYPES表示,如下所示:
static final String[]USER_ACTIVITY_TYPES={
"other","cheek","touch","long_touch","touch_up","button","unknown"
};
另外,在LocalPowerManager中,也定义了相关的type值,如下所示:
[—>LocalPowerManager.java]
public interface LocalPowerManager{
public static final int OTHER_EVENT=0;
public static final int BUTTON_EVENT=1;
public static final int TOUCH_EVENT=2;//目前只使用这3种事件
……
}
[1]读者可阅读SDK文档中关于SystemClock类的说明。