电池续航时间是移动用户体验中最重要的一个方面。没电的设备完全无法使用。因此,对于应用来说,尽可能地考虑电池续航时间是至关重要的。在我们开发时对于单个APP应该注意能够:
减少操作:您的应用是否存在可删减的多余操作?例如,是否可以缓存已下载的数据,而不是每次重新下载 数据?
推迟操作:应用是否需要立即执行某项操作?例如,是否可以等到设备充电后或者Wifi连接时(通常情况下使 用移动网络要比WIFI更耗电 )再将数据备份到云端?
合并操作:工作是否可以批处理,而不是多次将设备置于活动状态?比如和服务器请求不同的接口获取数 据,部分接口是否可以合并为一个?
Doze低电耗模式和StandBy待机模式
从 Android 6.0(API 级别 23)开始,Android 引入了两项省电功能,通过管理应用在设备未连接至电源时的行为 方式,帮助用户延长电池寿命。当用户长时间未使用设备时,低电耗模式会延迟应用的后台 CPU 和网络活动,从 而降低耗电量。应用待机模式会延迟用户近期未与之交互的应用的后台网络活动。
低电耗模式和应用待机模式管理在 Android 6.0 或更高版本上运行的所有应用的行为,无论它们是否专用于 API 级 别 23。
Doze低电耗模式
如果设备 未充电、屏幕熄灭、让设备在一段时间内保持不活动状态 ,那么设备就会进入Doze模式。在Doze模式 下,系统会尝试通过限制应用访问占用大量网络和 CPU 资源的服务来节省电量。它还会阻止应用访问网络,并延 迟其作业、同步和标准闹钟。
Doze中文是打盹,所以系统会定期退出打盹一小段时间,让应用完成其延迟的活动。在此维护期内,系统会运行 所有待处理的同步、作业和闹钟,并允许应用访问网络。
随着时间的推移,系统安排维护期的次数越来越少,这有助于在设备未连接至充电器的情况下长期处于不活动状态 时降低耗电量。
一旦用户通过移动设备、打开屏幕或连接至充电器唤醒设备,系统就会立即退出低电耗模式,并且所有应用都会恢 复正常活动。
在低电耗模式下,您的应用会受到以下限制:
暂停访问网络。
系统忽略PowerManager.WakeLock唤醒锁定。
标准 AlarmManager 闹钟(包括 setExact() 和 setWindow() )推迟到下一个维护期。
如果需要设置在设备处于低电耗模式时触发的闹钟,请使用API 23(6.0)提供的 setAndAllowWhileIdle() (一次性闹钟,同set方法)或 setExactAndAllowWhileIdle() (比set方法设
置的精度更高,同setExact)。
使用 setAlarmClock() 设置的闹钟将继续正常触发,系统会在这些闹钟触发之前不久退出低电耗模 式。
系统不执行 WLAN 扫描。 系统不允许运行同步适配器AbstractThreadedSyncAdapter (账号同步拉活)。 系统不允许运行 JobScheduler 。
Android 6.0+ 里的 设置 - 应用 - 特殊访问权限 - 电池优化 功能,可以配置 Doze 模式白名单。一般用户安装的应用都默认启用了电池优化,如果要使某个应用在系统处于低电耗模式时也能正常访问网络,可以设置“不允许”电池优化。
2种打盹模式
- 浅度打盹
屏幕关闭
电池未处于充电模式
- 深度打盹
屏幕关闭
电池未处于充电模式
设备静止约30分钟
Doze 模式测试
进入未连接充电的模式
adb shell dumpsys battery unplug
强行进入Doze模式(deep表示深度打盹)
adb shell dumpsys deviceidle step [light|deep]
退出Doze模式,让手机恢复正常需要复位充电模式
adb shell dumpsys battery reset
Standby待机模式
应用待机模式允许系统判定应用在用户未主动使用它时是否处于待机状态。当用户有一段时间未触摸应用并且应用
没有以下表现,则Android系统就会使应用进入空闲状态 应用当前有一个进程在前台运行(作为活动或前台服务,或者正在由其他活动或前台服务使用)。
应用生成用户可在锁定屏幕或通知栏中看到的通知。
当用户将设备插入电源时,系统会从待机状态释放应用,允许它们自由访问网络并执行任何待处理的作业和同步。 如果设备长时间处于闲置状态,系统将允许闲置应用访问网络,频率大约每天一次。
检测耗电情况工具
Energy Profiler
使用 Android 8.0 及以上版本的设备时,使用Energy Profiler 可以了解应用在哪里耗用了不必要的电量。 Energy Profiler 会监控 CPU、网络无线装置和 GPS 传感器的使用情况,并直观地显示其中每个组件消耗的电量。还会显 示可能会影响耗电量的系统事件(唤醒锁定、闹钟、作业和位置信息请求)的发生次数。
具体使用参照官网
https://developer.android.google.cn/studio/profile/energy-profiler
Battery Historian
Battery Historian是一个可以了解设备随时间的耗电情况的工具 。在系统级别,该工具以 HTML 的形式可视化来
自系统日志的电源相关事件。在具体应用级别,该工具可提供各种数据,帮助您识别耗电的应用行为。
Battery Historian可以帮助我们查看应用是否具有以下耗电行为:
过于频繁地触发唤醒提醒(至少每 10 秒钟一次)。 持续保留 GPS 锁定。
至少每 30 秒调度一次作业。
至少每 30 秒调度一次同步。 使用手机无线装置的频率高于预期
参考官网
https://developer.android.google.cn/topic/performance/power/battery-historian?hl=zh_cn
如何进行电量优化?
了解手机关键耗电的地方及分析耗电的工具后。接下来就是我们的核心,如何来进行电量的优 化?首先我们先简单总结汇总一下耗电的相关因素
屏幕亮暗相关
设备 awake,sleep 的切换,尤其是唤醒.
CPU 运行相关
网络
传感器
我们都知道屏幕的渲染及 CPU 的运行是耗电的主要因素之一。所以当我们在做内存优化、渲染优化、计算优化的时候,就已然在做电量优化。所以在平时的开发中,我们要注意点滴性能 的优化积累,实际上当我们来做电量分析的时候,也是在找自己挖的坑。所以尽量有意识在项 目的开发过程中尽量少挖坑,这一点是我们在分析其他优化项首先要提到的一个点。
监听手机充电状态
我们可以通过下面的代码来获取手机的当前充电状态:
监控电池电量和充电状态
为了减少电池续航被我们软件的影响,我们可以通过检查电池状态以及电量来判断是否进行某些操作。比如我们可 以在充电时才进行一些数据上报之类的操作。
获取充电状态
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = registerReceiver(null, ifilter);
// 是否正在充电
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
// 什么方式充电?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); //usb
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB; //充电器
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
Log.e(TAG, "isCharging: " + isCharging + " usbCharge: " + usbCharge + " acCharge:" + acCharge);
监控充电状态变化
//注册广播
IntentFilter ifilter = new IntentFilter();
//充电状态 ifilter.addAction(Intent.ACTION_POWER_CONNECTED); ifilter.addAction(Intent.ACTION_POWER_DISCONNECTED); //电量显著变化 ifilter.addAction(Intent.ACTION_BATTERY_LOW); //电量不足
ifilter.addAction(Intent.ACTION_BATTERY_OKAY); //电量从低变回高 powerConnectionReceiver = new PowerConnectionReceiver(); registerReceiver(powerConnectionReceiver, ifilter);
public class PowerConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) {
Toast.makeText(context, "充电状态:CONNECTED", Toast.LENGTH_SHORT).show();
} else if (intent.getAction().equals(Intent.ACTION_POWER_DISCONNECTED)) {
Toast.makeText(context, "充电状态:DISCONNECTED", Toast.LENGTH_SHORT).show();
} else if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW))
{ Toast.makeText(context, "电量过低", Toast.LENGTH_SHORT).show();
} else if (intent.getAction().equals(Intent.ACTION_BATTERY_OKAY)) { Toast.makeText(context, "电量从低变回高", Toast.LENGTH_SHORT).show();
} }
}
这里我们就需要思考,根据具体的业务,考虑将一些不需要及时地和用户交互的操作放到充电 的时候去做。比如:360 手机助手,当充上电的时候,才会自动清理手机垃圾,自动备份上传图片、联系人 等到云端,从而避免当用户手机低电量时,任然继续进行耗电操作。
屏幕唤醒
当 Android 设备空闲时,屏幕会变暗,然后关闭屏幕,最后会停止 CPU 的运行,这样可以防 止电池电量掉的快。但有些时候我们需要改变 Android 系统默认的这种状态:比如玩游戏时我 们需要保持屏幕常亮,比如一些下载操作不需要屏幕常亮但需要 CPU 一直运行直到任务完成。
保持屏幕常亮比较好的方式是在 Activity 中使用 FLAG_KEEP_SCREEN_ON 的 Flag。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
}
这个方法的好处是不像唤醒锁(wake locks),需要一些特定的权限(permission)。并且能 正确管理不同 app 之间的切换,不用担心无用资源的释放问题。
另一个方式是在布局文件中使用 android:keepScreenOn 属性:
android:keepScreenOn = “true”的作用和 FLAG_KEEP_SCREEN_ON 一样,使用代码的好 处是你允许你在需要的地方关闭屏幕。
注意:一般不需要人为的去掉 FLAG_KEEP_SCREEN_ON 的 flag,windowManager 会管理好程序进入 后台回到前台的的操作。如果确实需要手动清掉常亮的 flag,使用
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
所以这里我们需要根据自己的 APP 实际情况,根据业务来控制好是否保持屏幕常量。比如 APP 需要支持视频播放。那么在播放的界面需要控制好不熄屏,当退出播放时,当然就没有了 这个设置。
WakeLock
wake_lock 锁主要是相对系统的休眠而言的,意思就是程序给 CPU 加了这个锁那系统就不会 休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些 问题。
需要使用 PowerManager 这个系统服务的唤醒锁(wake locks)特征来保持 CPU 处于唤醒状 态。唤醒锁允许程序控制宿主设备的电量状态,创建和持有唤醒锁对电池的续航有较大的影 响,所以,除非是真的需要唤醒锁完成尽可能短的时间在后台完成的任务时才使用它。比如在 Acitivity 中就没必要用了。如果需要关闭屏幕,使用上述的 FLAG_KEEP_SCREEN_ON。
只有一种合理的使用场景,使用后台服务在屏幕关闭情况下 hold 住 CPU 完成一些工作,需要 使用唤醒锁,如果不使用唤醒锁来执行后台服务,不能保证因 CPU 休眠未来的某个时刻任务 会停止,这不是我们想要的。
唤醒锁可划分并识别为四种用户唤醒锁:
注意:自 API 等级 17 开始,FULL_WAKE_LOCK 将被弃用。 应用应使用 FLAG_KEEP_SCREEN_ON。
1.添加唤醒锁权限:
2.直接使用唤醒锁:
注意:在使用该类的时候,必须保证 acquire 和 release 是成对出现的。不然当我们业务已经不需要时, 当 CPU 处于唤醒状态,这个时候就会损耗多余的电量。
JobScheduler
自 Android 5.0 发布以来,JobScheduler 已成为执行后台工作的很好的方式,其工作方式有 利于用户在适当的时机执行正确的事情。应用可以在安排作业的同时允许系统基于内存、电源 和连接情况进行优化。JobSchedule 的宗旨就是把一些不是特别紧急的任务放到更合适的时机 批量处理。这样做有两个好处:
- 避免频繁的唤醒硬件模块,造成不必要的电量消耗。
- 避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。
GPS
选择合适的 Location Provider
Android 系统支持多个 Location Provider:
- GPS_PROVIDER: GPS 定位,利用 GPS 芯片通过卫星获得自己的位置信息。定位精准度高,一般在 10 米左右, 耗电量大;但是在室内,GPS 定位基本没用。
- NETWORK_PROVIDER: 网络定位,利用手机基站和 WIFI 节点的地址来大致定位位置,这种定位方式取决于服务器,
即取决于将基站或 WIF 节点信息翻译成位置信息的服务器的能力。 - PASSIVE_PROVIDER: 被动定位,就是用现成的,当其他应用使用定位更新了定位信息,系统会保存下来,该应用接 收到消息后直接读取就可以了。
如果 App 只是需要一个粗略的定位那么就不需要使用 GPS 进行定位,既耗费电量,定位的耗 时也久。
及时注销定位监听
在获取到定位之后或者程序处于后台时,注销定位监听,此时监听 GPS 传感器相当于执行 no- op(无操作指令),用户不会有感知但是却耗电。
多模块使用定位尽量复
多个模块使用定位,尽量复用上一次的结果,而不是都重新走定位的过程,节省电量损耗;例 如:在应用启动的时候获取一次定位,保存结果,之后再用到定位的地方都直接去取。
网络优化
正常一条网络请求需要经过的流程是这样:
DNS 解析,请求DNS服务器,获取域名对应的 IP 地址;
与服务端建立连接,包括 tcp 三次握手,安全协议同步流程;
连接建立完成,发送和接收数据,解码数据。
这里有明显的三个优化点:
直接使用IP地址,去除DNS解析步骤
不要每次请求都重新建立连接,复用连接或一直使用同一条连接
压缩数据,减小传输的数据大小
DNS优化
DNS(Domain Name System),它的作用是根据域名查出IP地址,它是HTTP协议的前提,只有将域名正确的解析成IP地址后,后面的HTTP流程才能进行。
DNS完整的解析流程很长,会先从本地系统缓存取,若没有就到最近的DNS服务器取,若没有再到主域名服务器取,每一层都有缓存,但为了域名解析的实时性,每一层缓存都有过期时间。
传统的DNS解析机制有几个缺点:
缓存时间设置的长,域名更新不及时,设置的短,大量DNS解析请求影响速度;
域名劫持,容易被中间人攻击,或被运营商劫持,把域名解析到第三方IP地址,据统计劫持率会打到7%;
DNS 解析过程不受控制,无法保证解析到最快的IP;
一次请求只能解析一个域名。
为了解决这些问题,就有了HTTPDNS,原理很简单,就是自己做域名解析的工作,通过HTTP请求后台去拿到域名对应的IP地址,直接解决上述所有问题。
HTTPDNS的好处总结就是:
Local DNS 劫持:由于 HttpDns 是通过 IP 直接请求 HTTP 获取服务器 A 记录地址,不存在向本地运营商询问 domain 解析过程,所以从根本避免了劫持问题。
DNS 解析由自己控制,可以确保根据用户所在地返回就近的 IP 地址,或根据客户端测速结果使用速度最快的 IP;
一次请求可以解析多个域名。
HTTPDNS 几乎成为中大型 APP 的标配。解决了第一个问题, DNS 解析耗时的问题,顺便把DNS 劫持也解决了。
阿里HTTPDNS:
https://help.aliyun.com/document_detail/30103.html?spm=a2c4g.11186623.6.543.7eee78bc3kDYhO
连接优化
第二个问题,连接建立耗时的问题,这里主要的优化思路是复用连接,不用每次请求都重新建立连接,如何更有效 率地复用连接,可以说是网络请求速度优化里最主要的点了。
【keep-alive】: HTTP 协议里有个 keep-alive,HTTP1.1默认开启,一定程度上缓解了每次请求都要进行TCP三 次握手建立连接的耗时。原理是请求完成后不立即释放连接,而是放入连接池中,若这时有另一个请求要发出,请 求的域名和端口是一样的,就直接拿出连接池中的连接进行发送和接收数据,少了建立连接的耗时。 实际上现在无 论是客户端还是浏览器都默认开启了keep-alive,对同个域名不会再有每发一个请求就进行一次建连的情况,纯短 连接已经不存在了。
但有 keep-alive 的连接一次只能发送接收一个请求,在上一个请求处理完成之前,无法接受新的请求。若同时发 起多个请求,就有两种情况:
若串行发送请求,可以一直复用一个连接,但速度很慢,每个请求都要等待上个请求完成再进行发送。
若并行发送请求,那么只能每个请求都要进行tcp三次握手建立新的连接。
对并行请求的问题,新一代协议 HTTP2 提出了多路复用去解决。 HTTP2 的多路复用机制一样是复用连接,但它复 用的这条连接支持同时处理多条请求,所有请求都可以并发在这条连接上进行,也就解决了上面说的并发请求需要 建立多条连接带来的问题。
多路复用把在连接里传输的数据都封装成一个个stream,每个stream都有标识,stream的发送和接收可以是乱序 的,不依赖顺序,也就不会有阻塞的问题,接收端可以根据stream的标识去区分属于哪个请求,再进行数据拼接, 得到最终数据。
Android 的开源网络库OKhttp默认就会开启 keep-alive ,并且在Okhttp3以上版本也支持了 HTTP2。
数据压缩
第三个问题,传输数据大小的问题。数据对请求速度的影响分两方面,一是压缩率,二是解压序列化反序列化的速 度。目前最流行的两种数据格式是 json 和 protobuf,json 是字符串,protobuf 是二进制,即使用各种压缩算法 压缩后,protobuf 仍会比 json 小,数据量上 protobuf 有优势,序列化速度 protobuf 也有一些优势 。
除了选择不同的序列化方式(数据格式)之外,Http可以对内容(也就是body部分)进行编码,可以采用gzip这 样的编码,从而达到压缩的目的。
其它
1、使用webp代替png/jpg
2、不同网络的不同图片下发,如(对于原图是300x300的图片):
2/3G使用低清晰度图片:使用100X100的图片;
4G再判断信号强度为强则使用使用300X300的图片,为中等则使用200x200,信号弱则使用100x100图片;
WiFi网络:直接下发300X300的图片
3、http开启缓存 / 首页数据加入缓存
参考
https://developer.android.google.cn/topic/performance/power?hl=zh_cn
https://segmentfault.com/a/1190000018225386?utm_source=tag-newest
https://blog.csdn.net/lovelyelfpop/article/details/86612861
https://juejin.cn/post/6896302142542315533
https://tech.meituan.com/2018/03/11/dianping-shortvideo-battery-testcase.html