一、UI卡顿掉帧原因

iOS设备的硬件时钟会发出Vsync(垂直同步信号),然后App的CPU会去计算屏幕要显示的内容,之后将计算好的内容提交到GPU去渲染。随后,GPU将渲染结果提交到帧缓冲区,视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
也就是说,一帧的显示是由CPU和GPU共同决定的。
一般来说,页面滑动流畅是60fps,也就是1s有60帧更新,即每隔16.7ms就要产生一帧画面,而如果CPU和GPU加起来的处理时间超过了16.7ms,就会造成掉帧甚至卡顿。
CPU 和 GPU 不论哪个阻碍了显示流程,都会造成掉帧现象。所以开发时,也需要分别对 CPU 和 GPU 压力进行评估和优化。
二、 滑动优化方案
CPU:把以下操作放在子线程中
1.预排版(布局计算、文本计算、缓存高度等等)
2.预渲染(文本等异步绘制,图片解码等)
- 对象创建
复杂界面不要用xib,多用懒加载 - 文本渲染
常见的文本控件其排版和绘制都是在主线程进行的,当显示大量文本时,CPU的压力会非常大。对此解决方案只有一个,那就是自定义文本控件,用TextKit或最底层的CoreText对文本异步绘制。CoreText对象创建后可直接获取文本的宽高等信息,避免了多次计算(调整UILabel大小时计算一遍、UILabel绘制时内部再计算一遍)。CoreText对象占用内存较少,可以缓存下来以备稍后多次渲染。 - 文本计算
如果一个界面中包含大量文本,文本的宽高计算会占用很大一部分资源,并且不可避免。可以参考一下UILabel内部的实现方式:
//计算文本高度
[NSAttributeString boundingRectWithSize: options: context: ];
//绘制文本
[NSAttributeString drawWithRect: options: context: ];
尽管这两个方法性能不错,但仍需放在后台线程以避免阻塞主线程。
如果用CoreText绘制文本,就可以先生成CoreText排版对象,然后自己计算。并且CoreText还能够保留供稍后绘制使用。
- 图片的解码
当使用UIImage或CGImageSource的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到UIImageView或者CALayer.contents中去,并且CALayer被提交到GPU前,CGImage中的数据才会得到解码。这一步发生在主线程并且不可避免。
如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到CGBitmapContext中,然后从Bitmap中直接创建图片。目前常见的网络图片库都自带这个功能。
GPU : 纹理渲染,视图混合
-
纹理的渲染
所有的Bitmap,包括图片、文本、栅格化的内容,最终都要有内存提交到缓存,绑定为GPU纹理。当在较短时间显示大量图片时(比如tableview存在很多图片并且快速滑动时),CPU占用率很低,GPU占用非常高,界面仍会掉帧。避免这种情况的方法只能是只能尽量减少在短时间内显示图片数量,尽可能将多张图片合成一张进行显示。
多张图合成一张 -
图形的生成
CALayer的border、圆角、阴影、遮罩(mask)CAShapLayer的矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在GPU中。当一个列表视图中出现大量圆角的CALayer并且快速滑动时,可以观察到GPU资源已占满而CPU资源消耗很少,这时界面帧数会降到很低。为了避免这种情况可以尝试开启CALayer.shouldRasterize属性,但这会把原本离屏渲染的操作转嫁到CPU上去。对于只需要圆角的某些场合,也可以用一张已经绘制好的圆角图片覆盖到原本视图上;最彻底的解决办法就是把需要显示的圆形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。 -
视图的混合
当多个视图(或者说CALayer)重叠在一起显示时,GPU会首先把它们混合在一起。如果视图结果过于复杂,混合的过程也会消耗很多GPU资源。为了减轻这种情况的GPU消耗,应用应当尽量减少视图数量和层次,并在不透明的的视图里标明opaque属性以避免无用的alpha通道合成。
一般遇到性能问题时,考虑以下问题:
是否受到CPU或者GPU的限制?
是否有不必要的CPU渲染?
是否有太多的离屏渲染操作?
是否有太多的图层混合操作?
是否有奇怪的图片格式或者尺寸?
是否涉及到昂贵的view或者效果?
view的层次结构是否合理?
网友评论