ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
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()); } ~~~ **ActivityManagerService.java::main** ~~~ //调用BSS的publish函数,在内部将其注册到ServiceManager m.mBatteryStatsService.publish(context); ~~~ 下面来分析BSS的构造函数,见识一下这块电表的样子。 1. BatteryStatsService介绍 让人大跌眼镜的是,BSS其实只是一个壳,具体功能委托BatteryStatsImpl(以后简称BSImpl)来实现,代码如下: **BatteryStatsService.java::BatteryStatsService构造函数** ~~~ BatteryStatsService(String filename) { mStats = new BatteryStatsImpl(filename); } ~~~ 图5-2展示了BSS及BSImpl的家族图谱。 :-: ![](http://img.blog.csdn.net/20150803122147777?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图5-2 BSS及BSImpl家族图谱 由图5-2可知: - BSS通过成员变量mStats指向一个BSImpl类型的对象。 - BSImpl从BatteryStats类派生。更重要的是,该类实现了Parcelable接口,由此可知,BSImpl对象的信息可以写到Parcel包中,从而可通过Binder在进程间传递。实际上,在Android手机的设置中查到的用电信息就是来自BSImpl的。 BSS的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(); returndata; } ~~~ 由此可以看出,电量统计的核心类是BSImpl,下面就来分析它。 2. 初识BSImpl BSImpl功能是进行电量统计,那么是否存在计量工具呢?答案是肯定的,并且BSImpl使用了不止一种的计量工具。 (1) 计量工具和统计对象介绍 BSImpl一共使用了4种计量工具,如图5-3所示。 :-: ![](http://img.blog.csdn.net/20150803122205478?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图5-3 计量工具图例 由图5-3可知: - 一共有两大类计量工具,Counter用于计数,Timer用于计时。 - BSImpl实现了StopwatchTimer(即所谓的秒表)、SamplingTimer(抽样计时)、Counter和SamplingCounter(抽样计数)等4个具体的计量工具。 - BSImpl中定义了一个Unpluggable接口。当手机插上USB线充电(不论是由AC还是由USB供电)时,该接口的plug函数被调用。反之,当拔去USB线时,该接口的unplug函数被调用。设置这个接口的目的是为了满足BSImpl对各种情况下系统用电量的统计要求。关于Unpluggable接口的作用,在后续内容中可以能见到。 虽然只有4种计量工具(笔者觉得已经相当多了),但是可以在很多地方使用它们。下面先来认识部分被挂牌要求统计用电量的对象,如表5-5所示。 :-: ![ 用电量统计项](https://box.kancloud.cn/29c4adc2bf3a5e42dd6b8d3ffb0a24de_792x344.png =792x344) 表5-5 用电量统计项 表5-5中的电量统计项已经够多了吧?还不止这些,为了做到更精确,Android还希望能统计每个进程在各种情况下的耗电量。这是一项庞大的工程,怎么做到的呢?来看下一节的内容。 (2) BatteryStats.Uid介绍 在Android 4.0中,和进程相关的用电量统计并非以单个PID为划分单元,而是以Uid为组,相关类结构如图5-4所示。 :-: ![](http://img.blog.csdn.net/20150803122221423?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图5-4 BatteryStats.Uid家族 由图5-4可知: - Wakelock用于统计该Uid对应进程使用wakeLock的情况。 - Proc用于统计Uid中某个进程的电量使用情况。 - Pkg用于统计某个特定Package的使用情况,其内部类Serv用于统计该Pkg中Service的用电情况。 - Sensor用于统计传感器用电情况。 基于以上的了解,以后分析将会轻松很多,下面来分析它的代码。 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 (inti=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包括系统休眠时间[^platform]。 - 用于记录各种情况下电池电量的变量,如mDischargeStartLevel、mDischargeCurrentLevel等,这些成员变量的初始化函数为initDischarge。 - 用于保存历史记录的HistroryItem,在clearHistoryLocked函数中初始化,主要有mHistory、mHistoryEnd等成员变量(这些成员在clearHistoryLocked函数中出现)。 上述这些成员变量的具体作用,只有通过后文的分析才能弄清楚。这里先介绍StopwacherTimer。 ~~~ //调用方式 mPhoneSignalScanningTimer = newStopwatchTimer(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实例,代码如下: ~~~ void startRunningLocked(BatteryStatsImpl stats) { if(mNesting++ == 0) {//嵌套调用控制 // getBatteryRealtimeLocked函数返回总的电池使用时间 mUpdateTime = stats.getBatteryRealtimeLocked( SystemClock.elapsedRealtime()* 1000); if (mTimerPool != null) {//不讨论这种情况 } mCount++; mAcquireTime = mTotalTime;//计数控制,请读者阅读相关注释说明 } } ~~~ 当停用秒表时,调用它的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(newFile( 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所示。 :-: ![](http://img.blog.csdn.net/20150803122238418?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图5-5 PowerProfile文件示例 由图5-5可知,该文件保存了各种操作的耗电情况,以mAh(毫安)为单位。PowerProfile的getNumSpeedSteps将返回CPU支持的频率值,目前在该XML中只定义了一个值,即400MHz。 注意在编译时,各厂家会将特定硬件平台的power_profile.xml复制到输出目录。此处展示的power_profile.xml和硬件平台无关。 (3) BatteryService和BSS交互 BatteryService在它的processValues函数中和BSS交互,代码如下: **BatteryService.java** ~~~ private void processValues() { ...... mBatteryStats.setBatteryState(mBatteryStatus,mBatteryHealth, mPlugType, mBatteryLevel, mBatteryTemperature,mBatteryVoltage); } ~~~ BSS的工作由BSImpl来完成,所以直接setBatteryState函数的代码: **BatteryStatsImpl.java::setBatteryState** ~~~ public void setBatteryState(int status, inthealth, int plugType, int level, int temp, int volt) { synchronized(this) { boolean onBattery = plugType == BATTERY_PLUGGED_NONE;//判断是否为电池供电 intoldStatus = 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, intoldStatus, 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; longuptime = SystemClock.uptimeMillis() * 1000; longmSecRealtime = SystemClock.elapsedRealtime(); longrealtime = 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函数将更新对应信息,该函数比较简单,无须赘述。另外,addHistoryRecordLocked函数用于增加一条历史记录(由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, intevent) { getUidStatsLocked(uid).noteUserActivityLocked(event); } ~~~ 先是调用getUidStatsLocked以获取一个Uid对象,如果该Uid是首次出现的,则要在内部创建一个Uid对象。直接来了解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 { publicstatic final int OTHER_EVENT = 0; publicstatic final int BUTTON_EVENT = 1; publicstatic final int TOUCH_EVENT = 2; //目前只使用这三种事件 ...... } ~~~ [^platform]: 读者可阅读SDK文档中关于SystemClock类的说明。