电量优化你不得不知道的秘密

578 阅读13分钟

电量优化你不得不知道的秘密

什么?我写个“破”应用还得管电量?我哪儿知道自己写的应用费不费电啊。。。

但是当我们自己使用手机的时候:这个破应用怎么那么费电?长按,上滑,走起~~~

看到没,电量优化你能不重视么?好吧,我们的应用体量小,还真没怎么优化过😄。其实说起“电量优化”这个词,总有一种神秘感,不知从何入手。那么这里会告诉你,什么是电量优化,优化又是优化什么,怎么去做优化~

基础知识

Android电量优化
Android电量优化

概述

其实对于一个移动设备来说,有两个耗电大户:CPU和显示屏。为了降低这两个元件的耗电量,在Android系统中,也会通过电源管理来实现降低电量消耗的功能。

对于CPU,大多都会设计两种工作频率,大部分时间内CPU都工作在降低频率下,只有进行密集计算时,才会切换到高频率。这也就是为什么我们看视频,看直播,玩游戏,上网的时候,会明显感觉手机玩不了多久就得充电的原因。

对于显示屏。系统则会尽量减少亮屏的时间,当用户长时间不操作的时候,就会息屏等等。

电源管理架构

Android系统有一套自己的架构来来实现来实现对于电源的管理。其主要分为四个层次:

应用接口层:PowerManger开放给应用一系列的接口,应用可以通过PM的接口来申请wakelock,唤醒系统,使系统进入睡眠等操作。

Framework层:应用调用PowerManager开放的接口,来对系统进行一些列的操作是在PowerManagerService中完成的。PMS中处理和电量相关的计算。属于电源管理的决策层。协调与其他模块的交互,比如亮屏、暗屏、系统休眠、唤醒等等。

HAL层:power.c文件,通过上层传递下来的参数,向/sys/power/wake_lock或者/sys/power/wake_unlock文件节点写数据与Kernel层进行通信。

Kernel层:控制对应的硬件。不管是屏幕还是cpu,统属于硬件,最终通过对硬件的控制来实现对电量管理。

整个架构如下:

img
img

重要的接口

在电源管理系统中,介绍几个比较的接口。

  • userActivity():报告影响系统休眠的用户活动,重新计算灭屏时间。假设我们手机设置15秒不操作,就息屏。那么我们每次触屏或者滑屏的时候,都会调用这个方法,然后系统会重新进行倒计时的操作。
  • Wakelock:提供了相关的接口来操作wakelock锁。比如说申请或者释放等。

WakeLock机制

wakelock是一种锁机制,只要有应用拿着这个锁,那么CPU就无法进入休眠,会一直处于工作的状态。

一般手机待机时,LCD、WIFI都会进入休眠状态,Android应用程序的代码也会停止运行。

为了保证程序中关键代码的正确执行,提供了Wake Lock的API,使得应用程序能够通过代码阻止CPU进入休眠状态。而这种情况的滥用,则会成为电量杀手。

JobScheduler

JobScheduler是安卓5.0推出的API,允许开发者在符合某些条件时创建执行在后台的任务

JobScheduler的目的是为了把一些不是特别紧急的任务放到更合适的时机去批量处理数据。这么做有两个好处:

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

耗电统计

耗电统计是一个系统的组件,伴随着系统运行的整个过程。这个统计是基于软件层面实现的,所以不同而硬件模块配置不同的参数,然后使用算法进行估算,谷歌要求OEM厂商必须测量并提供其实际值并写入到power_profile文件。

工具

叨叨那么多,那么我怎么知道我的应用耗电多少啊?其实还是有一些功能能够辅助我们来查询电量的使用情况的。

Battery Historian

介绍

Google提供的一套专门分析电量使用的工具。能够支持Android5.0及以后的设备上进行电池的相关信息和事件分析。它是一款可视化功能,对于系统级别和应用级别的事件都能够清晰直观的展现出应用的耗电比例、执行时间、次数等等。而且 还支持两个bugreport文件的对比,对关键的区别点高亮显示。

使用方式

首先我们看一个常用的cmd命令行指令:

  • 重置电量信息:adb shell dumpsys batterystats --reset

  • 开启记录全面的电量信息:adb shell dumpsys batterystats --enable full-wake-history

  • 导出:adb bugreport bugreport.zip

在我们进行具体的应用的电量分析的时候,最好是先将电量信息重置。然后操作我们的应用,操作一段时间以后导出bugreport.zip(统计报告)文件。

注意事项:获取统计报告的时候,需要将统计重置,并断开USB连接,否则将会影响有效性

当导出zip文件以后,打开bathist.ef.lc/(如果这个网址不行的话,可能需要自己搭建对应的环境了,相关搭建文章大家可以自己查一下)。然后上传zip文件。

image-20200626171004724
image-20200626171004724

上传完文件以后,过一段时间就能看到电池的使用报告。

image-20200628151349783
image-20200628151349783
结果分析

以上图为例,可以看到电量有3个地方下降比较快。

img
img

下降点1

img
img

可以看到这个时候,Screen一行的数据都是红色,表明屏幕处于点亮的状态。而下面的Top App也处于蓝色部分,表明有APP正在运行,所以这时候可以看一下具体运行的APP是哪个

img
img

下降点2

在下降点2中,系统由原来的Doze睡眠模式唤醒了,屏幕点亮,而wifi状态由打开变为关闭。可以推测此时用户的行为是点亮屏幕,关闭WiFi

img
img

下降点3

在下降点3看到唤醒锁颜色异常,可以看到,有一个应用长时间占用了wakelock锁导致的。

img
img

Energy Profiler

介绍

在搭载 Android 8.0 (API 26) 或更高版本的关联设备时,我们可以通过Profiler中的Energy Profiler来进行电量分析。Energy Profiler能够监控CPU、网络无线装置和GPS传感器的使用情况,并直观地显示每个组件消耗的电量。而且还能显示可能会影响耗电量的系统事件(唤醒锁定、闹钟、作业和位置信息请求)的发生次数。

Energy Profiler并不会直接测量耗电量,而是使用一种模型来估算设备上每项资源的耗电量。

使用方式
  1. 依次选择 View > Tool Windows > Profiler 或点击工具栏中的 Profile 图标

    img
    img

    如果 Select Deployment Target 对话框显示提示,请选择要将您的应用部署到哪个设备上以进行分析。如果您已通过 USB 连接设备但系统未列出该设备,请确保您已启用 USB 调试

  2. 点击 Energy 时间轴中的任意位置以打开 Energy Profiler。

然后就可以看到如下的图片

img
img

如图所示,Energy Profiler 的默认视图包括以下时间轴:

  1. “Event”时间轴:显示应用中的 Activity 在其生命周期内不断转换而经历各种不同状态的过程。此时间轴还会指示用户与设备的交互,包括屏幕旋转事件。
  2. “Energy”时间轴:显示应用的估算耗电量。
  3. “System”时间轴:显示可能会影响耗电量的系统事件。
image-20200624154458348
image-20200624154458348

当我们发现某一个时刻的耗电量比较高的时候,将鼠标指针放在Energy时间轴中的条形上,就可以显示出如上图的说明。这里会显示出CPU、网络和GPS的使用情况。而下面则会会显示Wake Locks和对应的Jobs信息。

实战分析

这里我们申请了一个wakeLock代码,片段如下:

        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock lock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "test");
        lock.acquire(16 * 1000);
GIF 2020-6-26 18-18-14
GIF 2020-6-26 18-18-14
  1. 上面的区域可以实时的显示我们当前应用的网络请求、位置、CPU的状态等
  2. 底部的红色表示有wake lock。当我们选中这部分区域以后,就可以在右侧看到申请wakelock的代码位置。

这种方式更加直观简单。不再需要我们去导出文件然后上传分析了,所以使用上来说更简单一些。

但是相对于Battery Historian能够长时间记录使用情况并且能够分析处理,这种需要实时的去盯着页面的,可能就有些不太友好了。如果能够通过Battery Historian定位到具体的电量消耗的大概位置,然后再通过这种方式去分析可能更好一些。

电量优化方案

我们首先汇总一下耗电的相关因素:

  • 屏幕的亮暗
  • 设备唤醒,睡眠的切换。尤其是唤醒
  • CPU运行相关
  • 网络
  • 传感器

我们知道屏幕渲染以及CPU的运行是耗电的主要因素。所以当我们在进行内存优化、渲染优化、布局优化、计算优化的时候,其实就已经在做电量的优化了。因此在平时开发的过程中要尽量少挖坑。

这里我们会根据具体的耗电因素来提供相关的优化方向:

屏幕常亮 vs 唤醒

当Android设备空闲时,屏幕变暗,然后会关闭屏幕(这个是通过电源管理来实现的),最后停止CPU的运行从而实现节约电量。但是当设备从休眠状态中,被应用程序唤醒的一瞬间会耗电过多。所以有时候可以保持屏幕的常量来节省电量。比如说玩游戏。

保持屏幕常亮最好的方式是在 Activity 中使用 FLAG_KEEP_SCREEN_ON 的 Flag。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

这种方式相对于唤醒锁(wake locks)来说,不再需要担心资源释放的问题。电源管理系统能够自动的管理不同的APP之间的切换问题。

所以我们需要根据自己的APP实际情况来控制是否需要保持屏幕常亮。

唤醒锁

就像我们在基础知识里面所讲解的那样,当使用了wake locks锁以后,CPU就不会进行休眠,也就能过保证CPU处于唤醒的状态。但是这种情况对电池的续航又有较大的影响,所以除非有真的必要,否则还是尽量减少wake lock的使用(比如我们上面所说的保持屏幕常亮方案)。

对于不得不使用唤醒锁的时候,也要注意去合理的使用。毕竟wakelock申请忘记释放会造成手机耗电太快,严重影响用户体验,用户知道骂娘,厂商知道骂开发。这里给出几点使用时的几点注意事项。

  • 对于wake lock,acquire和release要成对出现。为了防止异常导致未释放的情况发生,可以使用try,catch,finally,在finally中来进行释放。
  • 使用带有参数的acquire,设置超时时间。一段时间过后由系统自动进行唤醒锁的释放。
CPU

CPU的耗电主要有两种:

  • 长期处于高频工作状态。如动画的处理、View的频繁绘制、复杂度比较高的算法处理等等
  • 频繁的唤醒。CPU唤醒会使用电量激增。

对于CPU的使用情况,可以通过TraceView,Systrace、Profile等工具来获取CPU消耗情况,从而定位CPU使用异常的地方。针对以上CPU比较耗电的问题提供以下两种优化方案:

  • 减少后台应用的主动运行。比如说在进入后台运行以后关闭动画、暂停网络请求。
  • 减少算法的复杂度,包括时间复杂度和空间复杂度(这时候体现算法的一部分作用了)。
网络

在介绍Energy Profiler工具的时候,我们发现,其实网络请求也是比较耗电的。所以对于网络请求我们提供了以下几种可以优化的地方。

  • 控制请求次数和请求时机。

  • 对数据进行一定的压缩处理、既可以减少时间消耗也可以减少流量消耗。

  • 禁止长时间使用轮训进行网络请求。好像使用websocket更加省点一些?

网络请求中,一些不是特别紧急的任务可以通过JobScheduler放到更合适的时机去批量处理。比如说长时间的下载任务、特定的推送服务、日报表信息等等。

传感器

这里我们考虑的传感器主要是GPS定位等功能。

由于GPS传感器比较耗电,所以我们需要根据实际需要调整定位模式。

  1. 能不用则不用。比如说使用网络定位来代替GPS
  2. 能少用则少用。在GPS的定位功能使用完成以后要及时关闭。
  3. 能低精度就不要高精度。对于定位要求不高的,直接选择低精度模式。

对于其他传感器,比如说蓝牙等,也是相似的原理。

总结

电量优化相对内存优化UI优化来说比较简单一些,主要是注意对于一些耗电因素的使用场景。其实在我们进行布局优化、内存优化、启动优化等各种性能优化本身就是电量优化的一种方式。

参考

Android P 电量管理

Android开发中的电量和内存优化 (Google开发者大会演讲PPT&视频)

深入探索 Android 电量优化

Energy Profiler电量检测

使用Battery Historian工具分析Android耗电分析

android PowerManager分析

Android电量优化全解析

BugReport 耗电分析

Top团队大牛带你玩转Android性能分析与优化

本文由 开了肯 发布!

文章GitHub仓库:输出文章

同步公众号[开了肯]

image-20200404120045271
image-20200404120045271