概要
应用运行时的卡顿问题非常影响用户体验,严重降低产品表现力,本文将介绍应用卡顿原因以及分析方法等等。
卡顿问题可分为两类,应用卡顿和系统卡顿,本文针对系统正常时应用卡顿场景。
黄油工程
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
如上图,第1帧在显示时,GPU并没有准备好第2帧数据,导致第1帧数据连续显示两次,导致不流畅、卡顿。应用需要减轻UI线程负担,将耗时工作放入工作线程中,同时精简绘制工作,保证16ms内可以完成一帧绘制。
图层叠加
Android通过图层叠加完成绘制。
左侧的对象是生成图形缓冲区的渲染器,如主屏幕、状态栏和系统界面。SurfaceFlinger 是合成器,屏幕负责显示合成界面。
应用界面层级应该尽量精简,同时减少过度绘制问题,减轻系统压力。
卡顿原因
应用卡顿常见的原因,UI线程负荷过重,绘制代码不当等。系统卡顿或资源紧张也有可能导致应用卡顿,gc过多也能导致卡顿。此外,过度绘制,界面层级过多也会导致绘制效率下降。
在某些GPU性能较差的机器上,alpha动画效率低下,也会导致卡顿。
分析方法
遇到卡顿问题,从现象入手,排除系统卡顿原因,接下来可通过log分析、工具定位解决卡顿问题。常用工具为traceview,systrace等。
Traceview,针对单一应用,统计具体一段时间内方法执行时间、次数等信息。
Systrace,记录整机运行情况,包括cpu使用情况,vsync信号,gc信息等等,它也能记录部分方法的执行情况,时间等。
Log分析,重点跟踪gc情况,具体业务逻辑流程等。
Traceview
Traceview有两种使用方式,既可使用DDMS采集数据,也可使用Debug类采集数据。使用DDMS采集数据的步骤如下:
- 在应用的AndroidManifest.xml添加 android:debuggable="true",打开debug属性
- 打开DDMS面板,选中调试的应用
- 点击 Start Method Profiling 按钮
- 操作机器,执行对应需要性能分析的过程
- 点击 Stop Method Profiling 按钮
抓取数据完成后会自动跳转到traceview界面。
除了使用DDMS采集数据,也可以使用Debug类采集数据,使用Debug类采集数据步骤如下:
- 在AndroidManifest.xml中添加写sd卡权限
- 在问题怀疑点处调用Debug.startMethodTracing("demo");
- 在问题结束点处调用Debug.stopMethodTracing();,例如,可以在Activity的onCreate和onPause处分别调用上述方法。
- 从sd卡中导致trace文件到pc端,利用ddms打开trace文件。
DDMS方式简单,但很粗略。而使用Debug类采集数据,相对复杂,但更精确。
Traceview分析面板上,有很多的维度,一般来说,我们需要分析出那些耗时或者调用次数过多的函数,耗时的函数可通过维度Incl CPU Time分析。调用次数过多的函数可通过Cpu Time/Call维度分析。耗时的函数需要分析,很容易理解,但调用次数过多的函数很容易被忽略,如果view的onMeasure函数在不需要刷新时被调用多次,这也是值得关注的问题,所以需要分析函数的调用次数。
Traceview应用
模拟如下场景,ListView滑动卡顿,在getView方法中sleep一段时间,导致一帧数据无法在16ms内准备完成,因此滑动卡顿。
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
。。。。。。
try {
Thread.sleep(50);
} catch (Exception e) {
}
return convertView;
}
使用DDMS采集数据分析,按Incl CPU Time降序排列,traceview面板如下:
因为Incl CPU Time是包含内部调用的其它函数时间的,所以从上到下,花时间最多的就是虚拟机相关方法以及ActivityThread的main方法(应用线程启动的入口方法)等。点击每一个方法,均可以看到此方法自身花费时间、调用其它方法花费时间以及它的前一调用方法。一般来说通过跟踪调用链,可找到对应问题所在。
从ActivityThread的main方法入手,parents属性不用管,查看children属性,self即是自身花费时间,此时为0,可见耗时是花在loop方法,一跟跟踪调用链最终发现如下:
Adapter类的getView方法中调用了sleep方法以及setImageResource方法,而sleep方法耗时占用整体耗时的97%,如此,问题原凶已找到。
有一个小窍门,在分析某个方法的耗时构成时,如果此方法显示有多个子方法耗时时,则选取耗时比较最大的子方法继续分析,如下下面的情况:
我们预先已知listview滑动卡顿,那么耗时最多的应该是与listview相关的函数,而事实也是如此,继续跟踪线框内容,即可找到最终问题。
Systrace
Systrace与Traceview所不同的是,Systrace它能提供丰富得多的信息,它是对整机这段时间内的全面信息反馈,可以查看cpu分配情况,查看vsync信号,查看应用具体一帧图像各阶段耗时情况,甚至还可以查看gc情况,所以Systrace可以从系统高度,整体高度来查看卡顿问题。如果是分析单一应用卡顿问题,一定要通过Systrace来排除系统问题,确认这段时间内cpu资源足够,也要查看gc情况。如果某段时间内,应用一帧卡顿,而cpu资源不够或此时正在gc,则并不能说明是应用的问题。
由于google对android studio的大力推荐,现在使用eclipse抓取的Systrace,已经无法被chrome识别了,所以需要as抓取Systrace,抓取并打开Systrace的步骤如下:
- 打开as,打开tools菜单,打开android device monitor
- 点击抓取systrace按钮,勾选所需的信息,再确定
- 在机器上进行相应操作,生成对应trace文件
- 打开chrome浏览器,在地址栏上输入chrome://tracing/,再使用chrome打开之前保存的trace文件
Systrace使用W放大,S缩小,A左移,D右移,右侧有选择条,可选择鼠标点击,也可选择时间段标记。操作较为简单,不再详述。
Systrace有几个需要注意的点,首先就是cpu的状态问题。例如上图红框所示,obtainView方法上是无色的,而右边小部分确是蓝色或绿色,无色则代表cpu此时在睡眠状态,蓝色或绿色表示cpu处于运行状态。如上图所示,则可知此时cpu状态异常,是否是调用了sleep方法导致这一帧卡顿了?可使用左键滑动选中一段距离,查看这段距离的cpu情况:
Systrace也是可以直接点击红色帧(红色帧即是卡顿帧),查看红色帧的一些基本情况,也能看到对于红色帧的处理意见。
Systrace还能够显示当前的gc情况,例如:
Systrace还有一个强大的功能,选中查看某段时间内cpu的情况,如下图所示,cpu这段时间内有gc操作,binder调用,还有打印log,如果gc操作过于频繁,binder调用都是会影响系统性能的,另外log过多也是会影响系统性能的。
章节中的示例,抓取Systrace,分析,案例简单,在Systrace上也一目了解,obtainView时间太长了,原因是cpu那段时间都处于sleep状态。
性能优化的其它方法
前文介绍的是解决卡顿问题的重型武器,也有一些方法可以缓解卡顿或者让性能更好。通过绘制原理的介绍,可以清楚地知道,如果UI的层级越少越好。怎么查看UI的层级呢?可以通过hierarchyviewer工具。可以通过自定义view形式实现UI层级的减少。UI层级减少,也会缓解过度绘制问题。
另外,减少界面刷新的次数也很重要,避免不必要的UI界面刷新。降低UI层级以及减少不必要刷新次数较为简单,不再详述。