对智能手机的续航能力敏感的Android用户来说,下面这条无疑是个消息。援引Android
Police报道在Android 6.0
Marshmallow兼容性定义文档中详细规定OEM厂商必须透明电池使用情况。
当前很多设备厂商设备的电池管理并不透明,
不允许智能手机用户查看所有的系统和应用状态,而在新版Android系统中谷歌督促所有OEM厂商必须在设备中提供电池状态。

性能优化总纲

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

希望大家会持续关注。

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

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

澳门新葡萄京官网注册 1

获得详细信息可访问:   Compatibility Definition Document (PDF)

题记

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


Android是一个以Linux为基础的半开源操作系统,主要用于移动设备,由Google和开放手持设备联盟开发与领导。Android系统主要应用在智能手机以及平板电脑设备上。日前,越来越多使用英特尔和AMD处理器的计算机也开始运行Android系统。

澳门新葡萄京官网注册 2

一、电池

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

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

PD的全名应该叫做USB Power Delivery
Specification,是USB的标准化组织推出的一个快速充电的标准。

在官方文档中写道:“向应用开发者提供更加精准的电量统计和电池消耗报告,丰富的工具来激励优化应用的耗电。”此外所有设备必须要实现能够最终硬件组件的电池使用情况并传输到专属的应用中,尤其需要部署实现:

测量电池用量###

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);
}

如今,在电池技术不会有突飞猛进的条件下,快速充电就成了手机解决续航问题的一个有效的解决方案。为了确保全球的
Android 手机、平板等硬件设备能够符合系统标准,Google
往往会在产品量产上市前先进行一轮 GMS
认证测试,用于检查兼容性、稳定性方面的问题。一般来说,所有要在海外市场销售的
Android 手机都必须通过这个认证,否则就无法正常使用 Play 应用商店、Gmail
邮件等各种 Google 服务。

– 必须提供每个组件的功率配置文件,在Android Open Source
Project网站上以书面的形式定义每个硬件元件的当前消耗值,和随着时间变化的耗电量曲线图。

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

近期,据 XDA Developers 报道,一份于 9 月份发布、疑似为最新的 GMS
认证文档显示,Google 官方对其中部分条例进行了更新,明确了 OEM
厂商需要遵循的新标准,比如 USB-PD 充电和数字健康等特性。

– 必须以mAh为单位报告所有硬件耗电量。

二、禁用电池广播

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

  • ACTION_BATTERY_LOW
  • ACTION_BATTERY_OKAY
  • ACTION_POWER_CONNECRED
  • ACTION_POWER_DISCONNECTED

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

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

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

先简单介绍下 PD 充电协议,这其实是 USB-IF
组织制定的一个快速充电的通用规范,能够让设备支持更高更强的电压和电流,输送功率最高可达
100W。

– 如果无法将硬件组件的耗电量传输给应用则应该归类于硬件本身

三、控制网络

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

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

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

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

但很多人仍会混淆 USB-C 接口和 PD 充电这两个指标,事实上 USB-C
只是一种接口,但有的设备厂商为了省事就借机把 PD
快充的特性去掉,要不就是只支持自家的私有快充标准,这也让各家 Android
手机的充电头兼容性变得十分混乱。


必须报告每个进程UID的的CPU耗电量。Android开源项目需要部署uid_cputime内核模组来实现。

后台数据

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

 ConnectivityManager的getBackgroundDataSetting()

早在 Android 7.0 时代,Google 就曾希望 OEM 厂商提供完整的 USB-C
接口标准,但当时认证文档中只是标注为‘强烈建议’,具体是否要执行,还是得看厂商的意愿。现在,这条规定的措辞已经调整为‘必须项’。按照文档的说法,所有
2019 年发布并配备了 USB Type-C 接口的新设备,必须要确保与符合 USB
规范的充电器具备‘完整的互操作性’,并配有 USB-C 的充电插头。

转自:    

数据传输传输速率的差异非常大,从小于每秒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的图片)


虽然 XDA 也指出,Google 的说法有些模糊,会让各家在支持的 PD
快充功率上仍会出现一些差异,但从整个 Android 生态来看,新规仍有助于
Android 手机的充电规格基本保持一致性,而不会出现‘都是 C 口却不能用 PD
快充’的尴尬情况。

四、定位

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

1、注销监听器还是和处理广播那样,在onPause()中调用removeUpdates()可以注销监听器。
2、并且可以用requestLocationUpdates()调整更新频率。选择合适的时间间隔和最小间隔距离可以适应不同的场景。
3、通过选择不同的位置服务来控制,如下:

  • GPS定位
  • WIFI定位
  • 基站定位
  • AGPS定位
    这四种定位的概念想必大多数都知道了,不知道的戳这里

除了充电标准的改动,XDA 还在这份 GMS 文档中发现了一些有趣的信息。

五、传感器

传感器是个很有意思的东西,与定位服务有点类似:应用向特定的传感器注册监听器,获得更新通知。
也是可以通过降低通知频率来省点,由于,每个设备不同,应用可以测量这4种延迟通知的频率,选择兼顾用户体验和省点的那一个。另一种策略是,当发现值不变化时,使用NORMAL或UI延迟,当发现有突然变化的时候,且话到GAME或FASTEST延迟。
如下:

  • SENSOR_DELAY_NORMAL
  • SENSOR_DELAY_UI
  • SENSOR_DELAY_GAME
  • SENSOR_DELAY_FASTEST

一个是旧版 Android 系统的审批时间表。按照文档内的说法,在 2020 年 1 月
31 日前,OEM 厂商仍可以提交预装了 Android 9
系统的产品以供检测,但在该日期之后,Google 只会接受预装了最新 Android 10
系统的硬件,设备开机时也要显示最新的‘Powered by Android’标志。

六、图形

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

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

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

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


其次,在手机成瘾问题上,文档要求所有更新至 Android 9、10
系统的设备都要将‘数字健康’、家长控制等特性内置到自己的系统中。目前这个应用已经应用在华硕、诺基亚等部分第三方
Android 手机上;当然 Google
也并未强求厂商必须使用原生的‘数字健康’应用,只要能提供类似的功能即可,如小米和华为都定制了自己的健康应用。

七、唤醒

有时候你得应用可能出于某种原因,需不时的被唤醒,去执行一些操作。>
但是很少应用真正需要到提醒时间去强行唤醒设备。当然闹钟这类程序会需要这种功能,但大多是等到用户主动唤醒才工作。

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

  • INTERVAL_FIFTEEN_MINUTES
  • INTERVAL_HALF_HOUR
  • INTERVAL_HOUR
  • INTERVAL_HALF_DAY
  • INTERVAL_DAY

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


和 iOS
上的‘屏幕使用时间’一样,这个‘数字健康’也包含了各种能让用户调整、监测自己手机状况的功能,比如会显示你每天使用手机的时长、哪些
App 花的时间最多、睡觉时手机可以强行进入勿扰模式等。

八、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,在退出和暂停的时候,否则可能一直显示,电量很快耗光。

此外是有关手势交互的设置,文档表示,所有通过 GMS 验证的 Android 10
手机,都需要将传统的‘三大金刚’虚拟按键或 Android 10
纯手势交互作为可选项,同时不允许厂商将自己的手势交互作为出厂默认的交互,至于之前的‘药丸’导航栏则不做硬性规定。

预防问题

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


最后,还出现了 Google
一项名为‘游戏设备认证’的新计划,需要通过该认证的手机必须要保证芯片中所有
CPU 核心都能被调用,避免出现降频,还需要支持最新的图形 API
接口、能够为游戏分配足够的运行内存等。猜测,这可能是 Google
面向雷蛇、黑鲨、华硕等一系列游戏手机而设立的标准。

九、总结

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

让各个手机用户都可以更好的享受快速充电技术,方便了大家的使用。享受到快充带来的便利。