Android BatteryStats服务功耗统计流程详解

简介

BatteryStatsService和BatterStatsImpl是系统中用于估算电流消耗的关键对象,能够估算并存储软件功耗和硬件功耗。其中主要流程分为事件回调时记录耗电信息、触发读取时计算并统计耗电信息两大流程。本文介绍耗电信息的读取和统计过程。

BatteryStatsService和BatteryStatsImpl

BatteryStatsService提供App和硬件的功耗,是通过BatteryStatsImpl的接口getUidStats()方法取得的,在分析该方法的流程前先简单介绍一些关键成员。

BatteryStatsHelper关键成员:

  1. BatterySipper:按照Uid和drain type来过滤存储的功耗记录,从BatteryStatsImpl中提取。可以理解为从系统记录的全部功耗信息中,按照Uid和Drain type提取出来。其中Drain type是耗电类型,如屏幕耗电、Wi-Fi耗电、App耗电等
  2. mUsageList:以BatterySipper的形式记录各Uid的功耗
  3. mWifiSippers、mBluetoothSippers:Wi-Fi、蓝牙专用的功耗记录(也是BatterySipper的形式)
  4. mUserSippers:mUsageList只存储refresh方法传入的参数内的uid的耗电。当uid不在refresh方法指定的uid范围内时,不存储到mUsageList而是存储到这里
  5. mMobilemsppList:各App的milliseconds per packets:各App的每个数据包的平均时间
  6. mRawRealtimeUs mRawUptimeUs : refresh()调进来的传入的elapsed time和uptime
  7. mBatteryUptimeUs mBatteryRealtimeUs mRaw*timeUs下当前battery*time
  8. mTypeBatteryUptimeUs mTypeBatteryRealtimeUs:当前版本的Android只支持上一次充满电后的记录,因此等同于mBattery*timeUs
  9. mBatteryTimeRemainingUs 根据上一次充满电后的掉电量/掉电时间估算出来还需要多久用光电池
  10. mChargeTimeRemainingUs 根据充电量/充电时间估算出来的还需要多久充满电池
  11. mMinDrainedPower mMaxDraingedPower 上次充电后的最小/最大的一次掉电量范围
  12. BatteryStatsImpl.TimeBase:拔掉电源、熄屏等各有一个TimeBase,封装了成员用于计算这些事件后的事件

BatteryStatsHelper.refreshStats:获取功耗记录主入口

refreshStats()方法用于获取系统记录的耗电情况,流程如下。

  1. 功耗记录是借助Binder从BatteryStatsService获取的:BatteryStatsService.getStats()->getActiveStatistics()获取BatteryStatsImpl(binder parcel传递)
  2. 调用processAppusage计算各Uid App的电耗
  3. 调用processMiscUsage()计算硬件功耗
  4. 计算mUsageList内记录的最高功耗到mMaxRealPower、mMaxPower
  5. 计算mUsageList内记录的各Uid的总功耗到mComputedPower、mTotalPower
  6. 根据mMinDrainedPower和mMaxDrainedPower,确定UNAACCOUNTED、OVERCOUNTED的电耗量,更新mTotalPower和mMaxPower,将over count和un count的电耗量加入到mUsageList中
  7. 进行电耗摊派。部分电耗属于系统需要隐藏的(shouldHideSipper),或者是要分摊给Uid们的。其中,要分摊的种类是需要隐藏的种类的子集,也就是说部分需要隐藏的电耗是不需要摊派的(或者之前已经摊派了,不用重复分摊)。其中屏幕功耗的分摊方式是,根据几个sipper的前台时间来算比例,根据前台时间的多少来承担对应比例的屏幕功耗,将屏幕功耗按前台时间分出去;承担了屏幕功耗的sipper会将自己分担的屏幕功耗记录到proportionalSmearMah,并调用sumPower()更新自己的总功耗
  8. 至此,详细功耗信息就记录到了mUsageList中了,通过BatteryStatsHelper.getUsageList()即可取得,遍历该集合的BatterySipper即可取得各uid的耗电记录

processAppUsage():读取并按uid来计算App耗电

该方法根据uid和drain type来统计App的耗电情况。

  1. BatteryStatsImpl.getUsageStats()取得所有Uid,遍历processAppUsage()传入指定的需要统计的uid
  2. 创建BatterySipper;BatterySipper用于提取Uid里面指定消耗类型;因为Uid里面记录了App、硬件等不同类型的消耗,这里只统计App自己的消耗(排除硬件);Sipper的作用其实就是从Uid里面提取Type对应的数据并存储下来
  3. 通过几大类的PowerCalculator.calculateApp()将Uid内的信息提取出来,进行计算,将数据存储进sipper
  4. 最后调用sipper.sumPower()将几大类的功耗全部加起来得到总功耗
  5. 接着当这个Uid sumpower > 0时,就根据uid来添加到对应的sipper集合中。
    1. Wifi、蓝牙对应的Uid会独立存储在它们的特定的sipper集合
    2. 当该Uid不是processAppUsage()参数指定的要读取的uid时、且该UID是App的Uid(≥FISTST_APPLICATION_UID)时,存储到mUserSippers中
    3. 都不是时,存储到mUsageList这个sipper集合中
    4. UID == 0(ROOT)时,处理为uid为0的实体的Wakelock的功耗,相当于将uid为0的drain type为App的情况算作系统的唤醒功耗(设备唤醒的时间可能比屏幕亮起时间和应用程序唤醒锁定时间要长。如果可能,将此余数分配给操作系统);记录到uid为0的drain type为app的记录时,表明应用可能都已经停止耗电,但系统还有服务或逻辑在运行,或进入睡眠前的一段间隔,这些内容会算作系统的功耗,并在Uid记录中体现

PowerCalculator:9大耗电类别计算App耗电

PowerCalculator是abstract类,用于App的9大类别的耗电计算,分别为:CPU、WakeLock、移动网络、Wifi、蓝牙、传感器、摄像头、闪光灯、媒体:9大类。

主要方法:

  1. reset():清除掉本计算器内的所有状态和数据
  2. calculateApp(BatterySipper,BatteryStats.Uid,rawRealtimeUs,rawUptimeUs,statsType):计算App在这个类别下消耗的电量;其中sipper用于从Uid提取对应的信息,可以理解成一个提供过滤能力的吸管,用于存储返回给调用者的信息,包括类型、uid、各项细节功耗值;real/upTime:系统当前的real time和uptime;realtime就是linux系统记录的系统启动以来经过的时间,uptime是realtime去掉休眠的时间;statsType是如何统计,Android Q以后只有一种方式:从上一次充满电后开始的所有数据
  3. calculateRemaining(),参数与calApp()完全一致;作用是当Uid内部记录的电量消耗不能全部算给App时,剩余的电量消耗

class Uid

BatteryStats有个内部类Uid,它封装了各uid(应用、系统等)以及它们的运行时长、耗电信息、网络收发情况、CPU时间等等一切和功耗有关的信息。各个PowerCalculator基于这个对象内存储的数据来进行计算。

CpuPowerCalculator:计算App消耗在CPU上的功耗

BatterySipper与CPU相关的主要是:cpuTimeMs、cpuFgTimeMs、cpuPowerMah、packageWithHighestDrain,CpuPowerCalculator主要就是读取并计算这些值,存储到上述sipper的这些成员中,最后存储到mUsageList或mUserSippers。

calculateApp:

  1. 调用Uid的方法取得Uid记录的uid对应的CPU时间(分不同的cluster(簇,不同的簇,因大小核、架构而功耗不一样)、不同的CPU速度),将这些数据与power_profile.xml记录的硬件功耗相乘后相加,得到Uid在CPU上消耗的时间和电量。其中CPU电量的计算公式是:总电耗=总CPU时间*CPU激活状态下的功耗 + 各频率运行时长*各频率对应额外运行功耗
  2. 一个uid可以有多个进程,Uid.Proc对象表示一个进程的唤醒时长等统计信息,同一个uid的进程的信息对应的若干Proc存储在Uid内的ArrayMap中;取得Uid内所有Proc的foreground time求和得到uid对应的总的cpuFdTimeMs,存入sipper;并总时间(user time、system time、fg time)最大的Proc存储到sipper的packageWithHighestDrain用于表示该Uid下CPU消耗最大的Proc

calculateRemaining:空实现

WakeLockPowerCalculator:计算App持有唤醒锁的时间和功耗

calculateApp:

  1. WakeLock专门存储于一个集合,用Uid.Wakelock表示,里面记录的Wake time取出后累加记录到sipper的wakeLockTimeMs
  2. 电量计算公式为:wakeLockTimeMs * CPU idle电耗

calculateRemaining:

  1. calculateApp只统计了Uid自己的wakelock时长对应的电耗,当Uid内记录的电池时间大于sum(WakeLockTime + ScreenOnTime)时,大于的部分算作app wake,同样算入wakeLockTimeMs、及对应电耗

MobileRationPowerCalculator:计算App进行的移动网络射频功耗

calculateApp:根据App收发的数据包数量结合power_profile计算电耗

calculateRemaining:根据信号强度、扫描次数结合power_profile计算电耗

WifiPowerCalculator:计算App进行的Wifi收发功耗

calculateApp:根据收发时长、空闲时长结合power_profile计算电耗;获取Uid存储的收发字节数、数据包数量存储到sipper

MediaPowerCalculator:计算App音视频功耗

calculateApp:Audio、Video时长*对应功耗得出电耗

processMiscUsage():统计硬件功耗

该方法用于统计硬件的功耗,与App功耗一致,都是根据Uid、Drain type借助sipper来计算的。

addUserUsage

前面统计App用量时提到,如果不是App的uid或没有在refreshStats方法传入的参数中没包含这个uid,那么不会存到mUsageList里面,而是在mUserSippers内;这个方法将mUserSippers内的DrainType为User的sipper提取出来放入mUsageList内。

注意前面processAppUsage处理的DrainType是APP,这里是USER。

addPhoneUsage

添加"Phone On"类型的功耗;这里统计的是Ratio功能active的功耗,可以认为是不开飞行模式时、上了基站的通话网络情况下功耗;形象理解为:设备正常放着,不进行基带射频、扫描、上网、通话时的功耗

addScreenUsage

添加屏幕的功耗,DrainType.Screen加入到mUsageList

addAmbientDisplayUsage

添加屏幕微光情况下的功耗(Display Doze),DrainType.Ambient加入到mUsageList

addWiFiUsage

统计除分摊给App以外的剩余Wifi消耗,从mWifiSippers中提取,以DrainType.WIFI的新的sipper加入到mUsageList。

addBluetoothUsage

蓝牙消耗的电量不会像WiFi一样会分摊给各个使用的App,它没有进行摊派全部统计在这里。该方法统计蓝牙的整体消耗;同WIFI,DrainType.BLUETOOTH加入mUsageList

addMemoryUsage

类似蓝牙的情况

addIdleUsage

统计设备休眠、空闲时的AP(App Processor,即CPU)的功耗——也就是说,不包含BP(基带)的功耗,由于外设已经关闭或挂起、系统已经休眠,可以认为是深度待机状态下的整机功耗,可以说是开机状态下设备的最低功耗了

DrainType.IDLE sipper加入到mUsageList

addRadioUsage

射频也和Wifi一样进行了摊派,同样是将没有摊派出去的独立出来到DrainType.CELL。其中,射频会额外关注信号强度,信号强度对射频功率、模组功耗影响大,进而影响对电耗的估算

总结

BatteryStatsService从系统各服务、驱动节点、Linux文件系统节点中取得的数据,在需要时,通过BatteryStatsImpl.refresh()取出、过滤、计算。其中,按照App电耗、硬件电耗两大部分来分别计算。耗电信息从Uid中取得,经过PowerCalculator处理后放入BatterySipper。一些类别的硬件电耗会分摊到使用它的几个App中,最终会以uid和Drain Type的形式来存储到多个BatterySipper中,交给调用者完成整个功耗信息获取过程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容