Android优化之电量优化的实现
Android 5.0 后用 Battery Historian 工具分析电量。
耗电因素
移动网络请求
手机通过内置的射频模块和基站联系,从而链接上网的,而这个射频模块(radio)是非常耗电的,为了控制这个射频模块的耗电,硬件驱动及 Android RIL 层做了很多处理。例如可以单独关闭 radio(飞行模式),间歇性假休眠 radio(有数据发生时才上电,保持一个频率的与基站交互)等等。如今的 App 都是移动互联网 App,不可避免的会有大量的网络请求,会导致 radio 一直处于活跃状态,从而耗电量增加。
使用移动网络传输数据,电量的消耗有以下 3 种状态:
- Full power:高功率状态,移动网络连接被激活,允许设备以最大 的传输速率进行操作。
- Low power:低功耗状态,对电量的消耗差不多是 Full power 状态下的 50%。
- Standby:空闲态,没有数据连接需要传输,耗电最少。
从低功率到高功率大约 1.5s,从空闲态到高功率大约 2s,秒。在应用中每创建一个新的网络连接,网络(射频)模块都会转换到高功率状态(Radio Full Power),在数据传输完后再转回低功耗状态(Radio Low Power),转换的过程需要 5 秒,这 5 秒的耗电量保持在高功率状态,最后再转换空闲态需要 12 秒。因此,对于一个典型的移动网络设备,每个数据传输都会导致网络模块消耗 20 秒的电量。
WakeLock
Android 系统本身为了优化电量的使用,会在没有操作时进入休眠状态,来节省电量。当然,为了便于开发(很多应用不可避免的希望在灭屏后还能运行一些事儿,或是要保持屏幕一直亮着--比如播放视频),Android 提供了一个 PowerManager.WakeLock 的东西.
我们可以用 WakeLock 来保持 CPU 运行,或是防止屏幕变暗/关闭,让手机可以在用户不操作时依然可以做一些事儿。然而,获取 WakeLock 很容易,释放不好就会成为难题,消耗电量。例如获取了一个 WakeLock 来保持 CPU 运转,做一个复杂运算并将数据上传到后台服务器,然后释放该 WakeLock。然而这个过程可能并不像我们想象的那么快,可能因为比如服务器挂掉,计算出了异常等等导致 WakeLock 没有释放,CPU 会一直得不到休眠,而大大增加耗电。
另外,WakeLock 还有 android:keepScreenOn 属性,还可以让屏幕常量,这也是耗电大户。
private void acquireWakeLock(Context ctx) { if (null == mWakeLock) { PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, "TestLocknService"); if (null != mWakeLock) { mWakeLock. acquire(); } } }
- PARTIAL_WAKE_LOCK:保持 CPU 正常运转,屏幕和键盘灯有可能 会关闭。
- SCREEN_DIM_WAKE_LOCK:保持 CPU 运转,允许保持屏幕显示,但有可能变暗,允许关闭键盘灯。
- SCREEN_BRIGHT_WAKE_LOCK:保持 CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯。
- FULL_WAKE_LOCK:保持 CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度。
- ACQUIRE_CAUSES_ WAKEUP:强制使屏幕亮起,这种锁主要用于一些必须通知用户的操作。
- ON_AFTER_RELEASE:当锁被释放时,保持屏幕亮起一段时间。
需要注册权限
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.DEVICE_POWER"/>
GPS
应用中经常会用到定位服务,Android 提供了 Network 定位和 GPS 定位。相对来说,GPS 会精确得多,对于一些诸如跑步,导航类的应用基本会使用 GPS 定位。然而,GPS 定位也会消耗大量的电量。
AlarmManager
间隔不能太短。
优化建议
优化网络请求
在蜂窝移动网络下,最好做到批量执行网络请求,尽量避免频繁的间隔网络请求,尽量多地保持在 Radio Standby 状态。
尽量在 Wi-Fi 环境下使用数据传输。
谨慎使用 WakeLock
WakeLock 获取释放成对出现(调用 release),使用超时 WakeLock,以防出异常导致没有释放。
WakeLock 有一个接口 setReferenceCounted,用来设置 WakeLock 的计数机制,true 为计数,false 为不计数,默认是 true。所谓计数即每一个 acquire 必须对应一个 release;不计数则是无论有多少个 acquire,一个 release 就可以释放。虽然官方说默认 是计数的,但有的第三方 ROM 做了修改,使默认是不计数的。
主动设置 wakeLock.setReferenceCounted(false)。
监听手机充电状态
BatteryManager 会发送一个包含充电状态的持续广播,我们可以通过此广播获取充电状态和电量详情。因为这是一个持续广播,无需写 Receiver,可以直接通过 intent 获取相关数据。
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = context.registerReceiver(null,ifilter); // 设备正在充电 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS,-1); boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; // 也可以监听充电状态的变化,只要设备连接或断开电源,BatteryManager 就会广播相应的操作 int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1); boolean usbCharge = chargePlug == BATTERY_PLUGGED_USB; boolean acCharge = chargePlug == BATTERY_PLUGGED_AC;
另外页可以注册 Receiver来监听
<receiver android:name=".PowerConnectionReceiver"> <intent-filter> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> </intent-filter> </receiver>
Doze and App Standby
Android 6.0 提供了两个用来节省电量的技术 Doze 和 App Standby。
- Doze 瞌睡。如果设备闲置了一段较长时间,Doze 技术将通过延迟后台网络活动,CPU 运行等来减少电量损耗。
- App Standy 应用待机。不是最近得到过用户使用的 App,App Standy 将延缓这个应用的后台网络活动。
所有 Android 6.0 及以上的设备上,Doze and App Standby 都会运行。可能会影响 App 的运行,可以根据官方文档适配。
可以在代码中调起电量优化的设计页面,让用户选择是否将应用加入白名单,以在 Doze 模式下能够做一些事情。
定位
定位中使用 GPS,及时关闭
// Remove the listener you previously added locationManager.removeUpdates(locationListener);
计算优化
缩短代码产生指令运行的时间,进而减少某个应用程序对 CPU 时间片 的总占用时间,进而减少单位时间内该应用程序占整个系统耗电的百分比。
浮点运算比整数运算更消耗 CPU 时间片,因此耗电也会增加,在编写 代码的过程中应该尽量减少浮点运算。
- 除法变乘法。
- 充分利用移位。
- 查表法,直接使用映射关系,但这会增加内存占用,视情况而定。
熄屏后停止一些和 UI 效果有关的操作,比如动画。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- 腾讯云GAME-TECH沙龙干货回顾:腾讯游戏云全球化实践
- 基于内容关键性的高效 FEC 抗网络丢包算法
- 腾讯云GAME-TECH沙龙干货回顾:三七互娱游戏全球化运营心得分享
- 《实现模式》读书总结
- 腾讯云 GAME-TECH 沙龙干货回顾:Eyougame 海外发行实践分享
- 腾讯 AI Lab 副主任俞栋:过去两年基于深度学习的声学模型进展
- 白辉(七公):电商转型难题,看云如何破题
- 《Node.js在CLI下的工程化体系实践》成都OSC源创会分享总结
- 5分钟教你玩转 sklearn 机器学习(上)
- WebSocket系列之socket.io
- Python 数据分析学习笔记
- ERP管理软件系统设计方案
- 腾讯云GAME-TECH沙龙——全球同服游戏语音解决方案
- 【Scikit-Learn 中文文档】双聚类 - 无监督学习 - 用户指南 | ApacheCN
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Kafka分区分配策略(Partition Assignment Strategy)
- 内网渗透-代理篇(一)
- java学习应用篇|逃不掉的HelloWorld
- java学习原理篇|java程序运行套路
- 架构师成长之路系列(二)
- 前端性能优化 24 条建议(2020)
- 【Flutter 实战】大量复杂数据持久化
- GBDT+LR:Practical Lessons from Predicting Clicks on Ads
- 告别setState()! 优雅的UI与Model绑定 Flutter DataBus使用~
- k8s etcd 的实现原理
- iOS动态View的探索
- 安卓开发的瑞士军刀“Retrofit2框架”
- R语言中的广义线性模型(GLM)和广义相加模型(GAM):多元(平滑)回归分析保险资金投资组合信用风险敞口
- 来玩 TencentOS tiny 物联网终端操作系统
- LeetCode | 66.加一