电量优化 - 电量的统计原理与监控

最近有很多同学来催了,到底还讲不讲?由于活水转岗去微信,面试七轮持续了两个月时间,绿色通道答辩持续了差不多半个月的时间。我发现很多时候都到了身不由己,不是不想去做而是真的没有时间了。我们想去做点事,却发现身体和时间真的不够用。

App “耗电综合征”

当我们说一个 App 耗电的时候我们在说什么?

我们可能是指 App 吃 CPU 导致系统掉电快,也可能是在说系统告警 App 后台扫描频繁消耗电量,还可能是在说使用 App 时手机发烫严重…… 是的,相对于 Crash、ANR 等常见的 APM 指标,Android App 电量优化更像是一个综合性的问题。

一方面,造成 App 耗电的原因是多种多样的,比如 CPU/GPU Load、屏幕、传感器以及其他硬件开销等,每个分类的排查思路是大相径庭的,再加上 AOSP 没有 “官方” 的耗电异常检测框架,各个 OEM 厂商自家系统对 App 耗电的监控方案又各不相同(且没有充分的公开文档),所以检测方案需要结合具体 App 项目实际和用户反馈状况,针对具体的耗电类型做出考量和取舍。另一方面,耗电问题也经常是比较 “主观” 的,比如用户感觉 App 新版本掉电比较快了,或者在户外气温比较高的环境使用 App 时感觉设备发烫了,又或只是单纯的因为使用时间变长了导致系统耗电排行靠前了等等,这些通常都是一些比较微妙的主观感受,难以量化问题。

因此,如何检测各种类型的耗电异常,以及如何提炼耗电问题的规则(划红线)是优化电量指标的关键所在。微信 Android 项目在与 App 耗电异常这项 “疑难杂症” 日常斗智斗勇的过程中,产出了一些比较实用的工具和优化思路。本文针对 Anroid App 的耗电问题,本文主讲 “App 电量统计原理”。后面的我们会陆续对“耗电异常监控方案”以及相关的 “优化案例” 两部分进行解析和分享。

App 电量统计原理

电量计算公式

了解 App 电量统计原理之前,有必要先复习一下电量计算公式:

电量 = 功率 × 时间

其中需要注意一点的是, 功率 = 电压 × 电流。而在数码产品中,元器件一般对电流比较敏感,而电压基本是恒定的,所以我们直接使用电流来代替功率,这也是我们经常说 “毫安时”(mAh)而不说 “千瓦时 / 度”(kWh)的原因。

Android 硬件模块的电量统计方式

了解计算公式之后,App 的电量统计思路就比较清晰了:

App 电量 = SUM (模块功率 × 模块时间)

其中模块主要是指 Android 设备的各种硬件模块,主要可以分为以下三类。


硬件模块分类

第一类,像 Camera/FlashLight/MediaPlayer/ 一般传感器等之类的模块,其工作功率基本和额定功率保持一致,所以模块电量的计算只需要统计模块的使用时长再乘以额定功率即可。

第二类,像 Wifi/Mobile/BlueTooth 这类数据模块,其工作功率可以分为几个档位。比如,当手机的 Wifi 信号比较弱的时候,Wifi 模块就必须工作在比较高的功率档位以维持数据链路。所以这类模块的电量计算有点类似于我们日常的电费计算,需要 “阶梯计费”。

第三类,也是最复杂的模块,CPU 模块除了每一个 CPU Core 需要像数据模块那样阶梯计算电量之外,CPU 的每一个集群(Cluster,一般一个集群包含一个或多个规格相同的 Core)也有额外的耗电,此外整个 CPU 处理器芯片也有功耗。简单计算的话,CPU 电量 = SUM (各核心功耗) + 各集群(Cluster)功耗 + 芯片功耗 。如果往复杂方向考虑的话,CPU 功耗还要考虑超频以及逻辑运行的信息熵损耗等电量损耗(这方面有兴趣的话可以自行拓展查证,Android 系统 CPU 的电量统计只计算到芯片功耗这一层)。屏幕模块的电量计算就更麻烦了,很难把屏幕功耗合理地分配给各个 App, 因此 Android 系统只是简单地计算 App 屏幕锁(WakeLock)的持有时长,按固定系数增加 App CPU 的统计时长,粗略地把屏幕功耗算进 CPU 里面。

最后,需要特别注意的是,以上提到的各种功率和时间在 Android 系统上的统计都是估算的,可想而知最终计算出来的电量数值可能与实际值相差巨大,Facebook 的工程师对此也有所吐槽:Mistrusting OS Level Battery Levels,这点大家心里要有一点概念。

Android 系统电量统计服务

Android 系统的电量统计工作,是由一个叫 BatteryStatsService 的系统服务完成的。先了解一下其中四个比较关键的角色:

  • 功率:power_profile.xml,Android 系统使用此文件来描述设备各个硬件模块的额定功率,包括上面提到的多档位功率和 CPU 电量算需要到的各种参数值。
  • 时长:StopWatch & SamplingCounter,其中 StopWatch ⏱ 是用来计算 App 各种硬件模块的使用时长,而 SamplingCounter 则是用来采样统计 App 在不同 CPU Core 和不同 CpuFreq 下的工作时长。
  • 计算:PowerCalculators,每个硬件模块都有一个相应命名的 PowerCalculator 实现,主要是用来完成具体的电量统计算法。
  • 存储:batterystats.bin,电量统计服务相关数据的持久化文件。
工作流程

BatteryStatsService 的工作流程大致可以分为两个部分:时长统计 & 功耗计算。


BatteryStatsService 时长统计流程

BatteryStatsService 框架的核心是 ta 持有的一个叫 BatteryStats 的类,BatteryStats 又持有一个 Uid [] 数组,每一个 Uid 实例实际上对应一个 App,当我们安装或者卸载 App 的时候,BatteryStats 就会更新相应的 Uid 元素以保持最新的映射关系。同时 BatteryStats 持有一系列的 StopWatch 和 SamplingCounter,当 App 开始使用某些硬件模块的功能时,BatteryStats 就会调用相应 Uid 的 StopWatch 或 SamplingCounter 来统计其硬件使用时长。

这里以 Wifi 模块来举例:当 App 通过 WifiManager 系统服务调用 Wifi 模块开始扫描的时候,实际上会通过 WifiManager#startScan () --> WifiScanningServiceImp --> BatteryStatsService#noteWifiScanStartedFromSource () --> BatteryStats#noteWifiScanStartedLocked (uid) 等一连串的调用,通知 BatteryStats 开启 App 相应 Uid 的 Wifi 模块的 StopWatch 开始计时。当 App 通过 WifiManager 停止 Wifi 扫描的时候又会通过类似的流程调用 BatteryStats#noteWifiScanStoppedLocked (uid) 结束 StopWatch 的计时,这样一来就通过 StopWatch 完成 App 对 Wifi 模块使用时长的统计。

BatteryStatsService 功耗计算流程

具体电量计算方面,BatteryStats 是通过 ta 依赖的一个 BatteryStatsHelper 的辅助类来完成的。BatteryStatsHelper 通过组合使用 Uid 里的时长数据、PoweProfile 里的功率数据(power_profile.xml 的解析实例)以及具体各个模块的 PowerCalculator 算法,计算出每一个 App 的综合电量消耗,并把计算结果保存在 BatterySipper [] 数组里(按计算值从大到小排序)。

还是以 Wifi 模块来举例:当需要计算 App 电量消耗的时候,BatteryStats 会通过调用 BtteryStatsHelper#refreshStats () --> #processAppUsage () 来刷新 BatterySipper [] 数组以计算最新的 App 电量消耗数据。而其中 Wifi 模块单独的电量统计就是在 processAppUsage 方法中通过 WifiPowerCalculator 来完成的:Wifi 模块电量 = PowerProfile 预置的 Idle 功率 × Uid 统计的 Wifi Idle 时间 + 上行功率 × 上行时间 + 下行功率 × 下行时间。

public class WifiPowerCalculator extends PowerCalculator {

    @Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
        ...
        app.wifiPowerMah =
                ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa))
                / (1000*60*60);
    }
}

应用场景

作为补充,这里罗列几个 BatteryStatsService 系统服务的应用场景来说明其工作方式。

Android 系统 App 耗电排行

通过以上分析,我们其实已经知道 Android 系统 App 耗电排行是通过读取 BatteryStatsHelper 里的 BatterySipper [] 数据来实现排行的。一般情况下,BatteryStats 的统计口径是 STATS_SINCE_CHARGED, 也就距离上次设备充满电到现在的状态。不过个别 OEM 系统上这里的统计细节有所不同,有的 Android 设备系统可以显示最近数天甚至一周以上的 App 的电量统计数据,具体实现细节不得而知,姑且推断是根据 BatteryStatsHelper 自行定制的服务。

adb dumpsys batterystats & adb bugreport

或许你已经知道怎么通过 adb dumpsys batterystats 或者 adb bugreport Dump 出系统的电量统计数据,以及如何配合 Battery Historian 工具来分析这些数据,实际上这些 adb 命令都是通过 BatteryStatsService 查询 BatteryStats 里持有的 Uid [] 来获得相应的电量统计数据,具体实现可以参考 com.android.server.am.BatteryStatsService#dump

CPU Load/Usage

“CPU Load xx% yy% zz%” 之类的数据相信大家都或多或少见过,ANR 的 traces.txt、以上的 batterystats 和 bugreport Dump 出来的数据,以及 adb top 命令里都会显示类似的 CPU 负载数据,实际上这个数据也是通过 CPU 模块的统计时长来计算:CPU Load = SUM (App CPU Core 时长时间) / CPU 工作时间。需要注意的是 App CPU 时长是按 CPU Core 为单位分开计算的,所以计算结果完全可能超过 100%,比如一个 8 核心的 CPU 计算结果的理论上限是 800%。

后面的我们会陆续对“耗电异常监控方案”以及相关的 “优化案例” 两部分进行解析和分享。最后想送我们一句话,是《大学》中讲的:物有本末事有终始,知所先后则近道矣

视频链接:https://pan.baidu.com/s/1GJJCQfL5O68Q8Lt8NDvaoQ
视频密码:3bqh

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

推荐阅读更多精彩内容