电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情,随着Android开的性能要求越来越高,电量的优化,也显得格外重要,一个耗电的应用,用户肯定会毫不犹豫的进行卸载,所以本篇博客,我们一起来学习Android性能优化之电量优化。

耗电是如何产生的?

耗电情况,例如:打开屏幕,所有要使用CPU/GPU工作的动作都会唤醒屏幕,都会消耗电量。这和应用程序唤醒设备还不一样。

(1)唤醒屏幕

当用户电量屏幕的时候,意味着系统的各组件要开始进行工作,界面也需要开始执行渲染。

待机状态的电量消耗:

使用和唤醒屏幕后:

当设备从休眠状态中,被应用程序唤醒时,就会产生一条电量使用高峰线。

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

(2)蜂窝式无线

通过这张图,我们知道通过使用蜂窝无线时,会产生几个高峰:

1.当设备通过无线网发送数据的时候,为了使用硬件,这里会出现一个唤醒好点高峰。

2.接下来还有一个高数值,这是发送数据包消耗的电量,

3.然后接受数据包也会消耗大量电量 也看到一个峰值。

开启无线模式这个过程非常耗电,那么硬件这块为了防止频繁开启关闭耗电,采取了一个无奈的办法,会在一个小段时间内保持开启模式,防止短时间内还有数据包需要接收,也就是图中的Keep Awake的那一段。

如何进行电量使用分析?

(1)电量数据收集

Android 5.0及以上的设备, 允许我们通过adb命令dump出电量使用统计信息.

1.因为电量统计数据是持续的, 统计我们的待测试App之前先reset下, 连上设备, 命令行执行:

$ adb shell dumpsys batterystats --reset
Battery stats reset.

2.断开测试设备, 操作我们的待测试App.

3.重新连接设备, 使用adb命令导出相关统计数据:

// 此命令持续记录输出, 想要停止记录时按Ctrl+C退出.
$ adb bugreport > bugreport.txt

导出的统计数据存储到bugreport.txt, 此时我们可以借助如下工具来图形化展示电池的消耗情况.

注意, 官方SDK文档导出文件方式为:

adb shell dumpsys batterystats > batterystats.txt

使用python historian.py batterystats.txt > batterystats.html查看数据

是battery-historian老版本的使用方式. 目前Battery Historian已更新2.0版本, 推荐使用bugreport方式导出数据分析, 可以看到更多信息.

(2)电量分析工具Battery Historian

工具开源地址: https://github.com/google/battery-historian

根据gitbub上面介绍,Battery History工具的安装有两种方式:

1.通过安装Docker环境来安装。(需要FQ)

Docker只支持Windows10

Gitbub上面是这样的命令及地址:

docker -- run -p <port>:9999 gcr.io/android-battery-historian:2.1 --port 9999

2.通过编译gitbub上面的源码来安装。

(1)GO环境安装:

1.下载

下载目录:https://golang.org/doc/install

https://golang.org/doc/install?download=go1.7.3.windows-amd64.msi

2.安装GO

3.配置GOROOT和GOPATH

a. GOROOT的作用是告诉Go 命令和其他相关工具,在哪里去找到安装在你系统上的Go包,所以这里配置的是GO的安装目录

b.GOPATH可以简单理解为是工程的目录,所以创建一个GO的工程路径



C.最后配置一下环境变量,把Go的bin目录放到path环境变量中

D. 检查Go是否安装成功,打开命令行输入Go version

(2)安装Git

1.点击下载【下载】; (https://git-scm.com/

2.按照步骤安装;

3.安装完成检查:命令行输入git version

(3)安装Python

1.点击下载【下载】,注意仅支持python2.7 (https://www.python.org/

2.安装完成;

3.环境变量配置,添加Path的路径,是Python的安装路径

4.输入命令行 python –V(注意是大写V)检查是否安装成功

(4)安装Java环境

1.点击下载【下载】;

2.完成安装。

(5)下载Battery Historian源码并且运行

输入命令行go get -d -u github.com/google/battery-historian/...

**下载到GOPATH配置目录下

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

$ cd $GOPATH/src/github.com/google/battery-historian

2.运行Battery Historian

1) go run setup.go

$ go run setup.go

等待数分钟或者10分钟左右,如果仍然没有下载成功,可以手动下载,如下操作

**下载【closure-library】和【closure-compiler】和【flot-axislabels】,解压放到GOROOT目录下third_party文件夹下方的的closure-compiler和closure-library和flot-axislabels文件夹 ../battery-historian\third_party;如果没有均手动创建

2)go run cmd/battery-historian/battery-historian.go

$ go run cmd/battery-historian/battery-historian.go [--port <default:9999>]

battery-historian使用

数据准备

battery-historian工具需要使用bugreport中的Battery History

1.先断开adb服务,然后开启adb服务

adb kill-server 这一步很重要,因为当我们开发时做电量记录时会打开很多可能造成冲突的东西。为了保险起见我们重启adb。

adb devices就会自动连接查找手机。当然也可以adb start-server

2.重置电池数据收集

数据,我们在开始的时候需要通过以下命令来打开电池数据的获取以及重置:

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

上面的操作相当于初始化操作,如果不这么做会有一大堆的干扰的数据,看起来会比较痛苦。然后把数据线直接拔掉(防止数据线造成充放电数据干扰),现在做一些测试,手动或者跑一些自动化的case都行。经过一段时间后,我们重新连接手机确认adb连上了,运行下面这条命令来将bugreport的信息保存到txt文档中,

adb bugreport > bugreport.txt

或者用下面的命令也可以:

adb shell dumpsys batterystats > batterystats.txt
adb shell dumpsys batterystats > com.example.android.demo.app > batterystats.txt

加上包名可以限制输出的数据是我们要检测的。

但是这个txt的数据可读性不强。接下来我们就要用到这个battery-historian工具了。

分析数据

各个参数的意义

首先我们在bugreport.txt找到Battery History数据栏类似下面的信息:

-------------------------------------------------------------------------------

DUMP OF SERVICE batterystats:

Battery History (2% used, 5980 used of 256KB, 45 strings using 2592):

                    0 (9) RESET:TIME: 2015-03-05-15-21-56

                    0 (2) 100 c0900422 status=discharging health=good plug=none temp=200 volt=4167 +running +wake_lock +sensor +screen data_conn=edge phone_signal_strength=great brightness=medium proc=u0a15:"android.process.acore"

                    0 (2) 100 c0900422 proc=u0a7:"com.android.cellbroadcastreceiver"

                    0 (2) 100 c0900422 proc=u0a53:"com.android.gallery3d"

你在html中信息都能从bugreport.txt中找到相应的信息。

现在来分析各个指标代表的意义:

横坐标



上面的10,20代表的就是秒的意思,它是以一分钟为周期,到第60秒的时候变为0。横坐标就是一个时间范围,咱们的例子中统计的数据是以重置为起点,获取bugreport内容时刻为终点。我们一共采集了多长时间的数据,图表下也有信息说明。(经其他人的反馈,这个坐标间隔是会随着时间的长度发生改变,所以要以你的实际情况为准。这个缩放级别可以调整的,如下图:)

纵坐标

数据项 含义
battery_level 电量,可以看出电量的变化
plugged 充电状态,这一栏显示是否进行了充电,以及充电的时间范围
screen 屏幕是否点亮,这一点可以考虑到睡眠状态和点亮状态下电量的使用信息
top 该栏显示当前时刻哪个app处于最上层,就是当前手机运行的app,用来判断某个app对手机电量的影响,这样也能判断出该app的耗电量信息。该栏记录了应用在某一个时刻启动,以及运行的时间,这对我们比对不同应用对性能的影响有很大的帮助
wake_lock wake_lock 该属性是记录wake_lock模块的工作时间。是否有停止的时候等
running 界面的状态,主要判断是否处于idle的状态。用来判断无操作状态下电量的消耗
Job 后台的工作,比如服务service的运行
data_conn 数据连接方式的改变,上面的edge是说明采用的gprs的方式连接网络的。此数据可以看出手机是使用2g,3g,4g还是wifi进行数据交换的。这一栏可以看出不同的连接方式对电量使用的影响
status 电池状态信息,有充电,放电,未充电,已充满,未知等不同状态
phone_signal_strength 手机信号状态的改变。 这一栏记录手机信号的强弱变化图,依次来判断手机信号对电量的影响
health 电池健康状态的信息,这个信息一定程度上反映了这块电池使用了多长时间
plug 充电方式,usb或者插座,以及显示连接的时间
Sync 是否跟后台同步
phone_in_call 是否进行通话
gps gps是否开启

进行电量优化

Track Battery Status & Battery Manager

我们可以通过下面的代码来获取手机的当前充电状态:

// It is very easy to subscribe to changes to the battery state, but you can get the current
// state by simply passing null in as your receiver. Nifty, isn't that?
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, filter);
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
if (acCharge) {
 Log.v(LOG_TAG,“The phone is charging!”);
 }

在上面的例子演示了如何立即获取到手机的充电状态,得到充电状态信息之后,我们可以有针对性的对部分代码做优化。比如我们可以判断只有当前手机为AC充电状态时 才去执行一些非常耗电的操作。

/** * This method checks for power by comparing the current battery state against all possible * plugged in states. In this case, a device may be considered plugged in either by USB, AC, or * wireless charge. (Wireless charge was introduced in API Level 17.) */
 private boolean checkForPower() {
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = this.registerReceiver(null, filter);
 // There are currently three ways a device can be plugged in. We should check them all.
boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
boolean wirelessCharge = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
{ wirelessCharge = (chargePlug ==   BatteryManager.BATTERY_PLUGGED_WIRELESS);
}
return (usbCharge || acCharge || wirelessCharge);
 }

屏幕唤醒

有些时候我们需要改变Android系统默认的这种状态:比如玩游戏时我们需要保持屏幕常亮,比如一些下载操作不需要屏幕常亮但需要CPU一直运行直到任务完成。

最好的方式是在Activity中使用FLAG_KEEP_SCREEN_ON 的Flag。

 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

另一个方式是在布局文件中使用android:keepScreenOn属性:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true">
    ...
</RelativeLayout>

android:keepScreenOn = ” true “的作用和FLAG_KEEP_SCREEN_ON一样。使用代码的好处是你允许你在需要的地方关闭屏幕。

注意:一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

Wakelock and Battery Drain

假设你的手机里面装了大量的社交类应用,即使手机处于待机状态,也会经常被这些应用唤醒用来检查同步新的数据信息。一个最简单的唤醒手机的方法是使用PowerManager.WakeLock的API来保持CPU工作并防止屏幕变暗关闭。这使得手机可以被唤醒,执行工作,然后回到睡眠状态。知道如何获取WakeLock是简单的,可是及时释放WakeLock也是非常重要的,不恰当的使用WakeLock会导致严重错误。例如网络请求的数据返回时间不确定,导致本来只需要10s的事情一直等待了1个小时,这样会使得电量白白浪费了。这也是为何使用带超时参数的wakelock.acquice()方法是很关键的。

wake_lock锁主要是相对系统的休眠而言的,意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了wake_lock锁。

wake_lock:两种锁,一种计数锁;非计数锁(锁了很多次,只需要release一次就可以解除了)

唤醒锁可划分为并识别四种用户唤醒锁:

标记值 CPU 屏幕 键盘
PARTIAL_WAKE_LOCK 开启 关闭 关闭
标记值 开启 变暗 关闭
标记值 开启 变亮 关闭
标记值 开启 变亮 变亮

自 API 等级 17 开始,FULL_WAKE_LOCK 被弃用。 改为使用 FLAG_KEEP_SCREEN_ON。

添加唤醒锁权限:

<uses-permission android:name="android.permission.WAKE_LOCK" />

直接使用唤醒锁:

PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag");
wakeLock.acquire();

注意:在使用该类的时候,必须保证acquire和release是成对出现的。不然当我们业务已经不需要时,当CPU处于唤醒状态,这个时候就会损耗多余的电量。

但是仅仅设置超时并不足够解决问题,例如设置多长的超时比较合适?什么时候进行重试等等?解决上面的问题,正确的方式可能是使用非精准定时器。通常情况下,我们会设定一个时间进行某个操作,但是动态修改这个时间也许会更好。例如,如果有另外一个程序需要比你设定的时间晚5分钟唤醒,最好能够等到那个时候,两个任务捆绑一起同时进行,这就是非精确定时器的核心工作原理。我们可以定制计划的任务,可是系统如果检测到一个更好的时间,它可以推迟你的任务,以节省电量消耗。

JobScheduler

JobSchedule的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。

自定义一个Service类,继承自JobService

public class JobSchedulerService extends JobService{
    private String TAG = JobSchedulerService.class.getSimpleName();

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.d(TAG, "onStartJob:" + jobParameters.getJobId());

        if(true) {
            // JobService在主线程运行,如果我们这里需要处理比较耗时的业务逻辑需单独开启一条子线程来处理并返回true,
            // 当给定的任务完成时通过调用jobFinished(JobParameters params, boolean needsRescheduled)告知系统。

            //假设开启一个线程去下载文件
            new DownloadTask().execute(jobParameters);

            return true;

        }else {
            //如果只是在本方法内执行一些简单的逻辑话返回false就可以了
            return false;
        }
    }

    /**
     * 比如我们的服务设定的约束条件为在WIFI状态下运行,结果在任务运行的过程中WIFI断开了系统
     * 就会通过回掉onStopJob()来通知我们停止运行,正常的情况下不会回掉此方法
     *
     * @param jobParameters
     * @return
     */
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        Log.d(TAG, "onStopJob:" + jobParameters.getJobId());

        //如果需要服务在设定的约定条件再次满足时再次执行服务请返回true,反之false
        return true;
    }

    class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
        JobParameters mJobParameters;

        @Override
        protected Object doInBackground(JobParameters... jobParameterses) {
            mJobParameters = jobParameterses[0];

            //比如说我们这里处理一个下载任务
            //或是处理一些比较复杂的运算逻辑
            //...

            try {
                Thread.sleep(30*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);
            //如果在onStartJob()中返回true的话,处理完成逻辑后一定要执行jobFinished()告知系统已完成,
            //如果需要重新安排服务请true,反之false
            jobFinished(mJobParameters, false);
        }
    }
}

记得在Manifest文件内配置Service

<service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>

创建工作计划

public class MainActivity extends Activity{
    private JobScheduler mJobScheduler;
    private final int JOB_ID = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mai_layout);

        mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );

        //通过JobInfo.Builder来设定触发服务的约束条件,最少设定一个条件
        JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));

        //循环触发,设置任务每三秒定期运行一次
        jobBuilder.setPeriodic(3000);

        //单次定时触发,设置为三秒以后去触发。这是与setPeriodic(long time)不兼容的,
        // 并且如果同时使用这两个函数将会导致抛出异常。
        jobBuilder.setMinimumLatency(3000);

        //在约定的时间内设置的条件都没有被触发时三秒以后开始触发。类似于setMinimumLatency(long time),
        // 这个函数是与 setPeriodic(long time) 互相排斥的,并且如果同时使用这两个函数,将会导致抛出异常。
        jobBuilder.setOverrideDeadline(3000);

        //在设备重新启动后设置的触发条件是否还有效
        jobBuilder.setPersisted(false);

        // 只有在设备处于一种特定的网络状态时,它才触发。
        // JobInfo.NETWORK_TYPE_NONE,无论是否有网络均可触发,这个是默认值;
        // JobInfo.NETWORK_TYPE_ANY,有网络连接时就触发;
        // JobInfo.NETWORK_TYPE_UNMETERED,非蜂窝网络中触发;
        // JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫游网络时才可触发;
        jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);

        //设置手机充电状态下触发
        jobBuilder.setRequiresCharging(true);

        //设置手机处于空闲状态时触发
        jobBuilder.setRequiresDeviceIdle(true);

        //得到JobInfo对象
        JobInfo jobInfo = jobBuilder.build();

        //设置开始安排任务,它将返回一个状态码
        //JobScheduler.RESULT_SUCCESS,成功
        //JobScheduler.RESULT_FAILURE,失败
        if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
            //安排任务失败
        }

        //停止指定JobId的工作服务
        mJobScheduler.cancel(JOB_ID);
        //停止全部的工作服务
        mJobScheduler.cancelAll();
    }

Android性能优化系列之电量优化的更多相关文章

  1. [MySQL性能优化系列]LIMIT语句优化

    1. 背景 假设有如下SQL语句: SELECT * FROM table1 LIMIT offset, rows 这是一条典型的LIMIT语句,常见的使用场景是,某些查询返回的内容特别多,而客户端处 ...

  2. Mysql优化系列之数据类型优化

    本篇是优化系列的第一篇:数据类型 为了不产生赘述,尽量用简洁的语言来描述. 在选择数据类型之前,首先要知道几个原则: 更小的通常更好 尽量使用可以正确存储数据的最小数据类型.更小的数据类型意味着更快, ...

  3. PLSQL_性能优化系列04_Oracle Optimizer优化器

    2014-09-25 Created By BaoXinjian

  4. Android优化系列之ListView优化老生常谈

    本文内容:adapter,listview的优化,RecycleBi,google大会推荐优化, 实现ListView的过程,Adapter起到了至关重要的作用,不仅仅因为getview()方法.那么 ...

  5. Android 性能监控系列一(原理篇)

    欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 一. 前言 性能问题是导致 App 用户流失的罪魁祸首之一,如果用户在使用我们 App 的时候遇到诸如页面卡顿.响应速度慢.发热严重.流量电 ...

  6. ElasticSearch优化系列七:优化建议

    尽量运行在Sun/Oracle JDK1.7以上环境中,低版本的jdk容易出现莫名的bug,ES性能体现在在分布式计算中,一个节点是不足以测试出其性能,一个生产系统至少在三个节点以上. ES集群节点规 ...

  7. Android 性能优化的方面方面都在这儿

    又到周六了,鸿洋的不定期的周六放送又来了~~这次来谈谈性能优化吧.大家在工作中或多或少都会拿自家的应用和竞品app做比对,不可避免的需要做一些app性能优化的活.很多时候可能是策略上的调整,不过还是有 ...

  8. Android性能优化系列之App启动优化

    Android性能优化系列之布局优化 Android性能优化系列之内存优化 Android性能优化系列之apk瘦身 应用的启动速度缓慢是我们在开发过程中常常会遇到的问题,比方启动缓慢导致的黑屏.白屏问 ...

  9. Android性能优化系列之apk瘦身

    Android性能优化系列之布局优化 Android性能优化系列之内存优化 为什么APK要瘦身.APK越大,在下载安装过程中.他们耗费的流量会越多,安装等待时间也会越长:对于产品本身,意味着下载转化率 ...

随机推荐

  1. 20145310 GDB调试汇编堆栈分析

    GDB调试汇编堆栈分析 由于老师说要逐条分析汇编代码,所以我学习卢肖明同学的方法,重新写了一篇博客. 代码: #include<stdio.h> short addend1 = 1; st ...

  2. 20145329《Java程序设计》第十周学习总结

    教材学习内容总结 网络编程 • 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据 1.网络概述 • 网络编程的实质:两个( ...

  3. 20145329 《Java程序设计》实验二总结

    实验指导教师:娄嘉鹏老师 实验日期:2016.4.12 实验时间:15:30~17:30 实验序号:实验二 实验名称:Java面向对象程序设计 实验目的与要求: 1.初步掌握单元测试和TDD 2.理解 ...

  4. SpringMVC HttpMessageConverter 匹配规则

    以下内容,如有问题,烦请指出,谢谢! SpringMVC启动时会自动配置一些HttpMessageConverter,接收到http请求时,从这些Converters中选择一个符合条件的来进行Http ...

  5. Spring @Scheduler使用cron时的执行问题

    主要想弄清使用Spring @Scheduler cron表达式时的两个问题: 同一定时任务,第二次触发时间到了,第一次还没有执行完成时会执行吗? 不同的定时任务,相互之间是否有影响? 结论写在前面: ...

  6. Axis.Labels.CustomSize

    tChart1.Axes.Bottom.Labels.CustomSize = ; //Changes spacing occupied by the axis labels between the ...

  7. [Pytorch]Pytorch 保存模型与加载模型(转)

    转自:知乎 目录: 保存模型与加载模型 冻结一部分参数,训练另一部分参数 采用不同的学习率进行训练 1.保存模型与加载 简单的保存与加载方法: # 保存整个网络 torch.save(net, PAT ...

  8. LA 5135 井下矿工(点—双连通分量模板题)

    https://vjudge.net/problem/UVALive-5135 题意:在一个无向图上选择尽量少的点涂黑,使得任意删除一个点后,每个连通分量至少有一个黑点. 思路: 首先dfs遍历求出割 ...

  9. 经典C#面试题

    1.在下面的代码中,如何引用命名空间fabulous中的great? namespace fabulous{// code in fabulous namespace}namespace super{ ...

  10. RabbitMQ 之 订阅模式 Publish/Subscribe

    模型图 我们之前学习的都是一个消息只能被一个消费者消费,那么如果我想发一个消息 能被多个消费者消费,这时候怎么办? 这时候我们就得用到了消息中的发布订阅模型 在前面的教程中,我们创建了一个工作队列,都 ...