Android丢帧分析与优化
如果你觉得应用卡顿、不够流畅,不用怀疑,很大原因是没有在16ms完成你的工作。
著名的“16ms”原则:
我们通常都会提到60fps(Frame Per Second)与16ms,可是知道为何会是以程序是否达到60fps来作为App性能的衡量标准吗?
60fps:人眼与大脑之间的协作无法感知超过60fps的画面更新。
*16ms:因为Android设定的刷新率是60fps,也就是每秒60帧,即16ms=1000/60Hz
Android系统每隔16ms会发出VSYNC信号重绘我们的界面。
就像这样:
如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。
关于VSYNC
为了理解App是如何进行渲染的,我们必须了解手机硬件是如何工作,那么就必须理解什么是VSYNC。
在讲解VSYNC之前,我们需要了解两个相关的概念:
Refresh Rate:代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60Hz。
Frame Rate:代表了GPU在一秒内绘制操作的帧数,例如30fps,60fps。
GPU会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。
不幸的是,刷新频率和帧率并不是总能够保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现Tearing的现象(画面上下两部分显示内容发生断裂,来自不同的两帧数据发生重叠)。
双缓冲机制
其实上面说的就是Android的双缓冲机制,而双缓冲技术一直贯穿这个Android系统。因为实际上帧的数据就是保存在两个缓冲区中,A缓冲用来显示当前帧,那么B缓冲就用来缓存下一帧的数据,这样就可以做到一边显示一边处理下一帧的数据。
前面的帧用序号表示,但实际上帧数据只保存在A、B两个缓冲区中。当前帧显示缓冲A,Android系统一旦发出VSYN信号时,就会在缓冲B中构建新的帧。当完成后(这里的完成指的是屏幕已经在缓冲B中拿到新一帧的数据,完成绘制),缓冲A的数据就会被清空,继续进行下一帧的绘制,注意,此时缓冲B的数据是不会被清空的,因为当前显示的是缓冲B中帧画面,清空的只是缓冲A的数据。
这样看起来貌似没什么问题,一切都是我们的掌控中。但是,由于某些原因,比如我们应用代码上处理不够好,又或者用户手机后台打开了很多应用,又在听歌又在下载视频什么的,CPU一时间被占用了,导致下一帧绘制的时间超过了16ms,那么问题就来了,这时候用户就不爽了,因为用户很明显感知到了卡顿的出现,也就是所谓的丢帧情况。如下图所示:
很好,下面我们来认真分析一下为什么会出现丢帧的情况:
Step1. 当Display显示第0帧数据,此时CPU和GPU已经开始渲染第1帧画面,并将数据缓存在缓冲B中;
Step2. 但是由于某些原因,就好像上面说的,CPU资源一时间被占用,导致系统处理该帧数据耗时过长或者未能及时处理该帧数据;
Step3. 当VSYNC信号来时,display向B缓冲要数据,这下悲催了,因为缓冲B的数据还没准备好,B缓冲区这时候是被锁定的,display无可奈何,只能继续显示之前缓冲A的那一帧,此时缓冲A的数据也不能被清空和交换数据。这种情况被Android开发组命名为“Jank”,就是所谓的“丢帧”,也被称作“废帧”;
Step4. 当第1帧数据(即缓冲B数据)准备完成后,它并不会马上被显示,而是要等待下一个VSYNC,Display刷新后,这时用户才看到画面的更新,中间这段时间的时间就白白被浪费掉了。
从上面的分析可以知道,因为缓冲B的超时,掉了链子,导致出现了丢帧的情况。因为一步的延迟,也很有可能导致后面的处理延迟,很可能造成一步慢步步慢啊。
三倍缓冲机制
出现上面这种情况怎么办,在Android系统里给出了这样的解决办法就是:再加入一个缓冲。这样就出现了三个缓冲,顾名思义,这里说的就是三倍缓冲。好,看下图:
当出现B缓冲超时,屏幕显示的还是缓冲A中的那一帧,因为此时缓冲A的数据还在使用,不能及时被交换,所以在下一次VSYNC信号来之前这段时间无任何作为,时间就会白白被浪费。为了避免这种时间浪费,在三倍缓冲机制中,系统这个时候会创建一个缓冲C,用来缓冲下一帧的数据。如上图所示,显示完缓冲B中那一帧后,下一帧就是显示缓冲C中的了。这样虽然还是不能避免会出现卡顿的情况,但是Android系统还是尽力去弥补这种缺陷,最终尽可能给用平滑的动效体验。
Overdraw解决方法
Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
1. 使用HierarchyViewer来查找Activity中的布局是否过于复杂
2. 打开Show GPU Overdraw的选项,观察UI上的Overdraw情况
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Overdraw有时候是因为你的UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。
3. 使用TraceView来观察CPU的执行情况,更加快捷的找到性能瓶颈
4. Profile GPU Rendering,选中On screen as bars选项
选择了这样以后,我们可以在手机画面上看到丰富的GPU绘制图形信息,分别关于StatusBar,NavBar,激活的程序Activity区域的GPU Rending信息。
中间有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
参考资料:
http://www.csdn.net/article/2015-01-20/2823621-android-performance-patterns/1
http://www.jianshu.com/p/02800806356c
https://www.youtube.com/watch?v=HXQhu6qfTVU&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&index=58
http://www.jianshu.com/p/1fb065c806e6