安卓App耗电问题的分析、解决与测评之路(一) --- 影响耗电的各种因素
最近老大来找,说论坛上有人说我们app 耗电量有点高,从而怀疑我们公司的可靠性。这里说明了一个公司的app不单只是一个app这么简单,他也是一个公司的门面,除了保障功能的可用性之外,操作的流畅、界面的优美、功能之稳定等各方面都不能忽视。因为他反应了公司的态度,是用户接触公司的第一眼,所以必须给以百分百的重视。
于是我就开始思考对这块的优化之路。
1 思路
app为什么会耗电?
其实这个问题答起来也很简单。这就相当于潜水员拿着个氧气筒潜入海洋,你的举手投足都在耗费着氧气。一个app运行,就算啥都不干,她在前台的时候也耗费着cpu运行时间,也会亮着屏耗费电量。我们可以这样说,一个app运行的实质,就是在一定时间内占用你手机的资源,无论是CPU资源、流量资源都好,资源用得越多,自然就越耗电。
所以我们怎么来评判一个app是否耗电、如何统计一个app的耗电情况,就是通过收集其耗费资源的所有信息,然后汇总进行分析。
因此,我们有必要先确定一下,一个app的生命周期中,究竟会使用哪些耗电的资源。
2 影响app耗电的各种因素
比较巧,作为一个移动操作系统,Android系统对自身耗电量非常之重视,我们其实不需要自己想破头皮到底有哪些耗费电量的资源会被app所使用,系统源码以及Android提代的开发生态工具都提供了这方面的信息。
2.1 基础概念
我们知道,Android系统里有个耗电量统计的功能,那么从这块入手,是不是就可以大概知道系统是怎么统计一个app的耗电呢。在动手之前,我们需要理清下关于app耗电量统计的基础:
- 手机由众多“部件”组成,所谓“部件”是指:CPU,WIFI,GPS….所以,Android App消耗总电量为:App运行过程中,涉及各部件的消耗电量的总和。
- 各个部件的耗电是不一样的,所以Android系统把他们的平均耗电记录在power_profile.xml配置文件中。这个文件对各个部件的平均耗电作了一个评估,那么进程在这个部件上的耗电就可以简单地用(时间平均耗电)或(总用量平均耗电)来统计。
- 一个app运行是可能是由多个进程组成的,电量统计就是把各个进程对上面说到的部件使用过程中的耗电科学地计算求合。
然后我们进入源码,去看看 Android系统是怎样统计app用电量的。
2.2 源码分析结论
源码中关于电量统计主要由以下几个核心类:
- PowerUsageSummary.java : 计算耗电量
- BatteryStatsImpl :提供App各部件运行时间。
- PowerProfile.java :提供部件电流数值,主要从 power_profile.xml配置文件中(此文件记录了系统各部件平均耗电量,一般作为计算的基数)读取。
下面的统计步骤具体来自 PowerUsageSummary.java 这个类的 processAppUsage() 方法:
系统的耗电统计是以 Uid为单位的,2个App签名和sharedUserId相同,则在运行时,他们拥有相同Uid。就是说processAppUsage统计的可能是多个App的耗电量数据,对于普通App,出现这种情况的几率较少,而对于Android系统应用则较为常见。
系统对耗电的统计可以分以下几个部分,各部分求合就是 app 的总耗电量了
2.2.1 CPU 耗电
计算Uid属下每个Process的耗电量数据,并求和。
1 | Process_Power = (CPUSpeed_Time * POWER_CPU_ACTIVE); |
进程运行总时间 = (进程在Linux内核态运行时间 + 进程在Linux用户态运行时间)
CPU工作总时间 = 软件运行期间CPU每个频率下工作的时间之和比例
进程消耗的电量 = CPU每个频率等级下工作的时间比例/CPU工作总时间 * 进程运行总时间
这里告诉我们,app耗电和其子进程们在CPU上的运行时间密切相关,因此,分开统计每个子进程的耗电,对耗电量非常大的进程或耗电异常的进程代码进行优化是必要的。
2.2.2 wake lock 耗电
计算Uid的wake lock耗电量
1 | power += ( wakelockTime * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE) ) / 1000; |
申请 wakelock的时间乘以申请系统 wake的总耗时。
- wake lock 的概念
Android系统本身为了优化电量的使用, 会在没有操作时进入休眠状态, 来节省电量。但是,我们的app有时可能需要阻止系统进入休眠,比如要后台播放音乐、比如有个通知到来要亮屏提醒用户,这时可以用WakeLock来保持CPU运行, 或是防止屏幕变暗/关闭, 让手机可以在用户不操作时依然可以做一些事儿。
因此, wake lock也会成为系统耗电的一个隐形杀手。对于wake lock 我们要注意以下几个事项:
- 尽量使用 PARTIAL_WAKE_LOCK(附录中附有 wakelock 类型列表)
- wakelock申请后必须释放
- 应用在前台时无需要申请 wakelock
2.2.3 流量 耗电
1 | // 数据耗电 = (网络上行+上行流量总和)*系统流量的平均耗时 |
可见,android 是简单用网络上行与上行流量总和,再乘以系统流量的平均耗时来计算数据流量耗时的,这是一个估量。
我们可以借助工具监控应用的网络数据,防止app由于代码的疏忽耗费了过多的数据流量。
2.2.4 Wi-Fi 耗电
1 | p = (wifiRunningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000; |
计算wifi的耗电与数据流量不同,是用使用wifi模块的占时来计算的。多使用缓存技术可以省这一步的电。
2.2.5 传感器 耗电
1 | p = (multiplier * sensorTime) / 1000; |
这里要说一下就是,GPS模块是个耗电大户,app中如果有使用GPS模块一定要关注这块指标,定位功能要尽量科学地少用,用的时长也别太长。
3 附录
3.1 Android系统中的各种进程
Android中有多种进程:前台进程,可见进程,服务进程,后台进程,空进程。
- 前台进程
这个进程是最重要的,是最后被销毁的。前台进程是目前正在屏幕上显示的进程和一些系统进程,也就是和用户正在交互的进程。例如,我正在使用qq跟别人聊天,在我的Android手机上这个进程就应该是前台进程。
- 可见进程
可见进程指部分程序界面能够被用户看见,却不在前台与用户交互的进程。例如,我们在一个界面上弹出一个对话框(该对话框是一个新的Activity),那么在对话框后面的原界面是可见的,但是并没有与用户进行交互,那么原界面就是可见进程。
- 服务进程
服务进程是通过 startService() 方法启动的进程,但不属于前台进程和可见进程。例如,在后台播放音乐或者在后台下载就是服务进程。
- 后台进程
后台进程指的是目前对用户不可见的进程。例如我正在使用qq和别人聊天,这个时候qq是前台进程,但是当我点击Home键让qq界面消失的时候,这个时候它就转换成了后台进程。当内存不够的时候,可能会将后台进程回收。
- 空进程
空进程指的是在这些进程内部,没有任何东西在运行。保留这种进程的的唯一目的是用作缓存,以缩短该应用下次在其中运行组件所需的启动时间。
它们的回收顺序从先到后分别是:空进程,后台进程,服务进程,可见进程,前台进程。
3.2 wakelock 种类
- PARTIAL_WAKE_LOCK: 保持CPU 运转,屏幕和键盘灯有可能是关闭的。
- SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯
- SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯
- FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度
- ACQUIRE_CAUSES_WAKEUP:正常唤醒锁实际上并不打开照明。相反,一旦打开他们会一直仍然保持(例如来世user的activity)。当获得wakelock,这个标志会使屏幕或/和键盘立即打开。一个典型的使用就是可以立即看到那些对用户重要的通知。
- ON_AFTER_RELEASE:设置了这个标志,当wakelock释放时用户activity计时器会被重置,导致照明持续一段时间。如果你在wacklock条件中循环,这个可以用来减少闪烁
4 小结
我们现在知道了影响一个app耗电的一些主要的相关因为,那么我们怎么统计这些信息?不用忧虑,我之前已经说过,作为一个移动操作系统,Android系统对自身耗电量非常之重视,Android提供了非常好用的开发生态工具为我们提供了这方面的信息,而且随着这些年的发展,大厂们的大神们也为我们铺好了路,有很多这方面的文献资源可以学习,还有很多现成的工具可以拿来使用。我将在后面的和大家一起继续这场耗电优化之旅