Android 性能优化实践小记

说来惭愧,看过那么多 Android 性能优化的文章,但这块实战经验微乎其微。抱着已经看过猪跑了,怎么着也得吃上猪肉的心态,尝试去解决某页面的卡顿。实践下来觉得是一次不错的经验,写文章记录下。

发现卡顿

在解决卡顿之前,你得发现哪里卡顿。这里多亏了 BlockCanary,它是 Android 上一个性能监控组件,能很方便地发现卡顿的原因。
下面是 BlockCanary 显示卡顿信息的截图。

可以看出,卡顿是由于 DateHelper.parseString 而引起的。
那接下来让我们看看 parseString 的具体代码。

1
2
3
4
5
6
7
8
9
public static Date parseString(String dateString) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
try {
return dateFormat.parse(dateString);
} catch (ParseException e) {
return null;
}
}

很简单的一段代码,将规定格式的 String 转换为 Date 对象,简单到貌似没有可以出错及优化的空间。
至此我们陷入了一个死胡同。我们查找到了疑似卡顿的原因,但是无法确认也不知如何优化。

TraceView

这时候,我们就要借助 TraceView 的力量了。TraceView 是一个性能分析的工具,记录了应用程序中每个函数的执行时间。具体使用方法可以参考Android应用开发性能优化完全分析

上图是 TraceView 部分结果的截图。Excl CPU Time 指当前方法(不包含内部调运的子方法)执行占用的CPU时间,这里我们可以看到一个非常显眼的数据 16.6%。由 NativeDecimalFormat.open 向上层层查找调用它的方法,我们可以查找到 SimpleDateFormat.<init>,最终查找到 DateHelper.parseString,终于露出马脚了。从 Calls+Recur Calls/Total 的数值来分析,我们可以看出 DateHelper.parseString 被调用了上百次,而每一次调用都会新建 SimpleDateFormat 对象,使得调用了 NativeDecimalFormat.open 方法,从而造成了卡顿。

解决方法

既然卡顿的原因是因为每次调用都会新建 SimpleDateFormat 对象,那么只要重用对象就可以解决。注意,最好在修改之前先添加一下单元测试,养成一个好习惯。

1
2
3
4
5
6
7
public static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
public static Date parseString(String dateString) {
try {
return DEFAULT_FORMAT.parse(dateString);
} catch (ParseException e) {
return null;
}

之后再通过 TraceView 进行了对比,就发现前几十项中 DateHelper.parseString 的身影消失不见了。

总结

在优化之前,我完全想不到卡顿的原因竟然是这样。因此,运用工具来快速、准确地定位卡顿的原因就显得很关键了,如 BlockCanary、TraceView 等。找准了原因,优化就是水到渠成的事了。

参考