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

作者: 影响身边的人 | 来源:发表于2016-08-02 14:27 被阅读640次

    性能优化总纲

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

    希望大家会持续关注。

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

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

    题记

    电池虽小,地位却非常重要。移动设备使用电池,做任何事情都要费电。而大多数情况下,白天很少有机会给电池充电,哪怕你带了电宝,也可能出现不够用的情况。而作为开发者,如果你的程序被用户发现耗电量过多很容易被卸载,再也不用,是非常致命的,因此我们要制定一系列解决方案,防止此类事情发生。本章带领大家探讨如何测量电池的使用量,以及即可以省电,又不影响用户体验的方法。


    一、电池

    一般来说,充满电的状态可以保证手机正常使用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生成的列表数据,这里的数据信息量很大,这里就不展开了。

    1. 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指定了要保持屏幕,屏幕就会一直保留。


    九、总结

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

    相关文章

      网友评论

      本文标题:Android优化(三)_延迟电池续航时间

      本文链接:https://www.haomeiwen.com/subject/zmbfsttx.html