正确姿势使用TraceView工具
正确姿势使用TraceView工具 在对手机应用性能分析和定位的过程中Traceview是使用最多的一个工具,在遇到启动时间长界面切换时间长特别卡顿的时候Traceview是首选工具。如果查看界面的帧率问题建议还是先使用GPU配置文件以列表的形式展示在屏幕上这样可以首先发现这个界面的帧率是否有问题再做后续的排查。如何开启TraceView ...
正确姿势使用TraceView工具
在对手机应用性能分析和定位的过程中Traceview是使用最多的一个工具,在遇到启动时间长界面切换时间长特别卡顿的时候Traceview是首选工具。如果查看界面的帧率问题建议还是先使用GPU配置文件以列表的形式展示在屏幕上这样可以首先发现这个界面的帧率是否有问题再做后续的排查。
如何开启TraceView
Traceview是Android平台特有的数据采集和分析工具它主要用于分析Android中应用程序的性能问题。Traceview本身只是一个数据分析工具而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下:
(1) 通过代码开启:
android.os.Debug.startMethodTracing();
...
android.os.Debug.stopMethodTracing();
就是使用如上代码方法,当运行了这段代码的时候,就会有一个trace文件在/sdcard目录中生成,也可以调用startMethodTracing(String traceName) 设置trace文件的文件名,最后你可以使用adb pull /sdcard/test.trace /tmp 命令将trace文件复制到你的电脑中,然后用DDMS工具打开OK了。
(2)通过DDMS工具
借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言此方法适用于没有目标应用源代码的情况(且必须应用源代码没有设置android:debuggable=“false”,否则也是望洋兴叹,无能为力了,可以修改固件另外一说 啊)。DDMS工具中Traceview的使用如图所示,主要是使用"Start Method Profiling"按钮进行相关的操作:
在做性能分析的过程中基本都是采用DDMS工具中来启动TraceView这样简单易用随便哪个地方的代码都可以跟踪。在对android 4.4以上手机点击TraceView按钮的时候会出现2种方式的选择对话框如下所示:
(1)、Sample based profiling以固定的频率像VM发送中断并搜集调用栈信息。低版本手机也是采用该方式来采集样本的默认是1毫秒采集一次。精确度和采集的频率有关间隔频率越小会越精确但运行也会相应的更慢。
(2)、Trace based profiling不论多小的函数都会跟踪整个函数的执行过程所以开销也会很大。运行起来会非常的慢不适合检测滑动性能。
一般情况用第一种默认1000微妙的间隔就足够了。
另外在Eclipse中或者Android Studio中启动的DDMS中的这个工具搜索功能不能使用如果要使用搜索功能可以通过Android SDK tools下的命令行来启动这样就可以搜索了。
TraceView面板介绍
通过前面的篇章,我想读者朋友们一定对TraceView的功能和启动方式有了一定的了解,那么下面我们来介绍一下其面板和及其功能,Traceview其UI划分为上下两个面板即Timeline Panel和Profile Panel,下面分别进行介绍:
Timeline Panel:左边是测试数据中所采集的线程信息右边Pane所示为时间线时间线上是每个线程测试时间段内所涉及的函数调用信息。内容的丰富代表该时间段执行的函数多从而可以反应线程的繁忙状态。也可以看出线程的启动时间和结束时间等。
Profile Panel是Traceview的核心界面其内涵非常丰富。它主要展示了某个线程先在Timeline Panel中选择线程中各个函数调用的情况包括CPU使用时间、调用次数等信息。而这些信息正是查找性能瓶颈的关键依据。
另外开发者可以在时间线Pane中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
另个面板之间也是互相联动的点击下面的函数可以在时间轴上显示对应的位置。如上图。点击时间线上的函数位置则可以展开对应Profile Panel的函数行数。在时间线上拉伸可以放大时间线双击顶部的时间条区域可以缩小会原始状态。
点一个方法后可以看到有两部分,一个是Parents,另一个是Children。
-
Parent表示调用这个方法的方法,可以叫做父方法
-
Children表示这个方法中调用的其他方法,可以叫做子方法
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类似只不过统计单位换成了真实时间 |
TraceView实际案例分析
了解完Traceview的基本信息后现在介绍如何利用Traceview来查找性能问题。让我们来浪一把,分析分析各种问题。
1、直接查看帧率和渲染情况
如上面的TraceView图面板所示显示了Draw函数的执行情况。在时间线上可以看到前面部分间隔平滑且时间比较短到中间部分开始就开始中断执行时间也明显拉长。说明出现了丢帧等情况通过放大时间线可以查看执行时间较长的draw函数中每一个函数的执行情况从而发现问题。
2、了解函数前后的路径和执行情况
Profile Panel面板的函数有Parents和Chindren对于部分有递归调用的函数还会有Parents while recursive和Chindren while recursive。通过点击Parents和Chindren的各个函数用于跟踪性能问题也能了解这个函数的来龙去脉。以及几个Parents调用了该函数每个Parents调用的次数。该函数自己的执行时间以及各个Chindren和他们的执行时间以及本函数Chindren中用到的某个函数A占据所有调用该函数A的分布比例情况。
3、找出函数所在的线程分布
Profile Panel面板的函数点击后在对应上面部分函数时间线上会有相应的指示如下面的括号上下的颜色标记符号该颜色标记符号和Profile Panel面板中的该函数前面的方块颜色对应。如下图Logger.isLoggable函数点击后从时间线上看到了在main线程和Thread-3191线程中的分布情况。
4、CPU定位高负荷函数
通过Incl Cpu Time排序就可以轻松发现cpu被哪些函数占用了。
)
5、查找主线程耗时
通过Incl Cpu Time排序可以找到相对耗时的函数在函数排序的面板中选中该函数如果在Main线程中底部某区间出现了括号则表示该线程这段时间执行了该函数。这样就可以找出主线程的耗时函数了。同理也可以查找某函数在各个线程中的分布情况。
6、查看部分GC原因和位置
因为安卓2.3以后GC并不会每次都停止其他线程因此只能跟踪到部分停止所有线程的GC情况。一般出现GC的时候时间线上会有比较大块的同颜色的区域点击后就可以定位到函数面板区域的GC函数一步一步向parent函数追踪就可以定位到GC的起因了。如下图的绿色部分主线程在加载资源图的时候发生了GC。
7、动画或者滑动过程是否触发Layout
动画和滑动过程中在控件调用gone或者动态添加删除重新设置paramsTextView重新设置文字以及重新设置Drawable的时候都会触发Layout。在ListView的getview过程中它自己阻断了这个requestlayout自己对子控件做了layout的操作所以不会引起整个界面的重新布局。但是如果在其他时间设置了图片、文字等就可能导致requestlayout被触发进而执行onMeasure过程和onLayout过程这样的话就会大大影响了滑动过程中的性能容易造成卡顿。在滑动过程中或者有动画的情况下做TraceView跟踪可以发现是否被触发了重新布局。在跟踪结束中搜索onLayout或者layout或者requestlayout可以方便找到对应的控件。
8、找出较小的耗时函数
前面按照Incl Cpu Time排序一下就可以找到较大的性能问题函数但是小的耗时函数就不是通过这种方式来找了。我们把Call+Recur Calls/Total和Cpu Time/Call放到最前面按照Cpu Time/Call排序找出平均执行时间久的函数展开其子函数分析是否存在问题并通过调用次数看严重的程度。如下图我们发现社区界面在滑动过程中的TBS提交埋点函数耗时过久进一步跟踪发现是Hashmap多余的putall操作。
9、查找高频率调用存在性能的点
我们把Call+Recur Calls/Total和Cpu Time/Call放到最前面按照Call+Recur Calls/Total排序查看执行次数多的隐患函数展开其子函数分析是否存在问题并通过Incl Cpu Time 的CPU占用比以及Incl Cpu Time 的占用百分比来判断严重性特别是调用次数多的且Cpu Time/Call次数也多的应该重点排查。通过这样我们就能找到高频率调用函数的性能问题点。我们发现一个简单的函数但是调用次数太多后导致了相对的耗时且这里只要用到一个宽度只要第一次获取后保存该值不需要每次从系统函数中去取这样就解决了。
但是有时候判断一个函数是否严重还是需要对系统的了解。比如SharedPreferences的apply函数较高频率调用但是其CPU和单次时间都不会占用多少但是这确是一个性能影响点因为直接commit有阻塞的IO操作apply函数调用后进程中有专门一个SharedPreferences的写线程会处理写入操作而这个写线程此时可能会很耗时。反过来如果看到SharedPreferencesImpl&*run线程占用较高cpu的时候就可以推断出较多的SharedPreferences的操作了我们应该通过搜索把apply的调用出都找出来。
10、查看布局性能问题
通过Incl Cpu Time百分比排序列表滑动过程中如果看到onMeasure或者onLayout大于25%以上的就应该可以判断出当前这个界面的布局性能不佳需要优化了。
在列表滑动过程中也需要检查getview这样的函数的性能特别是布局复杂的初始化时间会比较久。
11、查看布局复用问题
在列表滑动的过程中或者广告Banner控件一般的做法都是应该复用布局提升性能的但有时候因为觉得麻烦有些可能是动态添加的就没有复用这些view导致在滑动过程中还是会出现infalte布局的情况影响性能。跟踪方法是在这个列表已经滑动过的情况下开始进行TraceView这个时候来回滑动不应该出现infalte如果出现了就是复用出现了问题。下图中我们对“我的订单”界面做了跟踪发现有动态inflate button导致每次都额外增加了时间影响性能。
还有一种判断方法就是在进入界面的时候找出LayoutInflater.createViewFromTag函数找出它数量以及parents调用方检查是否有问题。
12、判断布局嵌套过多或者过于复杂
我们把Call+Recur Calls/Total和Cpu Time/Call放到最前面通过View/ViewGroup的draw调用次数和递归调用次数来判断布局的层级过多或者布局Layout太多。也可以通过buildDisplayList函数的调用和递归调用次数来判断布局的层级过多或者是Layout太多。
13、查类的初始化性能
我们把Call+Recur Calls/Total和Cpu Time/Call放到最前面通过Cpu Time/Call排序找到一些类的构造函数判断类的初始化性能。类的初始化过程如果太久特别是在主线程中会影响性能而这个又是一个容易忽略的问题因为类的初始化过程可以简单也可以复杂复杂的可以做懒加载来优化。如果调用次数多的那就更应该优化或者做复用。
14、排查字符串问题
把Call+Recur Calls/Total放到前面在搜索字符串相关的一些StringBuiler类或者StringBuffer类还有append方法以及enlarge方法来查看当前的字符串问题。找到调用方去掉不必要的字符串拼接和扩容来提升性能。
15、未开启硬件加速
检查绘制函数如果发现是drawSoftware那就是未开启硬件加速影响了帧率。
16、排查集成的问题
有时候集成需要多个包可能会漏掉其中一个这一个时候通过TraceView调用分析自己的某个函数但是和自己的预期不一样明明已经改过了为什么还会这样这个时候可能就是打包的时候没有引用到正确的包。
17、排查自己写的函数是否符合预期
有时候自己写的函数如字符串问题会被编译器做一些优化或者不太注意用了很多+号导致了很多StringBuilder对象的分配这个时候通过TraceView我们可以发现在该函数下创建了多少个StringBuilder和以及扩容的问题。通过调用次数来判断对性能的影响如果是频率比较多的函数就应该去优化这些问题。
18、发现可复用对象
在对一些频率较高的函数的子函数分析过程中我们可以去看是否每次这个函数调用的时候都会去创建这些对象如果是那可以考虑一下是否可以对这些对象做复用。如下图发现这个重入锁对象应该做复用。
19、判断主线程长时间等待原因
时间线上主线程长时间空白可能是受其他因素的影响比如安全软件对IO的监控用了锁等待某个资源或者CPU太忙了没有时间片来分配。下面第一张图是因为安全软件对IO的监控用了锁等待某个资源导致主线程执行性能问题第二张图则是由于其他现场太多太忙了导致主线程CPU分配不到时间片。
20、灵活运用时间线找出根源
在安卓代码中我们不建议主动调用System.gc方法来触发GC但是在检测首页滑动过程中LogCat中还是定时出现了GC_EXPLICIT的垃圾回收信息。通过启用TraceView的跟踪发现了调用System.gc的函数位置但是向上跟踪后最终只能跟踪到一个线程池的run具体这个线程的run由谁调用没法继续跟踪了。这样只能通过时间线上再去找问题通过鼠标放在时间线上从后到前简单扫描了一下时间线并未发现和taobao,ali等包名的函数为了继续排查只能放大时间线来发现线索通过放大时间线面板调用函数也会变得越来越细腻最终在调用gc前面部分位置找到了com.alibaba.mobileiim.channel.http.httpwebTokenCallback的函数调用从而定位到问题所在。
21、了解一些函数的性能问题如字符串函数格式化函数等
通过占用cpu百分比调用次数平均调用时间可以观察到一些系统类实现的函数有性能问题在高频率下不应该调用。
22、如果你对JAVA相当熟悉甚至可以通过这个软件发现一些代码上的问题
在分析一个高频率函数的时候发现该函数包装了一个subString方法但是子函数中却多了一个String类的创建。待着问题查看了实现代码发现该函数确实实现有问题。String是不可变对象source.substring函数本身就会返回指定的sub字符串内部会new一个string外部不需要再new string这样多了一次对象的分配。
查看该文件的这个函数
public static String substring(String source, int start, int end) {
if (end <= 0) {
new String(source.substring(start));
}
new String(source.substring(start, end));
}
23、一定不要忘了在各个界面的静默状态做跟踪特别是有动画的界面
在有广告条轮播等动画的界面尤其要注意像首页、社区等界面都发现了在广告条移出屏幕外的时候还有定时刷新广告的问题这个会影响性能和耗电。有时候看代码已经用Handler的removeCallbacks(this)接口移除了但队列中可能还有其他的定时实例会重新启动这个定时removeCallbacks只是在队列中移除了这个实例相关的消息。替换成removeCallbacksAndMessages(null)函数移除全部等候执行的消息后才解决了该问题。
TraceView也是一个在界面切换到后台被其他程序覆盖等情况下检查程序中仍再运行的线程等问题的首选工具。
以上是常用的TraceView性能跟踪的一些方法,当然随着使用的娴熟你会发现它的功能并不止这些而且用的熟练后很容易就能找到影响性能的关键点。
彩蛋
读者朋友们,经过上面的介绍我想大家一定掌握了TraceView使用精髓了,是时候展现真正的技术实力的时候了,下面我提供一个TraceView的现场现场,看看读者朋友们能否查出原因来。TraceView的文件见附件ddms_traceView.zip。
参考文章:
http://bxbxbai.github.io/2014/10/25/use-trace-view/
https://yq.aliyun.com/articles/20467
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)