一、布局渲染流程
我们平常开发中的那些控件,比如Button,TextView,是如何渲染到屏幕上的呢?
简而言之,就是现先将xml解析成相应的对象,然后CPU经过计算以后将图形信息传给GPU,GPU来负责绘制,栅格化等操作,最终显示到手机屏幕上。
二、为什么会出现卡顿
Android每16ms对屏幕进行一次刷新,当一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件等待GPU完成栅格化渲染操作,最后在下一次VSync信号到来时才显示到屏幕上,这样就会让这一帧画面,在屏幕上多停留了16ms
如上图
Step1. Display显示第0帧数据,此时CPU和GPU渲染第1帧画面,而且赶在Display显示下一帧前完成
Step2. 因为渲染及时,Display在第0帧显示完成后,也就是第1个VSync后,正常显示第1帧
Step3. 由于某些原因,比如CPU资源被占用,系统没有及时地开始处理第2帧,直到第2个VSync快来前才开始处理
Step4. 第2个VSync来时,由于第2帧数据还没有准备就绪,显示的还是第1帧。这种情况被Android开发组命名为“Jank”。
Step5. 当第2帧数据准备完成后,它并不会马上被显示,而是要等待下一个VSync。
所以总的来说,就是屏幕平白无故地多显示了一次第1帧。原因大家应该都看到了,就是CPU没有及时地开始着手处理第2帧的渲染工作,以致“延误军机”。
试想用户盯着同一张图看了32ms而不是16ms,当然很容易察觉出卡顿感,哪怕仅仅出现一次掉帧,用户都会发现动画不是很顺畅。
三、如何解决这种问题
16 毫秒的时间主要被两件事情所占用
第一件:将 UI 对象转换为一系列多边形和纹理
第二件: CPU 传递处理数据到 GPU 。所以很明显,我们要缩短
这两部分的时间,也就是说需要尽量减少对象转换的次数,以及上传数据的次数
如何减少这两部分的时间 以至于在 16ms 完成呢?
(1)CPU 减少 xml 转换成对象的时间(去掉重复的、减少不必要的布局,尽量扁平化)
(2)GPU 减少重复绘制的时间
四、什么是过度绘制
GPU的绘制过程,就跟刷墙一样,一层一层地进行,16ms刷一次。这样就会造成,图层覆盖的现象,即无用的图层还被绘制在底层,造成不必要的浪费
GPU过度绘制的几种情况
- 自定义控件中,onDraw方法做了过多重复绘制
- 布局层次太深,重叠性太强。用户看不到的区域GPU也会渲染,导致耗时增加
过度绘制查看工具
在手机端的开发者选项里,里面有个调试GPU过度绘制工具
点击以后会发现你的屏幕变得红红绿绿的,这些颜色就代表了特定部分过度绘制的程度
- 蓝色:过度绘制一次(无过度绘制)
- 淡绿:过度绘制两次
- 淡红:过度绘制三次
- 深红:过度绘制四次
我们优化的目的就是尽量减少红色,看到更多蓝色的区域。
可以看到,这里的RecyclerView布局列表项重复绘制了四次,导致画面十分卡顿。
通过分析,我们发现问题有以下几个:
- 背景和主题冲突,重复绘制两次。解决办法,设置主题为null。之后我们发现背景由绿色变为了蓝色,减少了一层绘制
- imageView也设置了一个background,导致过度绘制,解决办法,将这个background去掉。之后图片也由淡红变成了绿色。
- RecyclerView的item项中多了一层不必要的LinearLayout,将其删除,我们的过度绘制又减少了一层。
其实还有更多方法来减少过度绘制,优化我们的性能。
- ListView,RecyclerView列表项中避免使用LinearLayout多重嵌套,尽量用一个RelativeLayout。
- 可以在TextView中加上drawableLeft来设置图片,避免使用imageView+TextView的方式。
因为所有的布局解析都要消耗cpu的计算性能,所以Layout并不是越多越好。
五、自定义View的优化
自定义View优化的重点主要在Canvas上,例如出现几个View互相层叠时(卡牌式),可以通过裁剪画布防止过度绘制
六、使用Hierarchy Viewer 优化布局
在Android Studio 的Android Device Monitor中直接打开这个工具
然后打开我们要检测的app,然后
之后可以看到我们的View树,
看到这个简直崩溃了,太多了。
我们看到视图中几乎所有节点都有三个点,颜色也各不相同
这三个点也是代表着View的Measure, Layout和Draw。
不同颜色意味着不同的速度:
绿: 表示该View的此项性能比该View Tree中超过50%的View都要快;例如,代表Measure的是绿点,意味着这个视图的测量时间快于树中的视图对象的50%。
黄: 表示该View的此项性能比该View Tree中超过50%的View都要慢;
红: 表示该View的此项性能是View Tree中最慢的。
可以很清楚的分辨清不同View的性能以及Measure, Layout和Draw的时间,通过红色的View节点进去查看,布局是否有不合理之处。
优化方式
- Hierarchy Viewer View树中只有一个子节点且id没有被其他地方使用的布局可以去掉
- 减少不必要嵌套
- 使用merge标签避免与父容器重叠。
- include 复用布局,并且复用的布局可以在GPU里可以高效缓存