Android优化(三)_延迟电池续航时间

性能优化总纲

大概会花一个月左右的时间出7-8个专题来分享一下在工作和学习中积累下来的Android性能优化经验。

希望大家会持续关注。

现在是专题三:电池电量优化

但这也仅仅是为大家提供一些思路与较为全面的总结,算不上什么,希望有错误或问题在下面评论。

也欢迎点开我的掘金、简书、CSDN主页看看其他文章。

最后完结以后会将思维导图与优化框架整理出来,请期待。

题记

电池虽小,地位却非常重要。移动设备使用电池,做任何事情都要费电。而大多数情况下,白天很少有机会给电池充电,哪怕你带了电宝,也可能出现不够用的情况。

而作为开发者,如果你的程序被用户发现耗电量过多很容易被卸载,再也不用,是非常致命的,因此我们要制定一系列解决方案,防止此类事情发生。

本章带领大家探讨如何测量电池的使用量,以及即可以省电,又不影响用户体验的方法。


一、电池

一般来说,充满电的状态可以保证手机正常使用1-2天,除去屏幕和CPU所消耗的电量以外,设备使用多少电量严重以来所有应用都做了什么,也就是取决于你的应用是如何设计和实现的。 一般有如下功能:

  • 执行代码(显而易见)
  • 数据传输(上传和下载,使用WiFi,2G,3G,4G)
  • 定位
  • 传感器
  • 渲染图像
  • 唤醒任务 在学习如何最大限度的减少耗电量之前,我们想要有办法来测量。
测量电池用量
1、Battery Historian

Battery Historian是Android 5.0开始引入的新API。通过下面的指令,可以得到设备上的电量消耗信息:

$ adb shell dumpsys batterystats > xxx.txt  //得到整个设备的电量消耗信息
$ adb shell dumpsys batterystats > com.package.name > xxx.txt //得到指定app相关的电量消耗信息复制代码

得到了原始的电量消耗数据之后,我们需要通过Google编写的一个python脚本把数据信息转换成可读性更好的html文件:

$ python historian.py xxx.txt > xxx.html复制代码

打开这个转换过后的html文件,可以看到类似TraceView生成的列表数据,这里的数据信息量很大,这里就不展开了。

3) Track Battery Status & Battery Manager

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


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,“Thephoneischarging!”);
}复制代码

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


private boolean checkForPower(){

IntentFilter filter=new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus=this.registerReceiver(null,filter);
int chargePlug  = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);
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);
}复制代码
2、另一种方法得到耗电量

APP获取电量算法。 经过查看源码,我们看到app计算电量的算法如下:

  • 在主Activity里面 info.getBatteryStats() 就搞定了。 首先 load(),如果load失败,走CPU时间计算,通过getAppListCpuTime这样函数。 CPU的时间计算,有3个核心步骤:
  1. ActivityManager遍历runningApp进程,获取对应pid
  2. getAppProcessTime(pid)通过读取/proc/pid/stat文件,拿取APP在CPU的运行时间。
  3. 重新为BatterySipper附值:+time;
  • 获取APP消耗 processAppUsage();也分三步走:
  1. 通过PowerProfile 获取cpu的速度层次(speedsteps),方便后面使用
  2. 根据不同CPU的速度等级,计算cpu在某个速度下的电量,mA毫安
  3. mPowerProfile.getAveragePower(PowerProfile.POWERCPUACTIVE,p)

很多地方都用到这个API获取power。 那它究竟做了些什么呢? 查看系统源码可以知道:

实际上这句话是获取1个叫PowerMap的数据结果,获得电量。而PoweMap的赋值,是来源于com.android.internal.R.xml.powerprofile 的文件。 关于该文件的获取 android-版本号/core/res/res/xml/powerprofile.xml

计算各种耗电量的详细算法是: 来自深入浅出Android App耗电量统计 总结App耗电量计算公式:(

Uid_Power(App耗电量,单位:mAh) = Uid_Power1 + Uid_Power2 + Uid_Power3 + Uid_Power4 + Uid_Power5

Uid_Power1 = (Process1_Power + … + ProcessN_Power);

Process_Power = (CPUSpeed_Time * POWER_CPU_ACTIVE);

Uid_Power2 = PartialWakeLock_Time * POWER_CPU_WAKE

Uid_Power3 = ( tcpBytesReceived + tcpBytesSent ) * averageCostPerByte

Uid_Power4 = wifiRunningTimeMs * POWER_WIFI_ON

Uid_Power5 = (Sensor1_Power + … + SensorN_Power)

Sensor_Power = Sensor_Time * Power_Sensor

二、禁用电池广播

系统除了定义了ACTION_BATTERY_CHANGED包含了电池信息,还定义了应用可以使用的4个广播Intent:

  • ACTION_BATTERY_LOW
  • ACTION_BATTERY_OKAY
  • ACTION_POWER_CONNECRED
  • ACTION_POWER_DISCONNECTED

当我们在一个广播接收器接收系统发送的这四个广播时,只要有一个发生,应用就会启动。这样有一个严重的缺陷,如果你在前台运行时,是没有问题的,但是如果在后台时,还出现Toast消息(非系统提醒),就有可能干扰其他应用,损害用户体验

所以我们有一个很好的解决:

  • 只有当应用在前台运行时才可以启用广播
  • 步骤: 1、广播接收器默认必须是禁用 2、广播接收器必须在onResume()中启用,在onPause()被禁用

三、控制网络

现在基本所有的应用都必须在设备和服务器之间传递数据,就像获取电池状态一样,应用需要获取设备商的网络链接信息。ConnectivityManager类提供了API。供应用调用以此访问网络信息。 Android设备通常由多个数据连接:

  • Bluetooth
  • Ethernet
  • Wi_Fi
  • WiMax
  • 移动网络(EDGE、UMTS、LTE)

为了最大限度延长电池的使用时间,我们需要直到如下事情:

  • 后台数据设置
  • 数据传输频度
后台数据

可以通过下面这个方法来获取后台数据的设置,不过在4.0以后,这个方法始终返回为true,当强行不允许时,网络会断开。

    ConnectivityManager的getBackgroundDataSetting()复制代码
数据传输

传输速率的差异非常大,从小于每秒100Kb的GPRS数据连接到每秒几Mb的LTE或Wifi都有。除了连接类型,NetWorkInfo类还指定了连接的子类型,例如:

  • NETWORK_TYPE_GPRS(API 1)

  • NETWORK_TYPE_LTE(API 11)

  • NETWORK_TYPE_HSPAP(API 13)

如果创建和部署了新技术,也会增加新的子类型。留意一下每个SDK版本的改动。

人们都习惯更快的连接,即使WiFi芯片耗电量比较多,但Wifi的速率和免费可以让数据在最短时间最小成本完成传输,从而降低电池消耗。

如果能控制数据的传输类型,就可以现压缩数据,再传输到设备上。虽然解压缩数据耗费CPU,也多用了些电量,但传输速度大大加快,数据通讯设备可以很快关闭,从而延长了电池寿命。通常我们这么做:

  • 使用GZIP压缩文本数据,使用GZIPInputStream类访问数据;
  • 使用匹配设备分辨率的资源(比如:不必为320480的屏幕下载19201080的图片)

四、定位

现在很多的App都会做一件事:获取你的定位信息。一些用户可能愿意牺牲电池寿命来频繁的更新位置,而其他人定远县志更新次数,以确保设备点亮不会很快消耗完,所以需要提供不同的昔阳县来满足用户需求。

1、注销监听器 还是和处理广播那样,在onPause()中调用removeUpdates()可以注销监听器。

2、并且可以用requestLocationUpdates()调整更新频率。选择合适的时间间隔和最小间隔距离可以适应不同的场景。

3、通过选择不同的位置服务来控制,如下:

  • GPS定位
  • WIFI定位
  • 基站定位
  • AGPS定位

这四种定位的概念想必大多数都知道了,不知道的戳这里


五、传感器

传感器是个很有意思的东西,与定位服务有点类似:应用向特定的传感器注册监听器,获得更新通知。

也是可以通过降低通知频率来省点,由于,每个设备不同,应用可以测量这4种延迟通知的频率,选择兼顾用户体验和省点的那一个。另一种策略是,当发现值不变化时,使用NORMAL或UI延迟,当发现有突然变化的时候,且话到GAME或FASTEST延迟。如下:

  • SENSOR_DELAY_NORMAL
  • SENSOR_DELAY_UI
  • SENSOR_DELAY_GAME
  • SENSOR_DELAY_FASTEST

六、图形

应用花费了很多时间在屏幕上画东西,无论是使用GPU渲染的3D游戏还是使用CPU的日历程序,都想只以最少的代价赖在屏幕上展示期望的结果,以延长电池寿命。

如前所述,CPU非全速时使用的电量相对少一些。现代的CPU使用动态调频喝点呀来节省电力和减少发热量。这两种技术通常一起使用,成为DVFS技术(动态电压和频率调整),Linux的内核、Android以及现代处理器都支持这种技术。

虽然你不能直接控制电压和频率,或将内部组建断电,但可以控制应用渲染的方式。流畅的帧速率还是要达到的。虽然在Android上帧率有上线(每秒60帧),但优化渲染还是有效果的。除了可能降低能耗,你可可以为后台运行的应用留出更多的空间,提供了更好的整体用户体验。

例如:比如我们手机里的壁纸,可以调用onVisibilityChanged()方法。事实上,壁纸可以是不可见的。但很容易忘记,持续绘制壁纸会消耗很多的电量。


七、唤醒

有时候你得应用可能出于某种原因,需不时的被唤醒,去执行一些操作。

但是很少应用真正需要到提醒时间去强行唤醒设备。当然闹钟这类程序会需要这种功能,但大多是等到用户主动唤醒才工作。

多数情况下,应用需要将未来某一刻安排提醒到时,但对时间要求并不是很严格。为此Android定义了AlarmManager.setInexactRepeating(),它的参数和其“兄弟”setPepeating()基本相同,这种题型更节能,系统也避免了出现不必要的唤醒,Android定义了5个提醒间隔:

  • INTERVAL_FIFTEEN_MINUTES
  • INTERVAL_HALF_HOUR
  • INTERVAL_HOUR
  • INTERVAL_HALF_DAY
  • INTERVAL_DAY

最好的结果就是所有应用都使用这种提醒,而不用精确的触发提醒。为了尽可能的节电,应用还可以让用户配置提醒的调度,因为有人发现较长时间间隔并不会对用户体验有不好的影响。


八、WakeLock

有些时候,一些应用即使长时间不和设备交互,也要阻止进入休眠状态,来保持良好的用户体验,就比如在看视频的时候,这种情况下,CPU需要做视频解码,同时屏幕保持开启,让用户能够观看。此外,视频播放时屏幕不能变暗。

Android为这种情况设计了WakeLoac类

private void runInWakeLoac(Runnable runnable,int flags){
    PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
    PowerManager.WakeLoac wl = pm.newWakeLock(flag,"My WakeLock");
    wl.acquire();
    runnable.run();
    wl.release();
}复制代码

有一点需要注意:需要WAKE_LOCK权限。

系统的行为取决于WakeLock对象创建时传入的flags:

  • PARTIAL_WAKE_LOCK(CPU开)
  • SCREEN_DIM_WAKE_LOCK(CPU开、暗色显示)
  • SCREEN_BRIGHT_WAKE_LOCK(CPU开、明亮显示)
  • FULL_WAKE_LOCK(CPU开、明亮显示、键盘开) 这些标记可以结合使用
  • ACQUIRE_CAUSES_WAKEUP(打开屏幕和键盘)
  • ON_AFTER_RELEASE(WakeLock是放后继续保持屏幕和键盘开启片刻) 特别重要的一点:一定要释放WakeLock,在退出和暂停的时候,否则可能一直显示,电量很快耗光。
预防问题

防止出现特殊的问题,建议使用带超时的WakeLoac.acquire()版本,它会在超时后自动释放。 另外:可以用setKeepScreenOn()方法控制是否要保持屏幕,只要可见的View指定了要保持屏幕,屏幕就会一直保留。

九、总结

用户不会注意到应用是否延长了电池寿命。但是如果不做任何处理,那就有可能被注意到了。因为单个应用耗电过多,用户几天还是可以感受出来的,用户到时候就会卸载用用,所以要对电量使用做一些优化处理,并且给用户配置选项的自由,来应对用户产生的各种需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值