机型: iPhone8P iOS12.2
CoreAnimation 图形性能和进程的CPU使用情况 屏幕渲染
红色代表GPU需要做额外的工作来渲染View,绿色代表GPU无需做额外的工作来处理bitmap。
1、Color Blended Layers
(1)、图层混合
首先我们要明白像素的概念,屏幕上每一个点都是一个像素,像素有R、G、B三种颜色构成(有时候还带有alpha值)。如果某一块区域上覆盖了多个layer,最后的显示效果受到这些layer的共同影响。举个例子,上层是蓝色(RGB=0,0,1),透明度为50%,下层是红色(RGB=1,0,0)。那么最终的显示效果是紫色(RGB=0.5,0,0.5)。这种颜色的混合(blending)需要消耗一定的GPU资源,因为实际上可能不止只有两层。如果只想显示最上层的蓝色,可以把它的透明度设置为100%,这样GPU会忽略下面所有的layer,从而节约了很多不必要的运算。
(2)、调试Color Blended Layers
第一个调试选项”Color Blended Layers”正是用于检测哪里发生了图层混合,并用红色标记出来。因此我们需要尽可能减少看到的红色区域。一旦发现应该想法设法消除它。开始调试后勾选这个选项,我们在手机上可以看到如下的场景:
重要的是backgroundColor属性,如果不设置这个属性,控件依然被认为是透明的,所以我们做的第一个优化是设置控件的backgroundColor属性。
PS:如果label文字有中文,依然会出现图层混合,这是因为此时label多了一个sublayer。
2、Color Hits Green and Misses Red
(1)、光栅化
光栅化是将一个layer预先渲染成位图(bitmap),然后加入缓存中。如果对于阴影效果这样比较消耗资源的静态内容进行缓存,可以得到一定幅度的性能提升。demo中的这一行代码表示将label的layer光栅化:
view sourceprint?
1.label.layer.shouldRasterize = YES;
(2)、调试Color Hits Green and Misses Red
第二个调试选项是“Color Hits Green and Misses Red”,它表示如果命中缓存则显示为绿色,否则显示为红色,显然绿色越多越好,红色越少越好。
注意:光栅化的核心在于缓存的思想。我们自己动手把玩一下,可以发现以下几个有意思的现象:
view sourceprint?
1.上下微小幅度滑动时,一直是绿色
2.上下较大幅度滑动,新出现的label一开始是红色,随后变成绿色
3.如果静止一秒钟,刚开始滑动时会变红。
这是因为layer进行光栅化后渲染成位图放在缓存中。当屏幕出现滑动时,我们直接从缓存中读取而不必渲染,所以会看到绿色。当新的label出现时,缓存中没有个这个label的位图,所以会变成红色。第三点比较关键,缓存中的对象有效期只有100ms,即如果在0.1s内没有被使用就会自动从缓存中清理出去。这就是为什么停留一会儿再滑动就会看到红色。
光栅化的缓存机制是一把双刃剑,先写入缓存再读取有可能消耗较多的时间。因此光栅化仅适用于较复杂的、静态的效果。通过Instrument的调试发现,这里使用光栅化经常出现未命中缓存的情况,如果没有特殊需要则可以关闭光栅化,所以我们做的第二个优化是注释掉下面这行代码
view sourceprint?
1.// label.layer.shouldRasterize = true
3、Color Copied Images
(1)、颜色格式
像素在内存中的布局和它在磁盘中的存储方式并不相同。考虑一种简单的情况:每个像素有R、G、B和alpha四个值,每个值占用1字节,因此每个像素占用4字节的内存空间。一张1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600个像素,因此占用了超过8Mb的内存。但是一张同样分辨率的PNG格式或JPEG格式的图片一般情况下不会有这么大。这是因为JPEG将像素数据进行了一种非常复杂且可逆的转化。
CPU主要处理两件事:
(1)把图片从PNG或JPEG等格式中解压出来,得到像素数据
(2)如果GPU不支持这种颜色各式,CPU需要进行格式转换
比如应用中有一些从网络下载的图片,而GPU恰好不支持这个格式,这就需要CPU预先进行格式转化。
(2)、调试Color Copied Images
第三个选项“Color Copied Images”就用来检测这种实时的格式转化,如果有则会将图片标记为蓝色。
4、Color Misaligned Images
(1)、图片大小
在项目中,我们网络请求图片,大小不一,但是展示的UIImageView有时候是固定大小。这时候我们就需要图片的缩放了。图片的缩放需要占用时间,因此我们要尽可能保证无论是本地图片还是从网络或取得图片的大小,都与其frame保持一致。
(2)、调试Color Misaligned Images
第五个选项“Color Misaligned Images”,它表示如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。勾选上这个选项并进行调试,可以看到如下场景:
5、Color Offscreen-Rendered Yellow
(1)、离屏渲染
离屏渲染表示渲染发生在屏幕之外。离屏渲染意味着把渲染结果临时保存,等用到时再取出,因此相对于普通渲染更占用资源。
(2)、调试Color Offscreen-Rendered Yellow
第六个选项“Color Offscreen-Rendered Yellow”会把需要离屏渲染的地方标记为黄色,大部分情况下我们需要尽可能避免黄色的出现。离屏渲染可能会自动触发,也可以手动触发。以下情况可能会导致触发离屏渲染:
1、重写drawRect方法;(自动触发离屏渲染)
2、有mask或者是阴影(layer.masksToBounds, layer.shadow*),模糊效果也是一种mask;(自动触发离屏渲染)
3、layer.shouldRasterize = true;(手动开启离屏渲染)
开始调试并勾选“Color Offscreen-Rendered Yellow”,会看到这样的场景:
可以看到tabbar和statusBar也是黄色,这是因为它们使用了模糊效果。
如果图片使用了阴影,也是黄色,这说明它也进行了离屏渲染,解决方案,在设置阴影效果的四行代码下面添加一行:
view sourceprint?
1.imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath
这行代码制定了阴影路径,如果没有手动指定,Core Animation会去自动计算,这就会触发离屏渲染。如果人为指定了阴影路径,就可以免去计算,从而避免产生离屏渲染。
设置cornerRadius本身并不会导致离屏渲染,但很多时候它还需要配合layer.masksToBounds = true使用。根据之前的总结,设置masksToBounds会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:
view sourceprint?
1.// 设置圆角
2.label.layer.masksToBounds = true
3.label.layer.cornerRadius = 8
4.label.layer.shouldRasterize = true
5.label.layer.rasterizationScale = layer.contentsScale
6、Color Compositing Fast-Path Blue
(1)、快速路径
离屏渲染的最后一步是把此前的多个路径组合起来。如果这个组合过程能由CPU完成,就会大量减少GPU的工作。这种技术在绘制地图中可能用到。
(2)、调试Color Compositing Fast-Path Blue
第七个选项“Color Compositing Fast-Path Blue”用于标记由硬件绘制的路径,蓝色越多越好。
7、Flash updated Regions
(1)、变化区域
刷新视图时,我们应该把需要重绘的区域尽可能缩小。对于未发生变化的内容则不应该重绘。
(2)、调试Flash updated Regions
第八个选项“Flash updated Regions”用于标记发生重绘的区域。
四、总结
1、避免图层混合
①、确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明; ②、如无特殊需要,不要设置低于1的alpha值; ③、确保UIImage没有alpha通道;
2、避免临时转换
①、确保图片大小和frame一致,不要在滑动时缩放图片; ②、确保图片颜色格式被GPU支持,避免劳烦CPU转换;
3、慎用离屏渲染
①、绝大多数时候离屏渲染会影响性能; ②、重写drawRect方法,设置圆角、阴影、模糊效果,光栅化都会导致离屏渲染; ③、设置阴影效果是加上阴影路径; ④、滑动时若需要圆角效果,开启光栅化;