电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情,随着Androi****的性能要求越来越高,电量的优化,也显得格外重要,一个耗电的应用,用户肯定会毫不犹豫的进行卸载,所以本篇博客,我们一起来学习Androi****性能优化之电量优化。
耗电是如何产生的?
耗电情况,例如:打开屏幕,所有要使用CPU/GPU工作的动作都会唤醒屏幕,都会消耗电量。这和应用程序唤醒设备还不一样。
>>>点击进入业务全开限时查看领取>>>(1)唤醒屏幕
当用户电量屏幕的时候,意味着系统的各组件要开始进行工作,界面也需要开始执行渲染。
待机状态的电量消耗:
使用和唤醒屏幕后:
当设备从休眠状态中,被应用程序唤醒时,就会产生一条电量使用高峰线。
当工作完成后,设备会主动进行休眠,这非常重要,在不使用或者很少使用的情况下,长时间保持屏幕唤醒会迅速消耗电池的电量。
(2)蜂窝式无线
通过这张图,我们知道通过使用蜂窝无线时,会产生几个高峰:
当设备通过无线网发送数据的时候,为了使用硬件,这里会出现一个唤醒好点高峰。
接下来还有一个高数值,这是发送数据包消耗的电量,
然后接受数据包也会消耗大量电量 也看到一个峰值。
开启无线模式这个过程非常耗电,那么硬件这块为了防止频繁开启关闭耗电,采取了一个无奈的办法,会在一个小段时间内保持开启模式,防止短时间内还有数据包需要接收,也就是图中的Keep Awak****的那一段。
如何进行电量使用分析?
(1)电量数据收集
Androi**** 5.0及以上的设备, 允许我们通过adb命令dump出电量使用统计信息.
1.因为电量统计数据是持续的, 统计我们的待测试App之前先rese****下, 连上设备, 命令行执行:
$ adb shel**** dumpsy**** batter**** --rese****
Batter**** stat**** reset****
2.断开测试设备, 操作我们的待测试App.
3.重新连接设备, 使用adb命令导出相关统计数据:
// 此命令持续记录输出, 想要停止记录时按Ctrl+C退出.
$ adb bugrep**** > bugrep****
导出的统计数据存储到bugrep****, 此时我们可以借助如下工具来图形化展示电池的消耗情况.
注意, 官方SDK文档导出文件方式为:
adb shel**** dumpsy**** batter**** > batter****
使用Pytho**** histor**** batter**** > batter****查看数据
是batter****老版本的使用方式. 目前Batter**** Histor****已更新2.0版本, 推荐使用bugrep****方式导出数据分析, 可以看到更多信息.
(2)电量分析工具Batter**** Histor****
工具开源地址: http****://github****/googl****/batter****
根据gitbu****上面介绍,Batter**** Histor****工具的安装有两种方式:
1.通过安装Docke****环境来安装。(需要翻墙)
Docke****只支持Window****
Gitbu****上面是这样的命令及地址:
docke**** -- run -p <port>:.. gcr.i****/androi****:2.1 --por**** ..
2.通过编译gitbu****上面的源码来安装。
(1)GO环境安装:
1.下载
下载目录:http****://golang****/doc/instal****
http****://golang****/doc/instal****?downlo****=go1.7.****
2.安装GO
3.配置GOROO****和GOPAT****
a. GOROO****的作用是告诉Go 命令和其他相关工具,在哪里去找到安装在你系统上的Go包,所以这里配置的是GO的安装目录
b.GOPA****可以简单理解为是工程的目录,所以创建一个GO的工程路径
C.最后配置一下环境变量,把Go的bin目录放到path环境变量中
D. 检查Go是否安装成功,打开命令行输入Go versio****
(2)安装Git
2.按照步骤安装;
3.安装完成检查:命令行输入git versio****
(3)安装Pytho****
2.安装完成;
3.环境变量配置,添加Path的路径,是Pytho****的安装路径
4.输入命令行 pytho**** –V(注意是大写V)检查是否安装成功
(4)安装Java环境
1.点击下载:http****://www.or****/techne****/java/javas****/downlo****/jdk8-d****;
2.完成安装。
(5)下载Batter**** Histor****源码并且运行
输入命令行go get -d -u github****/googl****/batter****/...
下载到GOPAT****配置目录下
1.进入到$GOPAT****/src/github****/googl****/batter****目录下方
$ cd $GOPAT****/src/github****/googl****/batter****
2.运行Batter**** Histor****
1) go run setup.****
$ go run setup.****
等待数分钟或者10分钟左右,如果仍然没有下载成功,可以手动下载,如下操作
下载【closur****】和【closur****】和【flot-a****】,解压放到GOROO****目录下third_****文件夹下方的的closur****和closur****和flot-a****文件夹 ../batter****;如果没有均手动创建
2)go run cmd/batter****/batter****
$ go run cmd/batter****/batter**** [--por**** <defaul****:..>]
batter****使用
数据准备
batter****工具需要使用bugrep****中的Batter**** Histor****
1.先断开adb服务,然后开启adb服务
adb kill-s**** 这一步很重要,因为当我们开发时做电量记录时会打开很多可能造成冲突的东西。为了保险起见我们重启adb。
adb device****就会自动连接查找手机。当然也可以adb start-****
2.重置电池数据收集
数据,我们在开始的时候需要通过以下命令来打开电池数据的获取以及重置:
adb shel**** dumpsy**** batter**** --enab**** full-w****
adb shel**** dumpsy**** batter**** --rese****
上面的操作相当于初始化操作,如果不这么做会有一大堆的干扰的数据,看起来会比较痛苦。然后把数据线直接拔掉(防止数据线造成充放电数据干扰),现在做一些测试,手动或者跑一些自动化的case都行。经过一段时间后,我们重新连接手机确认adb连上了,运行下面这条命令来将bugrep****的信息保存到txt文档中,
adb bugrep**** > bugrep****
或者用下面的命令也可以:
adb shel**** dumpsy**** batter**** > batter****
adb shel**** dumpsy**** batter**** > com.ex**** > batter****
加上包名可以限制输出的数据是我们要检测的。
但是这个txt的数据可读性不强。接下来我们就要用到这个batter****工具了。
分析数据
各个参数的意义
首先我们在bugrep****找到Batter**** Histor****数据栏类似下面的信息:
DUMP OF SERVIC**** batter****:
Batter**** Histor**** (2% used, .. used of 256K****, 45 string**** usin**** ..):
0 (9) RESE****:TIME: ..-03-****
0 (2) 100 c09004**** statu****=discha**** healt****=good plug=none temp=200 volt=.. +runnin**** +wake_l**** +senso**** +scree**** data_c****=edge phone_****=grea**** bright****=mediu**** proc=u0a1****:"androi****"
0 (2) 100 c09004**** proc=u0a7:"com.an****"
0 (2) 100 c09004**** proc=u0a5****:"com.an****"
你在html中信息都能从bugrep****中找到相应的信息。
现在来分析各个指标代表的意义:
横坐标
上面的10,20代表的就是秒的意思,它是以一分钟为周期,到第60秒的时候变为0。横坐标就是一个时间范围,咱们的例子中统计的数据是以重置为起点,获取bugrep****内容时刻为终点。我们一共采集了多长时间的数据,图表下也有信息说明。(经其他人的反馈,这个坐标间隔是会随着时间的长度发生改变,所以要以你的实际情况为准。这个缩放级别可以调整的,如下图:)
纵坐标
进行电量优化
Trac**** Batter**** Statu**** & Batter**** Manage****
我们可以通过下面的代码来获取手机的当前充电状态:
// It is very easy to subscr**** to change**** to the batter**** stat****, but you can get the curren****
// stat**** by simpl**** passin**** null in as your receiv**** Nift****, isn't that?
Intent**** filte**** = new Intent****(Intent****);
Inten**** batter**** = this.r****(null, filte****);
int charge**** = batter****(Batter****, -1);
boolea**** acChar**** = (charge**** == Batter****);
if (acChar****) {
Log.****(LOG_TA****,“The phon**** is chargi****!”);
}
在上面的例子演示了如何立即获取到手机的充电状态,得到充电状态信息之后,我们可以有针对性的对部分代码做优化。比如我们可以判断只有当前手机为AC充电状态时 才去执行一些非常耗电的操作。
/** * This metho**** check**** for powe**** by compar**** the curren**** batter**** stat**** agains**** all possib**** * plugge**** in states**** In this case, a devic**** may be consid**** plugge**** in eithe**** by USB, AC, or * wirele**** charge**** (Wirele**** charg**** was introd**** in API Leve**** 17.) */
privat**** boolea**** checkF****() {
Intent**** filte**** = new Intent****(Intent****); Inten**** batter**** = this.r****(null, filte****);
// Ther**** are curren**** thre**** ways a devic**** can be plugge**** in. We shoul**** chec**** them all.
boolea**** u ... Cha**** = (charge**** == Batter****);
boolea**** acChar**** = (charge**** == Batter****);
boolea**** wirele**** = fals****;
if (Build.**** >= Build.****)
{ wirele**** = (charge**** == Batter****);
}
retur**** (u ... Cha**** || acChar**** || wirele****);
}
屏幕唤醒
有些时候我们需要改变Androi****系统默认的这种状态:比如玩游戏时我们需要保持屏幕常亮,比如一些下载操作不需要屏幕常亮但需要CPU一直运行直到任务完成。
最好的方式是在Activi****中使用FLAG_K**** 的Flag。
getWin****().addFl****(Window****);
另一个方式是在布局文件中使用androi****:keepSc****属性:
<Relati**** xmln****:androi****="http****://schema****/apk/res/androi****"
androi****:layout****="match_****"
androi****:layout****="match_****"
androi****:keepSc****="true">
...
</Relati****>
androi****:keepSc**** = ” true “的作用和FLAG_K****一样。使用代码的好处是你允许你在需要的地方关闭屏幕。
注意:一般不需要人为的去掉FLAG_K****的flag,window****会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用getWin****().clear****(Window****)
Wakelo**** and Batter**** Drai****
假设你的手机里面装了大量的社交类应用,即使手机处于待机状态,也会经常被这些应用唤醒用来检查同步新的数据信息。一个最简单的唤醒手机的 ... 是使用PowerM****的API来保持CPU工作并防止屏幕变暗关闭。这使得手机可以被唤醒,执行工作,然后回到睡眠状态。知道如何获取WakeLo****是简单的,可是及时释放WakeLo****也是非常重要的,不恰当的使用WakeLo****会导致严重错误。例如网络请求的数据返回时间不确定,导致本来只需要10s的事情一直等待了1个小时,这样会使得电量白白浪费了。这也是为何使用带超时参数的wakelo****() ... 是很关键的。
wake_l****锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如 ... 等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以 ... 里面是有大量使用到了wake_l****锁。
wake_l****:两种锁,一种计数锁;非计数锁(锁了很多次,只需要releas****一次就可以解除了)
唤醒锁可划分为并识别四种用户唤醒锁:
自 API 等级 17 开始,FULL_W**** 被弃用。 改为使用 FLAG_K****。
添加唤醒锁权限:
<uses-p**** androi****:name="androi****" />
直接使用唤醒锁:
PowerM**** powerM**** = (PowerM****) getSys****(POWER_****);
WakeLo**** wakeLo**** = powerM****(PowerM****,"MyWake****");
wakeLo****();
注意:在使用该类的时候,必须保证acquir****和releas****是成对出现的。不然当我们业务已经不需要时,当CPU处于唤醒状态,这个时候就会损耗多余的电量。
但是仅仅设置超时并不足够解决问题,例如设置多长的超时比较合适?什么时候进行重试等等?解决上面的问题,正确的方式可能是使用非精准定时器。通常情况下,我们会设定一个时间进行某个操作,但是动态修改这个时间也许会更好。例如,如果有另外一个程序需要比你设定的时间晚5分钟唤醒,最好能够等到那个时候,两个任务捆绑一起同时进行,这就是非精确定时器的核心工作原理。我们可以定制计划的任务,可是系统如果检测到一个更好的时间,它可以推迟你的任务,以节省电量消耗。
JobSch****
JobSch****的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。
自定义一个Servic****类,继承自JobSer****
publi**** clas**** JobSch**** extend**** JobSer****{
privat**** Strin**** TAG = JobSch****();
@Overri****
publi**** boolea**** onStar****(JobPar**** jobPar****) {
Log.****(TAG, "onStar****:" + jobPar****());
if(true) {
// JobSer****在主线程运行,如果我们这里需要处理比较耗时的业务逻辑需单独开启一条子线程来处理并返回true,
// 当给定的任务完成时通过调用jobFin****(JobPar**** param****, boolea**** needsR****)告知系统。
//假设开启一个线程去下载文件
new Downlo****().execu****(jobPar****);
retur**** true;
}else {
//如果只是在本 ... 内执行一些简单的逻辑话返回fals****就可以了
retur**** fals****;
}
}
/**
* 比如我们的服务设定的约束条件为在WIFI状态下运行,结果在任务运行的过程中WIFI断开了系统
* 就会通过回掉onStop****()来通知我们停止运行,正常的情况下不会回掉此 ...
*
* @para**** jobPar****
* @retur****
*/
@Overri****
publi**** boolea**** onStop****(JobPar**** jobPar****) {
Log.****(TAG, "onStop****:" + jobPar****());
//如果需要服务在设定的约定条件再次满足时再次执行服务请返回true,反之fals****
retur**** true;
}
clas**** Downlo**** extend**** AsyncT****<JobPar****, Objec****, Objec****> {
JobPar**** mJobPa****;
@Overri****
protec**** Objec**** doInBa****(JobPar**** jobPar****) {
mJobPa**** = jobPar****[0];
//比如说我们这里处理一个下载任务
//或是处理一些比较复杂的运算逻辑
//...
try {
Thread****(30*..);
} catc**** (Interr**** e) {
e.prin****();
}
retur**** null;
}
@Overri****
protec**** void onPost****(Objec**** o) {
super.****(o);
//如果在onStar****()中返回true的话,处理完成逻辑后一定要执行jobFin****()告知系统已完成,
//如果需要重新安排服务请true,反之fals****
jobFin****(mJobPa****, fals****);
}
}
}
记得在Manife****文件内配置Servic****
<servic**** androi****:name=".JobSc****" androi****:permis****="androi****"/>
创建工作计划
publi**** clas**** MainAc**** extend**** Activi****{
privat**** JobSch**** mJobSc****;

privat**** fina**** int JOB_I**** = 1;
@Overri****
protec**** void onCrea****(Bundl**** savedI****) {
super.****(savedI****);
setCon****(R.layo****);
mJobSc**** = (JobSch****) getSys****(Contex**** );
//通过JobInf****来设定触发服务的约束条件,最少设定一个条件
JobInf**** jobBui**** = new JobInf****(JOB_I****, new Compon****(this, JobSch****));
//循环触发,设置任务每三秒定期运行一次
jobBui****(..);
//单次定时触发,设置为三秒以后去触发。这是与setPer****(long time)不兼容的,
// 并且如果同时使用这两个函数将会导致抛出异常。
jobBui****(..);
//在约定的时间内设置的条件都没有被触发时三秒以后开始触发。类似于setMin****(long time),
// 这个函数是与 setPer****(long time) 互相排斥的,并且如果同时使用这两个函数,将会导致抛出异常。
jobBui****(..);
//在设备重新启动后设置的触发条件是否还有效
jobBui****(fals****);
// 只有在设备处于一种特定的网络状态时,它才触发。
// JobInf****,无论是否有网络均可触发,这个是默认值;
// JobInf****,有网络连接时就触发;
// JobInf****,非蜂窝网络中触发;
// JobInf****,非漫游网络时才可触发;
jobBui****(JobInf****);
//设置手机充电状态下触发
jobBui****(true);
//设置手机处于空闲状态时触发
jobBui****(true);
//得到JobInf****对象
JobInf**** jobInf**** = jobBui****();
//设置开始安排任务,它将返回一个状态码
//JobSch****,成功
//JobSch****,失败
if (mJobSc****(jobInf****) == JobSch****) {
//安排任务失败
}
//停止指定JobI****的工作服务
mJobSc****(JOB_I****);
//停止全部的工作服务
mJobSc****();
}
来自:CSDN****马云龙
http****://blog.c****/u01212****/articl****/detail****/746176****
程序员大咖整理发布,转载请联系作者获得授权
标签: 手机电池损耗检测软件
