本文内容是参考文章:http://www.cocoachina.com/ios/20151130/14477.html
*写本篇文章的目的为了总结和梳理上面那篇文章(YYKit的作者写的)的知识点,我并不是想秀~~~~
一.屏幕显示图像的原理
基本上所有的显示器都是遵循CRT显示器原理,在显示一帧图像的时候大致是一把电子枪经过从左至右、从上到下的顺序进行扫描,扫描完成后显示器就会显示完整的一帧画面。
总的来说,CPU、GPU、显示器的协作方式大致是下图方式:
1、CPU计算显示内容传递给GPU。
2、GPU渲染完成后将这一帧的内容放到帧缓冲区。(这里有两个帧缓存区,GPU会根据信号来回切换)
3、视频控制器取出缓冲区的内容并转换传递给显示器。(这里视频控制器是通过垂直水平信号VSync获取缓冲区数据)
4、显示器显示完成后并发送VSync信号给GPU。
5、跳到第二步进行循环。
二、手机界面卡顿的原因
显示器在显示完一帧之后,发送垂直同步信号到CPU、GPU,如果这时候CPU和GPU在信号时间内没有完成计算或渲染内容,也就是缓存区没有内容可取,此时显示器还是会显示上一帧的内容,这时手机界面就会给人以卡顿的感觉。这也就是卡顿的主要原因了。
此时如果要对界面优化,主要就是针对CPU和GPU资源消耗进行优化了。
也许有人不知道CPU核GPU的区别(我就是其中一个),我查了下。CPU又叫中央处理器,GPU叫图形处理器。
——CPU比较适合处理复杂的逻辑控制,通用性更强,能够处理很多不同的数据类型。
——GPU适合图像处理和大规模并发计算。
想了解更多点击这里。
三、CPU资源消耗解决方案
在程序运行中比较消耗CPU资源的主要是:
1、对象创建
消耗资源:
——对象的创建会分配内存、调整属性、文件操作。
优化方案:
——尽量使用轻量的对象代替重量级的对象(如:轻量级CALayer、重量级UIView)
——尽量推迟对象的创建时间,并把对象的创建分散到多个任务中去(CALayer、UIVIew的创建操作只能在主线程)。
——如果对象可以复用并且复用的代价比释放重新创建要小就尽量复用。
2、对象调整
消耗资源:
——CALayer内部并没有属性,在调用属性方法时候,它内部是通过运行时resolveInstanceMethod为对象临时添加一个方法,并把对应的属性保存到内部的字典里面,同时还会通知代理、创建动画等等。所以在使用UIView的显示相关属性时其实都是CALayer属性映射来的。
优化方案:
——尽量减少显示相关属性不必要的修改。
——尽量避免视图层次调整、添加和移除。
3、对象销毁
消耗资源:
——只有当一个对象容器持有大量对象时,销毁时的资源消耗比较明显。
优化方案:
——如果对象可以放到后台线程去释放,就尽量放到后台去释放。
4、布局计算
消耗资源:
——不论用什么技术对视图进行布局,终究还是改变UIVIew的frame/bounds/center 等属性的调整。
优化方案:
——在后台线程提前计算好布局,在需要时一次性计算调整好对应属性,不要多次、频繁的计算和调整。
5、Autolayout
消耗资源:
——不论用什么技术对视图进行布局,终究还是改变UIVIew的frame/bounds/center 等属性的调整。
优化方案:
——在后台线程提前计算好布局,在需要时一次性计算调整好对应属性,不要多次、频繁的计算和调整。
6、文本计算
消耗资源:
——如果一个界面中包含大量文本,文本的宽高计算会占用很大一部分资源,并且不可避免。
优化方案:
——如果对文本显示没有特殊要求,可以参考下 UILabel 内部的实现方式:用 [NSAttributedString boundingRectWithSize:options:context:] 来计算文本宽高,用 -[NSAttributedString drawWithRect:options:context:] 来绘制文本。尽管这两个方法性能不错,但仍旧需要放到后台线程进行以避免阻塞主线程。
——如果用 CoreText 绘制文本,那就可以先生成 CoreText 排版对象,然后自己计算了,并且 CoreText 对象还能保留以供稍后绘制使用。
7、文本渲染
消耗资源:
——屏幕上能看到的所有文本内容控件,包括 UIWebView,在底层都是通过 CoreText 排版、绘制为 Bitmap 显示的。常见的文本控件 (UILabel、UITextView 等),其排版和绘制都是在主线程进行的,当显示大量文本时,CPU 的压力会非常大。
优化方案:
——解决方案只有一个,那就是自定义文本控件,用 TextKit 或最底层的 CoreText 对文本异步绘制。CoreText 对象创建好后,能直接获取文本的宽高等信息,避免了多次计算(调整 UILabel 大小时算一遍、UILabel 绘制时内部再算一遍);CoreText 对象占用内存较少,可以缓存下来以备稍后多次渲染。
8、图片的解码
消耗资源:
——用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。
优化方案:
——在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。
9、图像的绘制
消耗资源:
——图像的绘制通常是指用那些以 CG 开头的方法把图像绘制到画布中,然后从画布创建图片并显示这样一个过程。这个最常见的地方就是 [UIView drawRect:] 里面了。
优化方案:
——由于 CoreGraphic 方法通常都是线程安全的,所以图像的绘制可以很容易的放到后台线程进行。
四、GPU资源消耗解决方案
1、纹理的渲染
消耗资源:
——所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不论是提交到显存的过程,还是 GPU 调整和渲染 Texture 的过程,都要消耗不少 GPU 资源。当在较短时间显示大量图片时(比如 TableView 存在非常多的图片并且快速滑动时),CPU 占用率很低,GPU 占用非常高,界面仍然会掉帧。
——当图片过大,超过 GPU 的最大纹理尺寸时,图片需要先由 CPU 进行预处理,这对 CPU 和 GPU 都会带来额外的资源消耗。
优化方案:
——尽量减少在短时间内大量图片的显示,尽可能将多张图片合成为一张进行显示。
——目前来说,iPhone 4S 以上机型,纹理尺寸上限都是 4096x4096,更详细的资料可以看这里:iosres.com。所以,尽量不要让图片和视图的大小超过这个值。
2、视图的混合
消耗资源:
——当多个视图(或者说 CALayer)重叠在一起显示时,GPU 会首先把他们混合到一起。如果视图结构过于复杂,混合的过程也会消耗很多 GPU 资源。
优化方案:
——为了减轻这种情况的 GPU 消耗,应用应当尽量减少视图数量和层次,并在不透明的视图里标明 opaque 属性以避免无用的 Alpha 通道合成。当然,这也可以用上面的方法,把多个视图预先渲染为一张图片来显示。
3、图片的形成
消耗资源:
——CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在 GPU 中。当一个列表视图中出现大量圆角的 CALayer,并且快速滑动时,可以观察到 GPU 资源已经占满,而 CPU 资源消耗很少。这时界面仍然能正常滑动,但平均帧数会降到很低。
优化方案:
——可以尝试开启 CALayer.shouldRasterize 属性,但这会把原本离屏渲染的操作转嫁到 CPU 上去。对于只需要圆角的某些场合,也可以用一张已经绘制好的圆角图片覆盖到原本视图上面来模拟相同的视觉效果。最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。
最后作者推荐了个框架AsyncDisplayKit。该框架对CALayer进行了封装,当使用该框架封装的view时,里面将消耗性能的操作放在后台线程进行处理,并且节省了资源。
但是在平时日常的开发中,一般是没有时间进行优化的,最好是将优化工作放到后期处理。