袁宜霞
(广东省外语艺术职业学院,广东广州 510000)
随着5G 网络的普及带来了移动互联网的巨大发展,移动终端的App 应用越来越多,使用场景越来越丰富,使用频率也越来越高,使得移动终端电量消耗过快的问题日益突出。移动终端的电池电量是非常有限的,保持持久的续航能力尤为重要。App研发团队必须慎重检查电量的使用,以免导致用户手机耗电发热,带来不良体验。
移动终端电池不耐用有两方面原因:一方面是手机硬件和操作系统的问题,另一方面是移动应用App的问题,例如:随意自启动,频繁在后台多次唤醒,抢占桌面、通知栏和悬浮窗等。本文着重于关注Android 移动应用App 对电量带来的影响,总结导致电量消耗过快的常见原因,介绍Android 电量相关测试与优化技术,并以案例的方式,针对典型问题提出相应优化措施,进一步有效地提升App电量方面的用户体验。
图1是一个典型的智能手机硬件架构图[1]。
图1 智能手机的硬件架构[1]
当智能手机待机时,主板可以直接关闭应用处理器、无线网卡和显示器等外设的供电电源,而基带处理器、蜂窝话音和射频设备等需持续供电,保证继续等待来电、搜索网络等功能[1]。
GPS、SD 卡、相机、传感器、音频、蓝牙组件的耗电较少,而CPU、屏幕和射频通信(蜂窝话音、蜂窝数据和Wi-Fi)的耗电量较大,需要特别关注[2]。
关于CPU,本文将从计算类型和频率方面进行分析。
1)计算类型
缩短应用程序代码产生指令运行的时间,从而减少应用程序对CPU时间片的占用总时间,进而降低单位时间内目标应用程序在整台手机耗电的百分比。例如:在对算法性能要求高的应用程序中建议减少大整数除法的操作。
2)频率
CPU 在不同频率工作时,需要的电压是不同的,提高电压将直接提高CPU的耗电。电量=电流*时间,CPU工作的同时也会带着显示器等其他耗电组件一起运作,随着时长的增加,单位运算量所耗费的电量也跟着增加。因此,App 运行时提高CPU利用率,减少sleep的使用将能减少耗电量。
手机显示屏主要是LCD和OLED 两个大类:LCD是背光源发光,在背光灯泡亮度一定的情况下,显示不同颜色时耗电是一样的;OLED 是主动发光,显示不同颜色时,每个LED单元的功耗不同,所以整体功耗不同。全白颜色时OLED 耗电是LCD的3 倍,全黑颜色时LCD 耗电是OLED 的10 倍,30%白色时OLED和LCD耗电一样[3]。
对于LCD屏幕,需要重点关注屏幕亮度(0-255)的取值,值越大耗电量越大。一样的图片,最亮亮度下LCD屏幕的耗电量是最暗亮度下的2~3倍[3],所以选择适当的亮度将能给应用程序带来更优的省电效果;对于OLED 屏幕,不但要关注屏幕亮度,还要关注每个像素的RGB值,例如:Super AMOLED 最高亮度时全黑比全白节省电量60%[3],所以应用程序UI界面应多采用深色调。
1)蜂窝数据
蜂窝数据组件在以下三种状态中切换:①高功率状态:在这个状态下蜂窝数据需申请使用专享信道与基站进行通讯。当数据传输活动停止超过一个固定时间阈值时,状态迁移至低功率状态;②低功率状态:在这个状态下蜂窝组件释放了专享信道资源,使用共享公用信道与基站进行信令通讯。这个状态下耗电比高功率状态低20%以上。当传输空闲时间超过特定阈值时,状态迁移至空闲状态;③空闲状态:这个状态下蜂窝组件只接收寻呼信息。这个状态的功耗为高功率状态的六十分之一。
经测试,传输3 秒的数据,蜂窝数据组件需要约16 秒时间才能回到耗电量最低的空闲状态。因此,在蜂窝数据下,采用将大量碎片数据一起打包后,等待较大的时间间隔再进行依次整体传输的策略,将会更省电。
2)Wi-Fi
Wi-Fi 耗电需要关注2个因素:每秒发送和接收的包数(包率)和网速(通道率)。Wi-Fi组件在活动状态下有四种模式:低功率、高功率、低传输、高传输。当Wi-Fi组件从低或高功率状态开始传输数据时,短暂地进入相应的低或高传输状态,发送完毕就回到之前相应的状态。当进行高速传输时,Wi-Fi 组件在高传输状态维持的时间非常短。
综上,用Wi-Fi 传输数据时,在不超过最大传输单元的情况下,增大每个包的大小并降低发包的频率能够节省电量。
为了适应嵌入式设备省电的需要,Linux 操作系统实现了一套休眠和唤醒的机制,Android 沿用了这套机制并有新的改进。Early suspend:一种新的系统状态。显示器被关闭,重力感应器、触摸屏进入挂起状态,但是其他设备继续工作,系统依然进行任务处理,比如扫描SD卡文件。Late resume:一种新的系统操作。唤醒进入Early suspend状态的设备。
当系统进入休眠时,CPU 挂起,所有用户态应用程序和内核态的进程全部被冻结,按照各个设备注册的顺序调用外围设备的suspend 回调函数,执行每个外围设备的suspend,使核心设备和CPU进行休眠,直到系统被某种原因唤醒才会解除。此时系统只有系统时钟RTC(Real-Time Clock)在进行工作。
为了唤醒CPU,终端软件需要设置闹钟alarm。底层系统提供了两种类型的时钟,软时钟Timer 与硬时钟RTC。Android系统提供AlarmManager 这个API,其对应AlarmManagerServie服务程序,该服务程序在系统启动时被启动并初始化闹铃设备,一共有四种Alarm 类型:RTC_WAKEUP、RTC、ELAPSED_REALTIME_WAKEUP、ELAPSED_REALTIME[4]。
在特定使用场景下(例如:资源下载),应用程序需要唤醒系统并且让系统在一段时间内保持唤醒。因此,Android 系统提供了一种锁机制,只要进程持有wake lock,系统就无法进入休眠状态,这将会带来较大的耗电量。所以,关注应用程序中alarm和wake lock的使用是否合理对于节省电量十分重要。
目前,各种应用程序耗电量评估系统主要都是通过CPU利用率来估算每个应用程序所消耗的电量,部分系统也会根据其他硬件设备(屏幕、蜂窝数据、Wi-Fi等)使用时长来修正应用程序的耗电量。
移动终端App 电量测试是为了找出被测试的应用程序中不合理消耗电量的场景,从而进行代码优化,达到省电的目的。省电优化需要尽可能减少对各类资源占用的时间,例如:CPU、网络、屏幕、GPS、相机、SD 卡等。而不合理耗电的场景往往并不是在应用程序前台操作的过程中,而是在后台使用场景。
典型的后台使用场景:当用户按返回键或者HOME 键时,终端应用程序退出UI 界面,UI 主线程会停止运行。如果应用程序进程中不含service,等待3秒后,该进程会进入“缓存进程”列表,点选“显示缓存进程”或者执行PS 命令才能看见这个进程。“缓存进程”与正在运行的进程相比,仅仅是UI主线程是否运行的区别,创建出来的子线程仍然可以继续执行,从而为后台执行逻辑代码进行不合理耗电带来了可能性。以下是需要重点关注的会在后台执行的耗电API 和对象:java.lang.Thread.new()、ThreadPool.addTask()、run()、Handler.sendMessageDelayed()、java.util.Timer、Thread.sleep、AlarmManager。
手机灭屏后进入的休眠状态是移动终端大部分时间所处的状态。timer 和sleep 底层都是通过线程信号量来控制调度的,用的是软时钟的计数器,只统计CPU 运行过的相对时间间隔,所以在手机休眠后,java.util.Timer 和Thread.sleep 会停止。但是,alarm在系统休眠后使用的是RTC的硬时钟闹钟,即可以通过RTC 芯片引脚信号唤醒系统执行逻辑,所以,即使手机休眠了,AlarmManager 还是会持续工作。综上,如果AlarmManager使用不当将会带来终端的极大耗电。
本文将分别通过CPU、屏幕显示和网络三个方面的案例,对典型问题提出电量测试方法和相应优化措施,从而有效地提升App的电量方面的用户体验。
如下为CPU 相关概念,CPU 频率:执行相同周期个数时,CPU主频越快,那么耗时就越短;CPU时间片:每隔N个高电频脉冲,时钟计数器加1,把自然时间分成固定的小块,每一块为一个时间片,手机一般是10ms,单位是jiffies。于是,机器时间都转化为用时间片的个数来衡量;CPU 利用率:指CPU 执行非系统空闲进程的时间除以CPU总的执行时间。
通过CPU 利用率来估算应用程序占移动终端系统耗电的百分比是可行的,但操作应用程序时,CPU 利用率是一段比值波动的曲线,表示进程占用CPU 的百分比,不能定量地评估CPU 的具体消耗,精准度不高。电量=电流*时间,CPU 利用率高手机电流就高,但是不一定电量大,还需要看占用CPU 运行的时间长短。为了精准量化应用程序对CPU的消耗,测试人员需要固定CPU 频率后获取执行操作进程所占用的CPU 时间片总数。具体操作为:
首先,修改/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq的设置来固定CPU频率;然后,电量测试开始前和结束后,通过如下命令获得被测试的应用程序的进程在用户态和系统态消耗的CPU时间片总数:cat/proc/1/stat。此命令会显示进程处于用户态的时间utime 和进程处于内核态的时间stime,单位是jiffies。最后,电量测试开始前和结束后得到的utime+stime总数相减的差值就是被测试应用程序所消耗的CPU时间片。
1)典型案例——CPU频繁轮询耗电的问题分析:①应用程序的线程频繁监听桌面事件:无论移动终端是否关闭屏幕以及应用程序是否正在被使用,线程一直在轮询检测栈顶task(耗时0.2秒),每秒发出2次广播,5个监听者轮流去判断栈顶task 是否为桌面(耗时1 秒);②应用程序的线程频繁检测前台服务是否活着:每5秒进行1次检查。
2)典型案例——CPU频繁轮询耗电的解决办法:①移动终端开启屏幕且不再使用应用程序,应用程序的线程0.5s轮询一次,栈顶发生变化再对外广播;②移动终端开启屏幕且正在使用应用程序,应用程序的线程30s轮询一次,栈顶发生变化再对外广播;③移动终端灭屏的时候停止应用程序的轮询线程。
测试结论:在用户退出应用程序以及移动终端灭屏的时候,停止频繁的轮询能大大减少电量的消耗。
Android系统的动画性能不高,在进行Android应用程序开发时,遵循以下措施将能减少不必要的电量消耗:减少动画的长时间展示;合理控制动画绘制帧率,测试数据表明:动画绘制帧率为每秒40 帧时CPU 占比50%,绘制帧率为每秒10 帧时CPU占比10%,两者的耗电量相差1倍;仅在显示动画的时候才执行渲染逻辑,当移动终端灭屏或动画消失的时候立刻停止渲染逻辑避免不必要的电量消耗;提高动画绘制效率,减少绘制次数和单次耗时;合理选择动画的亮度,因为最亮亮度下LCD屏幕的耗电量是最暗亮度下的2~3 倍;合理使用暗配色动画,因为OLED屏幕为全黑颜色比全白颜色要节电60%。
1)典型案例——播放动画显示耗电的问题分析:①应用程序使用了呼吸动画效果,CPU 占用高达50%;②应用程序动画的绘制帧率高达每秒40 帧;③应用程序只有一套较为耗电的白色系背景。
2)典型案例——播放动画显示耗电的解决办法:①应用程序不使用呼吸动画效果,采用其他样式的动画;②调整应用程序动画的绘制频率为每秒10 帧的时候,足以满足用户的视觉效果需求;③为应用程序增加一套暗色系背景,让用户可以自由选择背景,也能进一步降低应用程序的电量消耗。
通过如上的几种优化方法,应用程序的电量消耗大幅减少。
对于Android 终端要获取服务器上不定时更新的信息,例如:微信用户需要及时获得其他用户发来的信息,通常有两种方法:第一种是客户端使用PULL(拉)的方式,隔一段时间就去服务器上获取信息,看看是否有新的信息;第二种就是服务器使用PUSH(推送)的方式,当服务器端有新信息了,服务器主动把最新的信息Push 到客户端上。相对于PUSH 方式,频繁的PULL 方式会带来客户端的网络流量和电量的过度消耗。所以,绝大部分应用程序都是采用PUSH机制。国内Android App需要实现一套PUSH 服务,一般会使用小于5 分钟的周期发送心跳数据包与后台云服务保持通讯链路[5]。这种方案意味着Android手机每天将从待机省电状态被唤醒近300次,手机每天有15%~20%的电量被消耗在发送过度频繁的心跳上,所以,在待机PUSH机制方面的电量优化考虑是很有必要的。
1)典型案例——待机PUSH 机制耗电的问题分析:①应用程序与后台维持长连接等待接收push 消息;②应用程序使用sleep 方式每隔5分钟发送1个心跳包到后台云服务;③应用程序开启另一个线程每5分钟用alarm保持2分钟wake lock。
2)典型案例——待机PUSH 机制耗电的解决办法:①应用程序与后台维持长连接等待接收push消息;②应用程序每隔5分钟用alarm和wake lock给后台云服务发送心跳包后立即释放wake lock。
PUSH 机制优化前的耗电量明显高于优化后,主要原因在于优化前的方案持有唤醒锁的时间过长。综上,结合项目实际情况,应用程序采用合理的PUSH机制能节省大量电能消耗。
在全民积极拥抱移动互联网的时代,越来越多的用户关注应用程序对电量的消耗。研发团队不断累积优秀开发技术的同时,测试人员也可以从移动终端的硬件和软件系统架构分析出耗电本质,进行可靠的电量测试,并总结出电量优化措施,协助研发团队打造精品应用程序。