iOS开发

iOS-性能优化之CoreAnimation使用

2019-05-10  本文已影响0人  路飞_Luck
目录
序言

iOS为了提高滑动的流畅感,特意在滑动的时候将runloop模式切换到UITrackingRunLoopMode,在这个过程中专心做跟滑动相关的工作。但是还是会遇到滑动不流畅的情况。接下来我们就需要借助CoreAnimation来找出造成不流畅的元凶。

一 CoreAnimation 的介绍

分析图像动画性能主要用的是Core Animation这个组件,先简单介绍一下里面一些经常用到的选项:

模拟器 -> Debug

image.png

标示混合的图层会为红色,不透明的图层为绿色,通常我们希望绿色的区域越多越好。

假如我们设置viewlayer的shouldRasterize为YES,那些成功被缓存的layer会标注为绿色,反之为红色。

标示那些被Core Animation拷贝的图片。这主要是因为该图片的色彩格式不能被GPU直接处理,需要在CPU这边做转换,假如在主线层做这个操作对性能会有一定的影响。

被缩放的图片会被标记为黄色,像素不对齐则会标注为紫色。

标示哪些layer需要做离屏渲染(offscreen-render)。

二 遇到性能瓶颈的解决出路

下面这张图摘自 WWDC2014 大会视频 Advanced Graphics and Animations for iOS Apps

Performance Investigation Mindset.png

解释说明如下:

是否受到CPU或者GPU的限制?
是否有不必要的CPU渲染?
是否有太多的离屏渲染操作?
是否有太多的图层混合操作?
是否有奇怪的图片格式或者尺寸?
是否涉及到昂贵的view或者效果?
view的层次结构是否合理?

当你碰到性能问题的时候,可以从这几方面入手

三 离屏渲染
3.1 CPU 的离屏渲染

通常在以下操作时容易造成离屏渲染

如果没有自定义绘制的任务就不要在子类中写一个空的drawRect方法,因为只要实现了该方法,就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值,造成资源浪费

上面的情况使用的就是CPU离屏渲染,首先分配一块内存,然后进行渲染操作生成一份bitmap位图,整个渲染过程会在你的应用中同步的进行,接着再将位图打包发送到iOS里一个单独的进程--render server,理想情况下,render server将内容交给GPU直接显示到屏幕上。

3.2 GPU 的离屏渲染

使用GPU在当前屏幕缓冲区以外开辟一个新的缓冲区进行绘制,通常发生的情况有:

离屏渲染.png

离屏渲染(offscreen-render)对性能到底有什么影响?

通常大家说的离屏渲染指的是GPU这块(当然CPU这块也会有影响,也需要消耗一定的资源),比如修改了layer
的阴影或者圆角,GPU需要做额外的渲染操作。通常GPU在做渲染的时候是很快的,但是涉及到offscreen-
render的时候情况就可能有些不同,因为需要额外开辟一个新的缓冲区进行渲染,然后绘制到当前屏幕的过程
需要做onscreen跟offscreen上下文之间的切换,这个过程的消耗会比较昂贵,涉及到OpenGL的pipeline跟
barrier,而且offscreen-render在每一帧都会涉及到,因此处理不当肯定会对性能产生一定的影响,所以可以的
话尽量减少offscreen-render的图层。

查看哪些图层需要离屏渲染可以用Instruments的Core Animation工具进行检测,Color Offscreen-Rendered Yellow选项会将对应的图层标记为黄色。

四 造成离屏渲染原因分析
4.1 Blending

假如最上层的view是不透明的,那直接使用这个view的对应颜色之就可以,但如果view是透明的,在计算像素的颜色值时就需要计算它下面图层,透明的视图越多,计算量就越大,因此也会对图形的性能产生一定的影响,所以可以的话也尽量减少透明图层的数目。

4.2 圆角 cornerRadius + masksToBounds
_iconImgView.layer.cornerRadius = 22;
_iconImgView.layer.masksToBounds = YES;
image.png

我们可以通过使用 绘制一个圆形图片覆盖或者使用 Quartz2D来实现圆角。
具体的可以参考文章 iOS-裁剪圆角方法汇总(五种)

4.3 shadowPath

设置阴影效果可以通过以下代码实现

// 设置阴影
CALayer *imageViewLayer = iconImgV.layer;
imageViewLayer.shadowColor = [[UIColor blackColor] CGColor];
imageViewLayer.shadowOpacity = 1.0; //此参数默认为0,即阴影不显示
imageViewLayer.shadowRadius = 2.0; //给阴影加上圆角,对性能无明显影响
imageViewLayer.shadowOffset = CGSizeMake(5, 5);

但是当你滑动的时候发现会导致离屏渲染。

一个简单的不需要离屏渲染的方法就是制定阴影的路径,也就是设置layer的shadowPath属性

//设定路径:与视图的边界相同
UIBezierPath *path = [UIBezierPath bezierPathWithRect:iconImgV.bounds];
imageViewLayer.shadowPath = path.CGPath;//路径默认为 nil
4.4 Rasterization

对于圆角这种类似导致的性能问题,最简单的就是在列表中不要使用圆角,假如要通过cornerRadius + masksToBounds方式裁剪圆角的话,一种最快提升性能的方式就是设置layer的shouldRasterize为YES。

除了 GroupOpacityEdgeAntialiasing,其他效果触发的离屏渲染都会对性能产生严重影响,离屏渲染真的是一无是处吗?不,离屏渲染本来是个优化设计。如何物尽其用?答案是:Rasterization

cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = cell.layer.contentsScale

shouldRasterize = false时,离屏渲染的黄色特征仅限于上述自动触发离屏渲染的效果的部分,shouldRasterize = true后该部分和开启了该属性的 layer 整体(在这里就是 cell 整体)都有黄色特征,所以开启 Rasterization 是手动启动了离屏渲染。

从前面来看,离屏渲染会给 GPU 带来沉重的负担,强制启动岂不是更糟?开启 Rasterization 后,GPU 只合成一次内容,然后复用合成的结果;合成的内容超过 100ms 没有使用会从缓存里移除,在更新内容时还会产生更多的离屏渲染。对于内容不发生变化的视图,原本拖后腿的离屏渲染就成为了助力;如果视图内容是动态变化的,使用这个方案有可能让性能变得更糟。

Core Animation Instruments 有个Color Hits Green and Misses Red的选项,开启 Rasterization 后开启这个选项,屏幕上绿色的部分表示有渲染缓存可用,红色的部分表示无渲染缓存可用。

使用该属性注意事项:

假如图层的内容经常变化,比如cell里面有涉及到动画之类的,那么缓存的内容就无效了,GPU需要重新创建缓存区,导致离屏渲染,这又涉及到OpenGL的上下文环境切换,反而降低性能。

五 实战技巧
5.1 裁剪圆角

对于圆角可以使用一张中间圆形透明的图覆盖在上面,虽然这会引入blending操作,但是大部分情况下性能会比离屏渲染好。
参考文章 iOS-裁剪圆角方法汇总(五种)

5.2 view 层次结构

让你的view层次结构平坦一些,因为OpenGL在渲染layer的时候,在碰到有子层级layer的时候可能需要停下来把两者合成到一个buffer里再接着渲染。(When the OpenGL renderer goes to draw each layer, it may have to stop for some subhierarchies and composite them into a single buffer).

5.3 延迟加载图片

有时候在边滚动边设置图片的时候可能会有一定的影响,因此可以在滚动的时候imageview不执行setimage的操作,滚动停止的时候才加载图片,由于滚动的时候NSRunloop是处于UITrackingRunLoopMode模式下,可以采用如下的方式,将设置图片放到NSDefaultRunLoopMode模式下才进行:

// 延迟加载图片
__block UIImage *downloadedImage = nil;
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:obj] options:SDWebImageDownloaderScaleDownLargeImages progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
    downloadedImage = image;
}];
[imgView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
5.4 * 图片加载的极限优化方式:FastImageCache

本文会持续更新,并且进行完善,配备相应的代码例子,敬请期待。


本文参考
iOS app性能优化的那些事(二)
iOS离屏渲染优化



上一篇下一篇

猜你喜欢

热点阅读