Android应用耗电量分析与优化建议

Battery Historian工具使用

Battery Historian 一款由Google提供的Android系统电量分析工具,从手机中导出bugreport文件上传至页面,在网页中生成详细的图表数据来展示手机上各模块电量消耗过程,最后通过App数据的分析制定出相关的电量优化的方法。
https://github.com/google/battery-historian

运行环境

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

  1. 通过安装Docker环境来安装。(这种方式很简单,推荐使用)
  2. 安装GO环境、Python、Java环境。(安装比较繁琐感兴趣可以去GitHub上看看)

Docker环境

Docker是一种容器,一般用于云计算和大数据平台。提倡的一种思想就是:软件即服务。一句话就可以将别人发布的docker服务环境一次全部copy过来(注意是整个软件环境,相当于复制了一台一模一样的主机,连软件都不要安装了,全有了。)

官方下载地址:

安装Battery Historian命令

安装完Docker环境后通过以下命令来安装Battery Historian工具:

Mac:

# <port>替换成本机一个端口
$ docker -- run -p <port>:9999 gcr.io/android-battery-historian:2.1 --port 9999

在此需要个漫长的下载安装等待,完成后在浏览器输入http://localhost:9999地址可以正常访问到Battery Historian页面说明已经成功了。

提交文件页面

Windows:

没有尝试!可参考:https://github.com/google/battery-historian

App电量消耗分析

工具安装成功后我们需要从手机上导出数据进行分析

# 将bugreport的信息保存到.zip文件中
$ adb bugreport bugreport.zip

Android 6.0以下系统请使用以下导出命令

# 将bugreport的信息保存到txt文档中
$ adb bugreport bugreport.txt

打开浏览器输入http://localhost:9999成功访问页面,将导出的.zip或.txt文件进行提交生成分析图表:

在该图中左侧为各单元模块,X轴为对应的时间线,可通过点击左侧Batter level(电量百分比)、Coulomb charge(电池容量)、Temperature(手机温度)选项展示出相应的变化曲线

电量消耗明细

通过选择标签System Stats、History Stats来查看手机整体的运行状态详情,也可以通过指定App包名选择App Stats标签查看单个的运行状态详情

详细数据

导致电量消耗过快的原因

Android系统为了尽可能的增加设备的续航,会不断的关闭各种硬件模块来节省电量。当我们的App在设备处于休眠状态下想要执行一次网络请求的时候;首先需要唤醒设备,接着会发送数据请求,然后等待服务端返回的结果,最后再经过一段时间的等待才会慢慢进入休眠状态。

  • 尽可能地避免唤醒锁或批量操作以避免频繁的唤醒设备即使屏幕没有被点亮CPU也会保持运行状态,通过AlarmManager可唤醒设备,而项目中不限制的滥用,导致系统被频繁唤醒;Android 的 Timer 类可以用来计划需要循环执行的任务,Timer 的问题是它需要用 WakeLock 让 CPU 保持唤醒状态,再加上不恰当的使用WakeLock最终没有合理释放掉,使得系统长时间无法进入休眠,势必导致高耗电,可参考(android设备休眠[http://www.cnblogs.com/kobe8/p/3819305.html])
  • CPU和网络耗电方面,主要是减少I/O操作(包括数据库操作),大量的计算;减少网络网络请求次数和数据量,将不重要的操作放在用户充电或已经连接至WiFi的时候。分析和记录之类的操作不需要实时进行。
  • 传感器方面设备屏幕亮度、颜色背景等需要考虑,但除了阅读类等应用,一般是不太考虑屏幕消耗的。更多的是对GPS的使用注意,减少无用的GPS请求和及时关闭GPS搜索。

优化建议

开发过程中可以尝试通过调整任务优先级等策略来达到降低损耗的目的,使用JobScheduler是个不错的选择:

  • 可以推迟的非面向用户的任务(如定期数据库数据更新);
  • 当充电时才希望执行的工作(如备份数据);
  • 需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取配置数据);
  • 零散任务合并到一个批次去定期运行;
  • 当设备空闲时启动某些任务;
  • 只有当条件得到满足, 系统才会启动计划中的任务(充电、WIFI…);

官方建议优化的一些方法
https://developer.android.google.cn/training/monitoring-device-state/index.html

对低电耗模式和应用待机模式进行针对性优化
https://developer.android.google.cn/training/monitoring-device-state/doze-standby.html

Android 7.0新特性对电池管理进一步加强,一些新的变化可能多对我们现有的业务会造成影响需关注
https://developer.android.google.cn/about/versions/nougat/android-7.0-changes.html#perf

JobScheduler

自 Android 5.0 发布以来,JobScheduler 已成为执行后台工作的首选方式,其工作方式有利于用户。应用可以在安排作业的同时允许系统基于内存、电源和连接情况进行优化。JobSchedule的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:

  • 避免频繁的唤醒硬件模块,造成不必要的电量消耗。
  • 避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量;

JobScheduler的简单使用,首先自定义一个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();
    }

作者:哎你的益达
链接:http://www.jianshu.com/p/ebac88cdf9d6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 条评论
发表一条评论