概念
什么是耗电优化?
有些同学可能会疑惑,所谓的耗电优化不就是减少应用的耗电,增加用户的续航时间吗?但是落到实践中,如果我们的应用需要播放视频、需要获取 GPS 信息、需要拍照,这些耗电看起来是无法避免的。
如何判断哪些耗电是可以避免,或者是需要去优化的呢?你可以看下面这张图,当用户去看耗电排行榜的时候,发现“王者荣耀”使用了 7 个多小时,这时用户对“王者荣耀”的耗电是有预期的。

假设这个时候发现某个应用他根本没怎么使用(前台时间很少),但是耗电却非常多。这种情况会跟用户的预期差别很大,他可能就会想去投诉。
所以耗电优化的第一个方向是优化应用的后台耗电
知道了系统是如何计算耗电的,那反过来看,我们也就可以知道应用在后台不应该做什么,例如长时间获取 WakeLock、WiFi 和蓝牙的扫描等。为什么说耗电优化第一个方向就是优化应用后台耗电,因为大部分厂商预装项目要求最严格的正是应用后台待机耗电

当然前台耗电我们不会完全不管,但是标准会放松很多。你再来看看下面这张图,如果系统对你的应用弹出这个对话框,可能对于微信来说,用户还可以忍受,但是对其他大多数的应用来说,可能很多用户就直接把你加入到后台限制的名单中了

耗电优化的第二个方向是符合系统的规则,让系统认为你耗电是正常的。
而 Android P 是通过 Android Vitals 监控后台耗电,所以我们需要符合 Android Vitals 的规则,目前它的具体规则如下:

虽然上面的标准可能随时会改变,但是可以看到,Android 系统目前比较关心后台 Alarm 唤醒、后台网络、后台 WiFi 扫描以及部分长时间 WakeLock 阻止系统后台休眠。
1.耗电监控
对于耗电监控也是如此,我们首先需要抽象出具体的规则,然后收集尽量多的辅助信息,帮助问题的排查。
1. Android Vitals
我们先复习一下Android Vitals 的几个关于电量的监控方案与规则。
在使用了一段时间之后,我发现它并不是那么好用。以 Alarm wakeup 为例,Vitals 以每小时超过 10 次作为规则。由于这个规则无法做修改,很多时候我们可能希望针对不同的系统版本做更加细致的区分。
其次跟 Battery Historian 一样,我们只能拿到 wakeup 的标记的组件,拿不到申请的堆栈,也拿不到当时手机是否在充电、剩余电量等信息。

对于网络、WiFi scans 以及 WakeLock 也是如此。虽然 Vitals 帮助我们缩小了排查的范围,但是依然需要在茫茫的代码中寻找对应的可疑代码。
2. 耗电监控都监控什么
Android Vitals并不是那么好用,而且对于国内的应用来说其实也根本无法使用。不管怎样,我们还是需要搭建自己的耗电监控系统。
那我们的耗电监控系统应该监控哪些内容,怎么样才能比 Android Vitals 做得更好呢?
-
监控信息。简单来说系统关心什么,我们就监控什么,而且应该以后台耗电监控为主。类似 Alarm wakeup、WakeLock、WiFi scans、Network 都是必须的,其他的可以根据应用的实际情况。如果是地图应用,后台获取 GPS 是被允许的;如果是计步器应用,后台获取 Sensor 也没有太大问题。
-
现场信息。监控系统希望可以获得完整的堆栈信息,比如哪一行代码发起了 WiFi scans、哪一行代码申请了 WakeLock 等。还有当时手机是否在充电、手机的电量水平、应用前台和后台时间、CPU 状态等一些信息也可以帮助我们排查某些问题。
-
提炼规则。最后我们需要将监控的内容抽象成规则,当然不同应用监控的事项或者参数都不太一样。 由于每个应用的具体情况都不太一样,下面是一些可以用来参考的简单规则。

在安卓绿色联盟的会议中,华为公开过他们后台资源使用的“红线”,你也可以参考里面的一些规则:

3. 如何监控耗电
明确了我们需要监控什么以及具体的规则之后,终于可以来到实现这个环节了。跟 I/O 监控、网络监控一样,我首先想到的还是 Hook 方案
Java Hook
Hook 方案的好处在于使用者接入非常简单,不需要去修改自己的代码。下面我以几个比较常用的规则为例,看看如果使用 Java Hook 达到监控的目的。
- WakeLock。WakeLock 用来阻止 CPU、屏幕甚至是键盘的休眠。类似 Alarm、JobService 也会申请 WakeLock 来完成后台 CPU 操作。WakeLock 的核心控制代码都在PowerManagerService中,实现的方法非常简单。
// 代理 PowerManagerService
ProxyHook().proxyHook(context.getSystemService(Context.POWER_SERVICE), "mService", this);
@Override
public void beforeInvoke(Method method, Object[] args) {
// 申请 Wakelock
if (method.getName().equals("acquireWakeLock")) {
if (isAppBackground()) {
// 应用后台逻辑,获取应用堆栈等等
} else {
// 应用前台逻辑,获取应用堆栈等等
}
// 释放 Wakelock
} else if (method.getName().equals("releaseWakeLock")) {
// 释放的逻辑
}
}
- Alarm。Alarm 用来做一些定时的重复任务,它一共有四个类型,其中ELAPSED_REALTIME_WAKEUP和RTC_WAKEUP类型都会唤醒设备。同样,Alarm 的核心控制逻辑都在AlarmManagerService中,实现如下:
// 代理 AlarmManagerService
new ProxyHook().proxyHook(context.getSystemService
(Context.ALARM_SERVICE), "mService", this);
public void beforeInvoke(Method method, Object[] args) {
// 设置 Alarm
if (method.getName().equals("set")) {
// 不同版本参数类型的适配,获取应用堆栈等等
// 清除 Alarm
} else if (method.getName().equals("remove")) {
// 清除的逻辑
}
}
- 其他。对于后台 CPU,我们可以使用卡顿监控相关的方法。对于后台网络,同样我们可以通过网络监控相关的方法。对于 GPS 监控,我们可以通过 Hook 代理LOCATION_SERVICE。对于 Sensor,我们通过 Hook SENSOR_SERVICE中的“mSensorListeners”,可以拿到部分信息。
通过 Hook,我们可以在申请资源的时候将堆栈信息保存起来。当我们触发某个规则上报问题的时候,可以将收集到的堆栈信息、电池是否充电、CPU 信息、应用前后台时间等辅助信息也一起带上。
插桩
虽然使用 Hook 非常简单,但是某些规则可能不太容易找到合适的 Hook 点。而且在 Android P 之后,很多的 Hook 点都不支持了。
出于兼容性考虑,我首先想到的是写一个基础类,然后在统一的调用接口中增加监控逻辑。以 WakeLock 为例:
public class WakelockMetrics {
// Wakelock 申请
public void acquire(PowerManager.WakeLock wakelock) {
wakeLock.acquire();
// 在这里增加 Wakelock 申请监控逻辑
}
// Wakelock 释放
public void release(PowerManager.WakeLock wakelock, int flags) {
wakelock.release();
// 在这里增加 Wakelock 释放监控逻辑
}
}
Facebook 也有一个耗电监控的开源库Battery-Metrics,它监控的数据非常全,包括 Alarm、WakeLock、Camera、CPU、Network 等,而且也有收集电量充电状态、电量水平等信息。
Battery-Metrics 只是提供了一系列的基础类,在实际使用中,接入者可能需要修改大量的源码。但对于一些第三方 SDK 或者后续增加的代码,我们可能就不太能保证可以监控到了。这些场景也就无法监控了,所以 Facebook 内部是使用插桩来动态替换。
遗憾的是,Facebook 并没有开源它们内部的插桩具体实现方案。不过这实现起来其实并不困难,事实上在我们前面的 Sample 中,已经使用过 ASM、Aspectj 这两种插桩方案了。后面我也安排单独一期内容来讲不同插桩方案的实现。
插桩方案使用起来兼容性非常好,并且使用者也没有太大的接入成本。但是它并不是完美无缺的,对于系统的代码插桩方案是无法替换的,例如 JobService 申请 PARTIAL_WAKE_LOCK 的场景。
总结
从 Android 系统计算耗电的方法,我们知道了需要关注哪些模块的耗电。从 Android 耗电优化的演进历程,我们知道了 Android 在耗电优化的一些方向以及在意的点。从 Android Vitals 的耗电监控,我们知道了耗电优化的监控方式。
但是系统的方法不一定可以完全适合我们的应用,还是需要通过进一步阅读源码、思考,沉淀出一套我们自己的优化实践方案。这也是我的性能优化方法论,在其他的领域也是如此。
版权声明:本文为CSDN博主「yueshao6800」的原创文章,转载附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq980106800/article/details/87811924
如果对您有帮助,请给我点个关注,每天分享知识干货,共同进步!

网友评论