在软件开发过程中,想必很多读者都遇到过系统性能问题。而解决系统性能问题的几个主要步骤是:
- 测评:对系统进行大量有针对性的测试,以得到合适的测试数据。
- 分析系统瓶颈:分析测试数据,找到其中的hotspot(热点,即bottleneck)。
- 性能优化:对hotspot相关的代码进行优化。
由上述步骤可知,性能优化的目标对象是hotspot。如果找到的hotspot并非真正的热点,则性能优化的结果必然是事倍功半甚至竹篮打水一场空。所以,作为Android性能调优相关知识的第一部分,本篇首先将向读者介绍Android平台中三个重要的性能测试工具,它们能很好得帮助开发者找到hotspot。
Traceview介绍
1 Traceview简介
Traceview是Android平台特有的数据采集和分析工具,它主要用于分析Android中应用程序的hotspot。Traceview本身只是一个数据分析工具,而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下:
- 开发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数,并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是Java线程)的函数执行情况,并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然后需要利用SDK中的Traceview工具来分析这些数据。
- 借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如图1-1所示。
![](http://static.oschina.net/uploads/img/201306/02214650_5kKP.jpg)
图1-1 DDMS中Traceview使用示意图
点击图1-1中所示按钮即可以采集目标进程的数据。当停止采集时,DDMS会自动触发Traceview工具来浏览采集数据。
下面,我们通过一个示例程序向读者介绍Debug类以及Traceview的使用。
2 Traceview示例分析
示例程序运行时的界面如图1-2所示:
![](https://i-blog.csdnimg.cn/blog_migrate/27b1ea88648ab4d1e4d76e9e04bba716.jpeg)
图1-2 示例界面图
图1-2中:
- SystraceDemoStringAAA等字样是TraceviewDemo程序启动时ListView最初显示的字符串。
- 当用户点击ListView中的某一项时,Traceview将计算对应项当前显示的字符串的MD5值40次,然后用计算得到的MD5字符串替换该项之前显示的内容。其效果如图1-2中的“MD5值“箭头所示。
该示例的关键代码如图1-3所示:
![](http://static.oschina.net/uploads/img/201306/02214657_LG4u.jpg)
图1-3示例代码
由图1-3可知:
- 左图中,Debug类的startMethodTracing和stopMethodTracing分别在MainAcvtivity的构造方法和onDestroy函数中调用。
- onCreate函数中我们设置了第一个hotspot,即getStringToShow函数。它将解析一个XML文件,并将解析后的字符串保存到mListItem中以作为ListView的显示内容。
- 右图中,当用户点击ListView中的某个Item时,程序在onListItem中将计算MD5值40次,然后用计算结果做为被点击项的新字符串显示。generateMD5中的函数是本示例的第二个hotspot。
现在,我们用Traceview工具将测试结果文件TraceviewDemo.trace打开。
Traceview界面比较复杂,其UI划分为上下两个面板,即Timeline Panel(时间线面板)和Profile Panel(分析面板)。图1-4所示为Timeline Panel界面:
![](http://static.oschina.net/uploads/img/201306/02214706_Cv4q.jpg)
图1-4 Traceview Timeline Panel示意图
图1-4中的Timeline Panel又可细分为左右两个Pane:
- 左边Pane显示的是测试数据中所采集的线程信息。由图1-4可知,本次测试数据采集了main线程,两个Binder线程和其它系统辅助线程(例如GC线程等)的信息。
- 右边Pane所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图1-4可知,main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
- 另外,开发者可以在时间线Pane中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
现在来看Traceview的Profile Panel界面,如图1-5所示:
![](http://static.oschina.net/uploads/img/201306/02214714_7gyc.jpg)
图1-5 TraceviewProfile Panel界面
Profile Panel是Traceview的核心界面,其内涵非常丰富。它主要展示了某个线程(先在Timeline Panel中选择线程)中各个函数调用的情况,包括CPU使用时间、调用次数等信息。而这些信息正是查找hotspot的关键依据。所以,对开发者而言,一定要了解Profile Panel中各列的含义。笔者总结了其中几个重要列的作用,如表1-1所示:
表1-1 Profile Panel各列作用说明
列名 | 描述 |
Name | 该线程运行过程中所调用的函数名 |
Incl Cpu Time | 某函数占用的CPU时间,包含内部调用其它函数的CPU时间 |
Excl Cpu Time | 某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间 |
Incl Real Time | 某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间 |
Excl Real Time | 某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间 |
Call+Recur Calls/Total | 某函数被调用次数以及递归调用占总调用次数的百分比 |
Cpu Time/Call | 某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间 |
Real Time/Call | 同CPU Time/Call类似,只不过统计单位换成了真实时间 |
另外,每一个Time列还对应有一个用时间百分比来统计的列(如Incl Cpu Time列对应还有一个列名为Incl Cpu Time %的列,表示以时间百分比来统计的Incl Cpu Time)。
了解完Traceview的UI后,现在介绍如何利用Traceview来查找hotspot。
一般而言,hotspot包括两种类型的函数:
- 一类是调用次数不多,但每次调用却需要花费很长时间的函数。在示例代码中,它就是hotspot 1。
- 一类是那些自身占用时间不长,但调用却非常频繁的函数。在示例代码中,它就是hotspot 2。
首先,我们来查找hotspot 1。
在Profile Panel中,选择按Cpu Time/Call进行降序排序(从上之下排列,每项的耗费时间由高到低),得到如图1-6所示的结果:
![](https://i-blog.csdnimg.cn/blog_migrate/27192ae7ebeac11fba747d4c3b4a7adb.jpeg)
图1-6 按CPU Time/Call降序排列数据
图1-6中:
- MainActivity.onCreate是应用程序中的函数,它耗时为4618.684。然后,点击MainActivity.onCreate项,得到箭头所示的小图。
- 小图中,Parents一行显示的是MainActivity.onCreate的调用者,本例中它是performCreate函数。这部分代码属于Framework部分。Children行显示的是MainActivity.onCreate调用的子函数。
- 在MainActivity.onCreate调用的子函数中,我们发现getStringsToShow在Incl Cpu Time %一列中占据了63.3%,它是onCreate子函数耗费时间最长的,而且Calls+Recur Calls/Total列显示其调用次数为1,即它仅仅被调用一次了。这个函数是应用程序实现的,所以极有可能是一个潜在的Hotspot。
- 另外,由于笔者已经知道getStringsToShow是示例应用自己实现的函数,故在图1-6的大图中,可直接根据MainActivity.getStringsToShow花费了2921.913CPU时间这个信息来确定Hotspot就是它。
相对来说,类型1的hotspot比较好找,步骤是先按降序对时间项进行排列(可以是时间百分比、真实时间或CPU时间),然后查找耗费时间最多的函数。一般而言,先应对应用程序自己实现的函数进行排查,Framework的函数也有可能是hotspot,但主因一般还是在应用本身(例如设置复杂的界面,导致对应XML解析非常慢)。
现在,我们来看如何查找类型2的hotspot。
点击Call/Recur Calls/Total列头,使之按降序排列。关注点放在那些调用频繁并且占用资源较多的函数。图1-7为降序排列的结果图。
![](https://i-blog.csdnimg.cn/blog_migrate/ee06ac06bebd7d085ec3522c588d50a2.jpeg)
图1-7类型2 Hotspot查找过程示意之一
图1-7所示的运行最频繁的几个函数中,我们发现了几个怀疑点,由图中的1和2箭头标示。
- 结合代码,箭头1所指的函数在代码中实际并不存在。这是因为代码中直接访问了内部类的私有成员,导致java编译器在编译时自动生成了这个函数。这个函数的调用次数非常多。所以,为了提高效率,我们可以修改内部类成员的访问类型定义为public。不过,该函数的Incl Cpu Time并不高,只有3.2%。
- 同样,箭头2所指部分的函数调用次数也很多,达到了5888多次。不过它们占用的时间百分比只有0.9%。
第一次查找的潜在点被排除后,继续浏览数据,得到如图1-8所示的结果。
![](https://i-blog.csdnimg.cn/blog_migrate/78ec7ea5e3e984ac4cd3aae78b23fea3.jpeg)
图1-8 类型2 Hotspot查找过程示意之二
在图1-8中:
- 红框处有两个重载的MyMD5.getHashString函数调用,它们各运行了368次,而且占用的CPU时间百分比达到了31.8%和53.2%。很显然,这2处调用就有优化的余地,这就是我们所怀疑的hotspot2。
找到hotspot之后,开发者就需要结合代码来进行对应的优化了。关于Java代码优化,读者可参考如下资料:http://developer.android.com/training/articles/perf-tips.html
总体而言,Hotspot的查找是一个细致的工作,需要开发者对目标程序的代码,以及Traceview工具都比较熟悉才行。
3 Traceview小结
Traceview工具是Android平台应用程序性能分析的利器。不过笔者觉得它的UI还是有些复杂。并且使用时感觉流畅度不够好。
Google官方关于Traceview的介绍可参考以下链接,不过其内容以及较久未更新了。http://developer.android.com/tools/debugging/debugging-tracing.html。
转载网址:http://my.oschina.net/innost/blog/135174?fromerr=OdP2WTtX