卡顿的原理
iOS系统界面滑动流畅性的保持主要是依靠CPU和GPU两大处理硬件间通力合作的结果,一个视图的显示需要先经过CPU创建、布局计算、对图片解码、文本绘制,然后CPU将计算的结果交给GPU,GPU可能需要对图形进行变换、合成、渲染,GPU然后将渲染的结果提交到帧缓冲区等待下一次的垂直同步信号(V-Sync)到来显示到显示器上,如果在一个V-Sync间隔内CPU或者GPU由于过高的利用率都可能导致数据的不及时提交,那么那一帧数据就会被丢弃,等待下一次的V-Sync再显示,这也就是通常能感受到的界面卡顿现象,也就是掉帧。
优化过程
所以当出现卡顿现象时主要去分析此刻CPU和GPU的利用率,当出现较高的CPU利用率时应及时去分析代码执行的效率,或者用Time Profier去分析导致较高占用率的代码,更多详细的关于优化CPU的情况本文不做详细分析。
而对于GPU资源消耗的优化本文将通过一个Demo优化过程来讲解优化的步骤,你可以在这里GitHub下载源码,Demo是有2个tab组成的,before是优化前视图,after是优化后的,你可以通过对比学习体会其中的差异。
下载运行demo, 打开Xcode的调试选项,在菜单栏-Debug-View Debugging-Rendering可以找到,运行APP(真机)勾选相关的菜单选项即可
1.视图混合(Blending)
app中显示的效果往往是多个视图重合叠加的效果,而在计算视图重合显示颜色的时候就需要考虑透明的影响,当顶部视图出现透明的情况时,颜色的计算就需要考虑其透明度,这样无疑增加了计算成本,消耗GPU资源,所以应尽量避免过多的透明视图数量。
对于UIImageView而言,如果图片本身是带有透明通道的同样会导致Blending,所以应该尽量避免使用带有透明度的图片。
对于文本UILable, 如果不设置背景颜色,同样会出现Blending,所以需要设置UILabel的背景颜色,对于显示中文的UILabel, 除了设置背景颜色外还需要设置masksToBounds属性,因为中文时UILable会多了一个sublayer。
勾选Rendering中的第一个选项Color Blended Layers,此选项就是用于检测哪些视图出现了Blending,出现Blending的地方会用红色标记出来,运行demo可以出现下图所示情况:
可以看到,before tab下的控制器视图无论是图片还是文本都被标红,表示出现了Blending,对于图片我们可以在Assets里面找到对应的图片,然后查看简介就可以查看图片是否有Alpha通道,会看到图片都具有Alpha通道,也就回导致Blending
对于这种情况,可以让UI切图时关闭此Alpha通道,也可以直接用mac自带的图片软件打开图片,然后导出图片关闭Alpha通道。所以我们需要将所有有Alpha的图片都处理一遍,尽量不要使用带有透明度的图片。
对于UILabel,上面有提到,我们只需要下面的2行代码即可处理:
titleLabel.backgroundColor = UIColor.white
titleLabel.layer.masksToBounds = true
通过去掉图片alpha通道优化后如下图所示,只有被设置为圆角的小图片还存在Blending,因为我们是直接设置的layer的cornerRadius(IOS9以下会导致离屏渲染),同样我们可以直接用无透明通道的圆角图片来替换解决,但这需要UI适配更多背景图片。
2.光栅化
开启光栅化是通过设置属性shouldRasterize,开启光栅化后CALayer会被保存为bitmap放到缓存中,这样在下次需要时可以直接中缓存中取出来显示,这样节省了渲染时间,例如对于设置有阴影效果的复杂视图会对性能有一定的提升。
nameLabel.layer.shouldRasterize = true//开启光栅化
第二个调试选项 Color Hits Green and Misses Red就是用来查看光栅化视图的,勾选后若视图被标记为绿色,则表示命中了缓存,直接从缓存中取出来显示,缓存的有效时间为100ms, 而红色则表示没有命中。Demo中,我们对第2个Label开启了光栅化,滚动会发现被标记为绿色
更新一个已光栅化的Layer会触发离屏渲染,所以选择哪些视图适合做光栅化需要根据场景权衡,光栅化适合那些布局复杂而不经常变动的视图,比如
- 用于避免静态内容的复杂特效的重绘,例如UIBlurEffect
- 用于避免多个View嵌套的复杂View的重绘。
同时注意缓存是有大小限制的,所以不要过度是使用光栅化,因为超过缓存大小会导致大量的离屏渲染。
3.颜色格式
Color Copied Images选项能标识出视图中不能被GPU处理的图片,因为来自网络的图片格式可能千变万化,有的图片的格式是GPU无法识别的所以会交给CPU处理,出现这种情况就需要修改图片格式,
4.不标准的表面颜色格式
Color Non-Standard Surface Formats 打开此选项后会发现Label会出现灰色的背景颜色,然后经过我们给Label设置了背景颜色后便消失了,关于此选项的相关介绍甚少,期待有人能挖掘挖掘,所以只能猜测,苹果推荐我们给Label设置一个背景颜色。
5.颜色刷新频率
默认情况下图层的颜色更新是有10ms的延迟的,在某些特定情况下可能需要关闭这个延迟,但绝大多数情况用不到这个选项。
6.图片大小
Color Misaligned Images选项在图片像素不对齐(也就是图片带alpha通道)时,会在图片上面加一层洋红色来标识;而图片被缩放时,会加一层黄色来标识,我们可以看到优化前的图片会出现图片缩放,因为图片的显示大小和图片的大小不匹配。
图片缩放同样会消耗GPU资源,所以尽量保证图片的显示大小和原图大小一致来避免缩放,所以demo的图片在处理后大小都等于显示的大小来避免缩放。
7.离屏渲染
Color Offscreen-Rendered Yellow会用黄色标识哪些图层出现了离屏渲染,什么是离屏渲染?
离屏渲染表示渲染不是发生在当前屏幕的缓冲区中,而是发生在其他缓冲区的渲染,这就需要开辟更多的缓冲区,等到要用的时候再从其他的缓冲区读取来显示,所以这样会消耗更多的GPU资源,所以避免离屏渲染可以有效的提升显示性能。
如上图所示,要显示一个相机图标和一个蒙层,在前两个渲染通道中,GPU分别得到了相机的纹理和蓝色的蒙版layer的渲染结果。但这两个渲染结果没有直接放入Frame Buffer中,这就是离屏渲染。等到第三个渲染通道,才把两者组合起来放入Frame Buffer中最终显示到屏幕上,这就是典型的离屏渲染。
运行demo,打开Color Offscreen-Rendered Yellow选项,在beforetab控制器视图下出现离屏渲染的图层便会被黄色标识出来,如图:
可以发现右边的大图出现了离屏渲染,而圆角的小图却没有,大图出现离屏渲染的原因是设置了shadow阴影,而因为测试机器是ios11所以设置图片的cornerRadius并不会导致离屏渲染。
同样设置了以下属性时,都会触发离屏渲染:
- shouldRasterize(光栅化)
- masks(遮罩)
- shadows(阴影)
- edge antialiasing(抗锯齿)
- group opacity(不透明)
- 复杂形状设置圆角等(ios8)
- 渐变
上面出现离屏渲染的case都应该要注意,所以针对shadow可以通过设置shadowPath来避免,光栅化也应该尽量避免:
优化后代码如下:
mainImageView.layer.shadowColor = UIColor.black.cgColor
mainImageView.layer.shadowOpacity = 1
mainImageView.layer.shadowRadius = 2
// mainImageView.layer.shadowOffset = CGSize(width: 2, height: 2)
mainImageView.layer.shadowPath = UIBezierPath.init(roundedRect: mainImageView.bounds, cornerRadius: 2).cgPath
smallImageView.layer.cornerRadius = smallImageView.frame.size.width / 2
smallImageView.clipsToBounds = true
nameLabel.backgroundColor = UIColor.white
titleLabel.backgroundColor = UIColor.white
titleLabel.layer.masksToBounds = true
nameLabel.layer.masksToBounds = true
// nameLabel.layer.shouldRasterize = true
8.快速路径
第七个选项“Color Compositing Fast-Path Blue”用于标记由硬件绘制的路径,蓝色越多越好,demo略
9.变化区域
最后一个Flash Updated Regions 用于标记发生重绘制的区域并用黄色标记出来,对于大多数不变的区域应该尽量的避免重绘而只有小部分经常变化的区域重绘这有助于显著提高性能。demo略
优化结果
经过对图片Alpha透明度调整,大小剪裁适配显示,更改设置阴影,文本背景等一系列的优化,使用Core Animation记录GPU使用率的变化来观察优化效果:
before:
可以看到优化前CPU平均占用率达到60%,这还只是简单的视图布局,如果更加复杂占用率还会增加,这也就意味着更高的卡顿风险。
after:
对比可以发现在提升了平均显示帧数的同时大大降低了GPU消耗,性能得到显著的提升。
测试环境:Xcode9.3、iPhone6s、 iOS11、Swift4
Demo地址:GitHub地址
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。