"
Free apps like Free Chess and Angry Birds spend under 25-35 percent of their energy on game play, but over 65-75 percent on user tracking, uploading user information and downloading ads.
"
android电量统计的原理可以参看这篇文章:
http://duanqz.github.io/2015-07-21-batterystats-part1
大致原理摘录如下:
电量记录
1. Android在进行电量统计时,并不是采用直接记录电流消耗量的方式,而是跟踪硬件模块在不同状态下的使用时间,收集一些可用信息,用来近似的计算出电池消耗量。
举一个例子,假定某个APK的使用了GPS,使用时间用 t 表示。GPS模块单位时间的耗电量用 w 表示,那么,这个APK使用GPS的耗电量就可以按照如下方式计算:
耗电量 = 单位时间耗电量(w) × 使用时间(t)
frameworks.jar里的frameworks/base/core/res/res/xml/power_profile.xml这个文件,记录着各个模块单位时间的耗电量, 由厂商定义。
以下是Nexus 5(hammerhead)耗电参数配置的代码片段:
<device name="Android">
<!-- All values are in mAh except as noted -->
<item name="none">0</item>
...
<item name="wifi.on">3.5</item>
<item name="wifi.active">73.24</item>
<item name="wifi.scan">75.48</item>
...
<item name="battery.capacity">2300</item>
</device>
2. Android框架层通过一个名为batterystats的系统服务,实现了电量统计的功能。
收集信息被组织起来,在内存中的数据结构是由BatteryStats类描述的。 为了能够从不同维度统计耗电量,这个数据结构设计得比较复杂,我们不在这里展开讨论,仅通过一个收集应用程序前台运行时间的例子,来说明信息收集过程。
记录应用程序中所有Activity从显示状态(Resumed)到消失状态(Paused)的时间,就能够统计应用程序的前台运行时间。Activity状态的切换是由AMS掌控的,因此AMS需要将Activity的状态信息通知给batterystats服务。
当Activity要切换到显示状态(Resumed)时,
会调用ActivityStackSupervisor.resumeTopActivitiesLocked()方法,
接下来会调用ActivityStack.resumeTopActivityInnerLocked()方法来完成Activity的状态切换,在完成状态切换后, 会调用
ActivityStackSupervisor.reportResumedActivityLocked()方法,从这里开始,就开始通报了:“本Activity已经进入了显示状态”。
在ActivityStackSupervisor.reportResumedActivityLocked()中得到BatteryStatsImpl对象, 并启动一个计时器(StopwatchTimer),记录下了启动时间.在Activity pause时, 再得到结束时间, 这样就得到了应用程序的acitiviy在前台的运行时间了。
除了应用程序前台运行时间,还有很多信息是batterystats服务关注的,包括WakeLock、Sendor、Wifi、Audio、Video等,这些信息的采集方式与上述过程雷同,都会经过以下步骤:
- 由相应的模块发起状态变更的通知
- BatteryStats使用定时器记录起止时间
电量信息的储存
Android支持历史电量信息的显示的,如果重新启动Android,那内存中的数据就丢失了, 所以需要把这些信息存储到磁盘上,磁盘上的 /data/system/batterystats.bin 文件中就是电量信息的序列化数据。
batterystats服务启动时,会从 batterystats.bin 这个文件中读取数据,来初始化BatteryStats这个数据结构。
电量计算
BatteryStatsHelper.refreshStats()承载了电量计算的全部过程,在需要显示电量统计信息的地方,就可以通过BatteryStatsHelper这个类,来获取统计完成的电量信息。 Setting.apk就引用了这个类。电量计算大体可以分为两块:
1. AppUsage:应用程序耗电量计算,是指每一个应用程序使用硬件模块所产生的耗电量
在BatteryStatsHelper.processAppUsage()这个方法中,实现了应用程序的电量计算(实际上统计的粒度是uid,不同的apk可以运行在同一个uid)。
2. MiscUsage:其他杂项耗电量计算
所谓杂项,其实就是用户比较关心的一大类,包括:待机的耗电量、亮屏的耗电量、通话的耗电量、Wifi的耗电量等,这个统计是系统层面的, 作为app的开发人员可以忽略掉这部分内容。
我们来总结一下应用程序的电量计算过程。Android通过一个名为BatteryStats.Uid的数据结构来维护一个应用程序的电量统计信息。 这个数据结构中,又包含很多子结构:
Proc:表示属于Uid的进程,一个Uid中可能会有多个进程,每个进程都有CPU占用时间
WakeLock:表示Uid持有的WakeLock锁的电量统计,一个Uid也可能会持有多个锁
Mobile Radio:表示Uid使用数据流量的电量统计,譬如3G流量、4G流量
Wifi:表示Uid使用wifi的电量统计
Sendor:表示Uid使用传感器的电量统计
Android提供的dumpsys命令用于查看系统服务的信息, 将batterystats作为参数,就能输出完整的电量统计信息。
adb shell dumpsys batterystats
App电量优化实践
1. 打点网络优化
现在用户操作的打点信息是实时上报, 每单击一次菜单项就有大概1k的数据上传. 可以把这打点上传的执行封装起来, 间隔一段时间统一上传, 或是在特殊时间点统一上传, 可以有效减少频繁地使用wifi和mobile radio模块。
TXD 发送数据 Transmit(tx) Data 的简写形式
RXD 接收数据 Receive(rx) Data 的简写形式
2. 尽量减少wakeLock的使用
例如现在的下载文件的逻辑, 应该使用带超时参数的acquire() API, 避免长时间使用wake lock.
DownloadThread.java
PowerManager.WakeLock wakeLock = null;
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
wakeLock.acquire();
3. 使用prepn profile 工具实时检测应用的耗电量
===DONE===