一、电量消耗理论与分析
写出耗电量低的应用的关键是要透彻理解它的理论以及全部过程。下面将对电量消耗的相关理论知识进行介绍。
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本身)
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)耗电参数配置的代码片段:
0
...
3.5
73.24
75.48
...
2300
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作为参数,就能输出完整的电量统计信息。
网友评论