16ms
关于Android的渲染机制是需要每隔16ms重新绘制一次Activity,也就是说需要在16ms内完成刷新屏幕的所有逻辑操作。Android的卡顿一般都是由于渲染机制造成的。
16ms相当于60fps,人眼与大脑之间的协作无法感知超过60fps的画面更新,电影使用的一般都是24fps,这已经能够满足观影的需求,所以如果超过60fps是完全没有必要的了,人眼也无法识别。Android系统会每隔16ms发出一个VSync信号,触发对UI进行渲染,如下图所示:
可以看到每隔16ms后会进行重绘的操作,在16ms内就可以看到流畅的界面,但是如果超过16ms就会出现如下的情况:
如果这个绘制操作超过了16ms,比如图中的24ms,这时在16ms后就不会进行渲染重绘,而是在下一个16ms再进行渲染重绘,也就是在32ms后才会看到,这就叫做丢帧了,这里是丢了一帧,及时如此用户也会感觉得到卡顿的。在丢帧了时候可以看到logcat会打印出drop frames的警告。
渲染原理
列名 | 解释 |
---|---|
PIPELINE | 管道 |
PROBLEM | 出现的问题 |
TOOLS | 解决的工具 |
SOLUTION | 解决的办法 |
出现卡顿的原因我们都知道是因为是CPU和GPU处理的时间超过了16ms,渲染的操作主要就是依赖于CPU和GPU。
关于CPU
CPU负责包括Measure,Layout,Record,Execute的计算操作。从上面的图可以看出CPU最常见的性能问题是不必要的布局和失效,这些内容必须在视图层次结构中进行测量、清除并重新创建,引发这种问题通常有两个原因:一是重建显示列表的次数太多,二是花费太多时间作废视图层次并进行不必要的重绘,这两个原因在更新显示列表或者其他缓存GPU资源时导致CPU工作过度。
-通过Hierarchy Viewer去检测渲染效率,去除不必要的嵌套
关于GPU
GPU主要负责栅格化操作,所谓栅格化操作就是把我们需要绘制的控件转为像素显示给用户看,但也不是直接通过xml就转化为像素的,其中首先需要CPU把xml先转化为多边形或者纹理,然后再由GPU进行栅格化,栅格化也是很费时的操作。
在GPU方面,最常见的问题是我们所说的过度绘制(overdraw),通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间。
-通过Show GPU Overdraw去检测Overdraw,最终可以通过移除不必要的背景以及使用canvas.clipRect解决大多数问题
所以在这16ms内要完成的两件事:第一,CPU将UI对象转化为一系列的多边形和纹理;第二,CPU传递数据到GPU,进行栅格化。
过渡绘制(OverDraw)
Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面, 如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
当设计上追求更华丽的视觉效果的时候,我们就容易陷入采用越来越多的层叠组件来实现这种视觉效果的怪圈。这很容易导致大量的性能问题, 为了获得最佳的性能,我们必须尽量减少Overdraw的情况发生。
按照以下步骤打开Show GPU Overrdraw的选项:设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,
蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。
绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。
淡红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。
深红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。
我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Overdraw有时候是因为你的UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景, 然后里面 的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域, 增加 蓝色区域的占比。这一措施能够显著提升程序性能。
OverDraw的处理
上面是我写的一个列表,一个很简单的列表,但是却是过渡绘制了,下面看看常见的解决过渡绘制的方法。
1. 去掉window的默认背景
当我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,当我们在xml文件又设置了background时这个默认的背景就是无用的就可以去掉。
getWindow().setBackgroundDrawable(null);
2. 去掉不必要的背景
有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景。比如上面整个界面设置了一个背景,然后每个Item又有一个背景,Item里面的TextView和ImageView也有背景,这样就会造成许多的重叠。
3. 使用clipRect
主要是在自定义View的时候使用,使用canvas.clipRect方法就是裁剪画布,只有裁剪出来的区域才会绘制,这样可以避免过度绘制,不太懂这个方法的可以看看Canvas的简单使用有简单的介绍。