最近公司SE拿了一个南京厂家做的一款工程机跟我反映一个问题,说在这个手机上使用公司APP的一个上下翻页功能的时候,翻页动作执行的就像慢动作一样(看下图);瞬间我特么菊花一紧,居然有这种事,这不能忍啊,同时心中也是一万只草泥马奔腾而过,心想:大哥 你又从哪个不知名厂家拿了个手机过来啊,鬼知道他们的手机有没有问题呢,测试都用了十来台工程机和市场上的民用手机都没出现这问题;但是现实是残酷的,谁让我是程序猿呢,只能乖乖的去查原因了
这是将翻页功能单独做个demo,可以看到翻页的过程很慢
ui卡顿应该是程序猿最不想见到的问题了,或者说现象吧,因为这让用户很直接的感受到极差的使用体验,此时用户的内心OS:这什么垃圾APP啊,做的这么卡;心情好点的可能连带着手机一起怼,这什么垃圾手机啊,性能这么差;所以在平常开发的时候一定要注意这方面的问题,多做测试,同时多拿一些机型测试,毕竟Android碎片化太严重了,随便一个小厂家都能做个手机出来卖。
一般来说UI卡顿,比如列表卡顿,动画卡顿,界面切换卡顿,基本上都是因为主线程有耗时操作,影响视图的连贯展示;而耗时有两种情况:
开头也说了,只在这款工程机上出现了翻页慢的情况,所以第一种情况是不可能了;所以只能考虑第二种情况了,因为第二种情况还是要取决于设备的硬件的,但是想一眼找到哪块方法耗时真不容易,这时候就要借助工具来帮我们找出到底谁在偷偷的做耗时操作
本文要用到的性能分析工具就是TraceView,它是Android平台的特有的一个用于性能分析的工具,通过图形化界面的方式展示程序的性能表现,可以具体追踪到方法级别;TraceView本身是一个数据分析工具,而数据采集需要依靠Debug 类或者 DDMS 工具,两者用法如下
假如你明确知道是哪个方法耗时,或者哪个操作耗时,这时候你就可以通过Debug类的API来分析;比如你想测下方法A要消耗多长时间和这段时间内所有线程的执行情况(只有Java线程),那就在方法A的开头添加Debug.startMethodTracing方法,然后结束时添加Debug.stopMethodTracing方法(想分析Native层的用startNativeTracing方法);这时候会在SD卡根目录下生成一个dmtrace.trace文件,切记要添加并申请WRITE_EXTERNAL_STORAGE权限,如下
public void onClick(View v) {
Debug.startMethodTracing();
//接下來做一些耗時操作
Debug.stopMethodTracing();
}
接下来将文件导入到电脑,你们可以看下自己的SDK安装目录下的tools目录下有一个traceview.bat文件,进入到这个目录然后在dos中运行traceview C:\tracefile\dmtrace.trace,然后就能看到一个分析图形了,最后按照官方文档分析就行了
我今天就是用这个工具来进行数据采集,DDMS 的全称是Dalvik Debug Monitor Service,是 Android 开发环境中的Dalvik虚拟机调试监控服务,可采集系统中某个正在运行的进程的函数调用信息;这个工具比较适合盲测,比如你也不知道具体哪个方法会出现耗时,那就通过它把APP的某一段使用过程进行方法追踪分析
使用方法是在Android Studio里进行如下操作
或者直接在dos窗口中输入monitor,接着就会弹出Android Device Monitor窗口:
点击上图中的按钮你可能会弹出一个选择框,如下
第一个:基于样本的分析,通过在给定频率上中断VM并在那时收集调用堆栈来工作。 开销与采样频率成正比
第二个:基于跟踪的分析,通过跟踪每个方法的进入和退出来工作。这捕获了所有方法的执行,无论多小,因此具有高开销
我这里选择第二种
接下来就使用这个工具再结合上面说的翻页卡顿例子实际操作,看看如何分析卡顿原因
我们打开DDMS,点击上方图中的按钮,然后做一次翻页动作,接着再次点击那个按钮停止方法追踪分析,这时DDMS会自动触发Traceview工具来分析采集数据,并弹出可视化的面板,如下
我这里就是追踪了一个翻页动作的方法执行情况,这个面板分为三部分:
接下来重点就是分析这个Profile Panel了,那需要先了解这个面板上的名称的意义,如下这幅图:
该图片顶部有多个英文名称,含义如下
名称 | 含义 |
---|---|
Name | 追踪期间所调用的方法 |
Incl Cpu Time % | Cpu执行该方法及其子方法所花费时间占Cpu总执行时间的百分比 |
Incl Cpu Time | Cpu执行该方法该方法及其子方法所花费的时间,以毫秒为单位 |
Excl Cpu Time % | Cpu执行该方法所花费的时间占Cpu总时间的百分比,不包含内部调用其它方法时间 |
Excl Cpu Time | Cpu执行该方法所花费的时间,不包含内部调用其它方法时间,以毫秒为单位 |
Incl Real Time % | 该方法及其子方法从开始执行到结束所花费的实际时间占总时间的百分比 |
Incl Real Time | 该方法及其子方法从开始执行到结束所花费的实际时间,以毫秒为单位 |
Excl Real Time % | 该方法从开始执行到结束所花费的实际时间占总时间的百分比,不包含子方法 |
Excl Real Time | 该方法从开始执行到结束所花费的实际时间,以毫秒为单位,不包含子方法 |
Calls+Recur Calls/Total | 方法调用次数+递归次数 调用次数/总次数 【前半截是方法的数据,后半截是点击方法后下方弹出的子方法的数据】 |
Cpu Time/Call | Cpu执行时间和调用次数的百分比,表示每次调用所消耗的Cpu时间 |
Real Time/Call | 方法执行实践花费的时间和调用次数的百分比,表示该方法平均执行时间 |
我们可以点击每个名称进行排序,这里以Incl Cpu Time进行降序排列,点击上图中的第一行,如下图:
可以看到占比最高的79.8%的方法是FlipRenderer类的onDrawFrame方法,并且CPU总的执行时间是817ms,这个方法就占用了652ms,从后面的Incl Real Time也表面该方法实际执行了1196ms,调用了24次;那我就继续点击这个方法看看,会跳到如下图所示的结果
这个图可以看到有Parents和Children两栏,其中Parents下方表示调用该方法的父方法,Children下方表示该方法内部调用的子方法,接下来我们继续点击比例最高的方法进入看看,直到下方这个图
最终可以看到是self这行的Incl Cpu Time比例最高,self代表自身方法中的语句执行情况,也就是说Cpu执行这个方法自身语句所花费的时间最多;同时可以看下Incl Real Time的值,除去执行自己语句的484.144ms的耗时,内部还调用了一个GLUtils.texSubImage2D方法耗时507.571ms;这样翻页慢的原因就是这个createTexture方法了
其实从后面的Calls+Recur Calls/Total 可以看出来createTexture方法被调用了48次,Incl Real Time值表明48次调用花费时间是1032.437ms,平均一次调用要1032.437/4821.509 = 21.509ms,跟Real Time/Call值是一样的,这也说明这种UI卡顿是属于单次执行不耗时,但是调用次数多了就耗时的情况
通过对比发现,这一个createTexture方法在其它设备上执行一次只需要5ms左右,但是在这个设备上执行一次时间就翻了5倍;一看手机使用的是一款联发科的低配版cpu,不过最后还是自己优化了下这个方法,降低执行所花费的时间,基本算解决了这个问题;大家也可以使用这个方法去分析下自己app里的UI卡顿原因