×
广告

七、Android性能优化之电量优化(一)

96
香沙小熊
2017.09.03 14:36* 字数 3428

一、电量消耗理论与分析

写出耗电量低的应用的关键是要透彻理解它的理论以及全部过程。下面将对电量消耗的相关理论知识进行介绍。

1、电量消耗的概念

首先要知道,电量的消耗,主要是指硬件的电量消耗(废话),在电子世界,这种硬件消耗电量来执行任务的过程,叫做超时电流消耗。


硬件消耗电量

同情况下,相同时间内,消耗的电量是不同的。比如使用飞行模式待机,确实可以坚持10多天。但是我们一旦使用手机,比如使用蜂窝式无线数据交换(3G4G)、屏幕保持唤醒状态等,电量就会消耗得很快:

耗电分析

作为开发者,我们很想知道我的应用执行的哪些任务消耗的电量是最多的?这个问题确实会很棘手。

电量优化是方方面面的,比如说减少内存的开销,减少界面的过度绘制,本身就是一种电量优化。

2、电量消耗计算

电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情(所以很多设备都把这个监测电量的功能阉割掉了。)。
唯一可行的方案是使用第三方监测电量的设备,这样才能够获取到真实的电量消耗(因为第三方硬件监测的时候是用的自己的供电而不是用的手机的电量)。
耗电情况,例如:打开屏幕,所有要使用CPU/GPU工作的动作都会唤醒屏幕,都会消耗电量。这和应用程序唤醒设备还不一样。
比如使用叫醒闹钟(wake clock)、AlarmManager、JobSchedulerAPI。因此很难知道自己的应用程序的真实耗电情况。

3、设备待机与唤醒电量消耗分析

为什么要单独拿这个出来讲呢,就是因为,唤醒这个瞬间是非常耗电的,下面允许我慢慢介绍。

先来看看待机状态的电量消耗:


待机状态电量消耗

待机状态下,电量的消耗是非常少的,这是毋庸置疑的。

使用和唤醒屏幕后:

屏幕唤醒

可以看到,屏幕唤醒的一瞬间是非常耗电的,这里有一条电量使用高峰线。

下面来看看CPU唤醒的曲线(CPU唤醒,屏幕不一定会唤醒):

CPU唤醒时

同样的,CPU唤醒的时候也会有一条电量使用高峰线。

CPU唤醒之后:


唤醒之后唤醒之后

CPU唤醒之后,设备的耗电不会出现唤醒的时候的高峰线。

值得注意的是当工作完成后,设备会主动进行休眠,这非常重要,在不使用或者很少使用的情况下,长时间保持屏幕唤醒会迅速消耗电池的电量。

结论

设备唤醒的瞬间是有消耗高峰的,因此,当你的工作需要持续的时候,可以考虑保持唤醒状态。

4、无线蜂窝耗电分析

蜂窝式无线也是耗电量非常可怕的,甚至比WIFI更加耗电,因此这里单独拿出来进行分析。

Tips:不使用流量的时候,最好把数据关闭,这样又省电又省流量。

下面开始分析无线蜂窝耗电的过程:

无线蜂窝耗电过程

如上图所示:

1、当设备通过无线网发送数据的时候,为了使用硬件,这里会出现一个唤醒高峰。
2、接下来还有一个高数值,这是发送数据包消耗的电量。
3、然后接受数据包也会消耗大量电量,也看到一个峰值。
4、保持唤醒状态,耗电比较均衡,很少出现高峰点。

所以我们开启无线模式这个过程非常耗电,那么硬件这块为了防止频繁开启关闭耗电,采取了一个无奈的办法,会在一个小段时间内保持开启模式,防止短时间内还有数据包需要接收。这些数据非常有用,可是不是所有开发者都有这个第三方设备跟踪。但是使用Android L版本就可以利用到新的一系列的工具来优化应用程序的耗电。(这里显然不要考虑兼容性问题,我只是想测电量消耗问题,同一款APP在不同版本的Android上耗电情况应该不会有太大影响,虽然不同Android版本对电量的优化不同,但是我们的分析对象是我们自己的APP本身)

二、电量分析工具Battery Historian的环境搭建与使用

Battery Historian 是一个这样的的工具:可以在 Android 5.0 Lollipop(API 级别21)及更高版本的 Android 设备上检测与电池相关的信息和事件,而在此期间,该设备没有插上电源。它允许应用程序开发人员在时间轴上可视化系统和应用级别的事件,并使用平移和缩放功能,在设备最后一次完全充电之后,可以轻松地查看各种聚合统计信息,可以选择一个应用程序,检查所选择的应用程序对电池指标的影响。此外,它还允许对两个错误报告进行 A/B 比较,突出显示了关键电池相关指标的差异。

环境配置

(1)安装Go编程语言

点击下载go语言
配置GOROOT、GOPATH、PATH环境变量。

GOROOT
GOPATH
Path

检查是否安装成功:cmd 执行 “go version”

(2)安装 Python

下载:https://www.python.org/ 【注意仅支持 python 2.7,python3.0改变很大】

安装

配置环境变量

Paste_Image.png

检查是否安装成功:cmd 执行 “python –V”【注意是大写V】

(3)安装Git

下载:https://git-scm.com/

安装

检查是否安装成功:cmd 执行 “git version”

(4)下载 Battery Historian 源码并且运行
  1. cmd 执行“go get -d -u github.com/google/battery-historian/...”【注意最后有三个点】

下载成功后会在GOPATH(我自己建的工作空间文件目录)下生成src文件夹


  1. 进入到$GOPATH/src/github.com/google/battery-historian目录下方


  2. 运行Battery Historian

  cmd 执行“go run setup.go”【第一次执行要下载,时间会久一些,以后就快些】

ps:http://blog.csdn.net/feitian_666/article/details/52756522 提供了一个在没有下载成功情况下补充“go run setup.go”的方法:手动下载【closure-library】和【closure-compiler】和【flot-axislabels】,解压放到 GOROOT 目录下 third_party 文件夹下方的的 closure-compiler、closure-library 和flot-axislabels 文件夹,如果没有均手动创建。

  cmd 执行 go run cmd/battery-historian/battery-historian.go
  1. 检查/battery-historian是否运行
    登录网址 http://localhost:9999查看是否加载运行battery historian。
    G@~)M7WYR)R@T_(V9P%@5JV.png

使用

(1).初始化

battery-historian工具需要使用bugreport中的Battery History,因此需要如下的操作。
重启adb服务:

adb kill-server
adb start-server

这一步很重要,因为当我们开发时做电量记录时会打开很多可能造成冲突的东西。为了保险起见我们重启adb。
通过以下命令来打开电池数据的获取以及重置:

adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset

上面的操作很重要,因为可以过滤掉不需要的数据。然后断开数据线(防止数据线造成充放电数据干扰),运行自己的APP进行测试。

(2)导出手机的 Bugreport 文件

重新连接USB调试,通过下面的命令获取数据:

adb bugreport bugreport.zip(6.0以及以下的,使用adb bugreport > bugreport.txt导出)

你在哪个目录下执行adb bugreport bugreport.zip 就会在哪个目录下生成 bugreport.zip文件

(3)上传bugreport.zip文件至 http://localhost:9999

最后

注意:7.0以下的,需要使用旧版本的adb工具,不然没法采集,参考文章http://blog.csdn.net/mwq30123/article/details/53888449
注意:官方SDK文档导出文件方式为:adb shell dumpsys batterystats > batterystats.txt。使用python historian.py batterystats.txt > batterystats.html查看数据。这是battery-historian老版本的使用方式. 目前Battery Historian已更新2.0版本, 推荐使用bugreport方式导出数据分析, 可以看到更多信息。
注意:模拟器可能获取不到有用的电量数据,网页没有显示电量信息,如下图所示:

模拟器的bugreport
真实手机的bugreport

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

特别感谢:
小楠总
动脑学院Ricky
张小侨
ahking17

性能优化
Web note ad 1