Android性能优化-功耗问题分析

摘要:项目中功耗问题查找。

1.功耗问题事件

  这些天测试那边进行亮屏测试发现待机机器时间非常短,于是悲催的被安排来查查什么问题,不查不知道,一查还真有挺多让人f*k的问题:
   1、下载任务
    下载状态错误,一直在循环尝试下载,由于下载任务获取了wakelock锁,一是无法休眠,二是耗CPU;
   2、上传任务
    无网络状态下上传失败,也进行多次重试,由于数据采集通过上传任务完成,因此很容易触发上传,每次CPU占用率都到30%多;
   3、推送任务
    用了极光和云巴的推送,但是没哪一家确保能百分百推送成功,于是应用程序员每隔10秒主动去拉取数据,囧rz;
   4、应用商城
    5分钟主动拉取一次数据,也不知道干嘛的,关键是没网络也干这事;
   5、OTAUpdate
    系统OTA模块无网络状况下一开机循环几十次http请求,还定时发送http请求;
   6、超级省电
    正常情况一晚耗20%的电,回家测试一晚超级省电,居然没电了。回公司一查mediaserver CPU占用率最高到50%,原因是systemui在切换到超级省电时开启了camera。
   7、桌面
    桌面图标有一个图标,显示一个小时钟,每秒会刷新一次时间(刷屏),导致桌面应用的CPU占用率长期为1%。这个本来影响不大,不过拿亮屏时的桌面测试,自然不让过。
   8、硬件问题
    测试时使用了研发样机,触摸屏中断一直有,导致CPU占用率一直为10%,工程样机已改。

  总结一下这些问题最终导致的都是CPU功耗高,待机时间自然就短了。说白了耗电实质还是在硬件设备的运转,如显示屏、CPU、GPU、WIFI、Codec等。如何降低功耗就是如何降低硬件功耗,由于软件控制硬件,硬件执行软件指定的行为,因此如何降低功耗就成了如何让软件尽量少处理任务。
  以查询到的几个问题为例,无一不是因为程序流程不当导致CPU、WIFI等硬件一直处于高负载运行状态中。对于以上问题我们有必要好好考虑执行这些功能的条件与前提,例如网络是否可用、是否需要实时更新,然后再选择一种适合的方式。当然如果是BUG导致的,那也只能慢慢查了。

2.为什么要清理后台?

  之前和同事讨论时同事说根据安卓机制应用切换到后台时会被pause,然后就不会耗CPU了。我只想说图样图森破了,如果真那么简单就好了,要不OPPO搞什么纯净后台。功耗不只是我们能看到界面所在进程,如果应用通过定时器定时执行某段代码,那么必然需要CPU处理,那功耗问题就来了。当某个后台进程设置了类型为RTC_WAKEUP或ELAPSED_REALTIME_WAKEUP的alarm,那么AlarmManagerService中会执行WakeLock.acuqire()将CPU从深度休眠时中唤醒。如果定时器唤醒的很频繁,那么将带来极大的电量损耗。
  现在很多APP根本不考虑手机的功耗问题,只要自己的应用能在机器中活着就行,在被kill掉后又通过各种方式复活,为解决这个问题需要引入新的进程启动控制管理机制,纯净后台是个不错的想法。

3.如何查找功耗问题

  在上面给出了导致功耗过高的原因,那么怎么才能检测出应用功耗过高呢?网上有BetterBatteryStats、Bettray Monitor Widget Pro等,然而作为技术流自然喜欢自己倒腾,一是兴趣,二也是有助于自己提高理解。

  首先从网络访问来说,我不希望修改系统源码再进行测试,于是通过hook机制,将http请求的地方全部记录下来进行统计分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void hookHttpUrlConnection() {
XC_MethodHook.Unhook unhook = XposedHelpers.findAndHookMethod(URL.class, "openConnection", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(
MethodHookParam param)
throws Throwable {
URL url = (URL) param.thisObject;
Log.e("NetWorkHook","AllTester NetworkTester URL openConnection,Package:"+AndroidAppHelper.currentPackageName() + " TIME:"+System.currentTimeMillis()+"ms"+ " URL:"+url.toString());
}
});
if (unhook != null) {
mUnhookList.add(unhook);
}
}
private void hookHttpClient() {
XC_MethodHook.Unhook unhook = XposedHelpers.findAndHookMethod(DefaultRequestDirector.class, "execute",
HttpHost.class, HttpRequest.class,HttpContext.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(
MethodHookParam param)
throws Throwable {
HttpRequest request = (HttpRequest) param.args[1];
Log.e("NetWorkHook","AllTester NetworkTester DefaultRequestDirector execute,Package:"+AndroidAppHelper.currentPackageName()+" TIME:"+System.currentTimeMillis()+"ms"+ " URL:"+request.getRequestLine().getUri());
}
});
if (unhook != null) {
mUnhookList.add(unhook);
}
}

  其次对定时器进行统计,大致流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void hookActivityManagerServicesetWakelockWorkSource() {
Class<?> hookClass = null;
try {
hookClass = Class.forName("com.android.server.AlarmManagerService", false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (hookClass == null) {
Log.e("AlarmTester","AllTester AlarmTester WAKELOCK NULL END HOOK");
return;
}
XC_MethodHook.Unhook unhook = HookUtils.hookLongestMethod(hookClass, "setWakelockWorkSource",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(
MethodHookParam param)
throws Throwable {
PendingIntent pendingIntent = (PendingIntent) param.args[0];
int type = (Integer) param.args[2];
String tag = (String)param.args[3];
boolean first = (Boolean)param.args[4];
if (pendingIntent != null) {
Log.e("AlarmTester","AllTester AlarmTester WAKELOCK alarm setWakelockWorkSource,Package:"+pendingIntent.getTargetPackage() +
" type:"+AlarmMessageWhat.codeToString(type)+" tag:"+tag+" first:"+first+" " + getCurrentTimeDesc());
}
}
});
if (unhook != null) {
mUnhookList.add(unhook);
}
}

  接下想法是对CPU使用率进行采样,基本想法如手动方式是adb shell top一样,定时获取当前采样的进程CPU使用率,然后进行统计。另外android已经帮我们统计了电量信息,通过dumpsys batterystats可以查看,是否可以借鉴,需要后面实现自动化检测工具时再进行分析。

4.总结

  这里分析了引起功耗高的各软件因素以及功耗的根本原因,同时查出了很多隐藏的问题,通过这些问题的解决很好的验证了降低功耗的方法。不足的是这些问题的查找都是通过查看打印日志手动分析,如何构建一个自动化分析的工具才是终极解决方式。