离屏渲染
要理解离屏渲染,需要先了解两个名词,OffScreen Buffer(离屏缓冲区),Frame Buffer(帧缓冲区)。
- 一般情况下,系统把需要显示位图的直接放到
Frame Buffer,然后显示出来。
- 当出现离屏渲染之后,会出现一个一个新的缓冲区
OffScreen Buffer,在OffScreen Buffer完成在Frame Buffer完成不了的操作,然后提交到帧缓冲区,最后显示出来
为什么离屏渲染会产生性能问题?
- 产生了离屏渲染,会开额外的内存空间
OffScreen Buffer - 在
OffScreen Buffer进行渲染然后提交到Frame Buffer需要时间
OffScreen Buffer空间是有大小限制的,屏幕像素的2.5倍
产生的原因
可能大家都知道设置圆角就有可能会产生离屏渲染,可是为什么设置圆角就有可能会产生离屏渲染呢?我们结合下面两张图来理解:
我们在显示一个图层的时候其实包括了三部分内容,backgroundColor,contents,borderWith/borderColor。
Apple对cornerRadius的解释
我们可以看到,我们通过cornerRadius设置圆角的时候,其实只是对background起作用,如果contents也需要设置圆角,需要设置maskToBounds属性。
接下来在代码中来看一下这个问题
看下代码
// 设置了backgroundColor
UIImageView * imageOne = [[UIImageView alloc] init];
imageOne.frame = CGRectMake(100, 180, 100, 100);
imageOne.image = [UIImage imageNamed:@"photo"];
imageOne.backgroundColor = [UIColor greenColor];
imageOne.layer.cornerRadius = 50.f;
imageOne.layer.masksToBounds = YES;
[self.view addSubview:imageOne];
//没有设置backgroundColor
UIImageView * imageTwo = [[UIImageView alloc] init];
imageTwo.frame = CGRectMake(100, 320, 100, 100);
imageTwo.image = [UIImage imageNamed:@"photo"];
imageTwo.layer.cornerRadius = 50.f;
imageTwo.layer.masksToBounds = YES;
[self.view addSubview:imageTwo];
当我们只设置了cornerRadius和maskToBounds并没有发生离屏渲染,但当我们同时设置了background这时就会产生离屏渲染。
根本原因就是我们在没有设置
backgroundColor的时候,默认是不需要对background层进行处理,在imageView里面我们只需要渲染出content,然后进行切maskToBounds,放入Frame Buffer能然后就能直接显示出来了。一旦我们设置了background,我们需要对background进行渲染,对contents进行渲染,然后进行混合运算后做圆角处理,再提交到Frame Buffer,进行显示。因为在同一layer下出现了多个图层,圆角的剪切是需要进行混合运算再剪切,通过上面的图例我们知道ImageView其实是有background,contents。maskToBounds其实是基于cornerRadius来做出运算后对contents切圆角,当我们没有设置backgroundColor也就没有必要去渲染background,对contents基于cornerRadius运算后得出圆角提交帧缓冲区(Frame Buffer)就行了。这也默认是对内存的一种优化,只有当遇到同一Layer下多层图层,直接提交Screen Buffer处理不了的融合显示,才会产生离屏渲染,开辟OffScreen Buffer,在OffScreen Buffer进行相应的各种算法。
哪些情况会触发离屏渲染
- 对
layer使用了mask(遮罩)。 - 对
layer使用了masksToBounds,clipsToBounds。 - 对
layer添加了投影。 - 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)。
- 设置了不为1的组透明度。
因为透明度不为1,像素点颜色的混合运算,需要等这几个不同透明度的图层都渲染好了,提交到
Screen Buffer再进行运算,最后提交Frame Buffer进行显示。 - 采用了光栅化
layer.shouldRasterize
shouldRasterize 的使用建议:
- 如果layer不能被复用,则没必要开启光栅化。
- 如果layer不是静态的,需要被频繁修改,比如处于动画之中,开启离屏渲染,会影响效率。
- 离屏渲染缓存内容有时间限制,如果缓存内容
100ms没有被使用,那么就会被抛弃,无法进行复用。 - 离屏渲染空间有限,屏幕像素的
2.5倍,超过了这个大小,无法被缓存
如何解决
官方对离屏渲染产生性能问题也进行了优化: iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染。 iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。
不对layer使用maskToBounds/clipsToBounds,例如imageView
imageView.clipsToBounds = YES;
imageView.layer.cornerRadius = 50.f;
使用贝塞尔曲线(UIBezierPath)绘制圆图
[[UIBezierPath bezierPathWithRoundedRect:imageView.boundscornerRadius:imageView.frame.size.width]addClip];
[imageView drawRect:imageView.bounds];
imageView.image=UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
使用CAShapeLayer和UIBezierPath设置圆角
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//设置大小
maskLayer.frame = imageView.bounds;
//设置图形样子
maskLayer.path = maskPath.CGPath;
imageView.layer.mask = maskLayer;
[self.view addSubview:imageView];
我们可以知道CAShapeLayer是CALayer的子类,可以使用CALayer的所有属性,但是只有配合UIBezierPath才会起作用,使用CAShapeLayer(属于CoreAnimation)与贝塞尔曲线可以实现不在view的drawRect(继承于CoreGraphics走的是CPU,消耗的性能较大)方法中画出一些想要的图形,CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用情况。
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。
imageView.layer.shadowColor=[UIColorgrayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];
imageView.layer.shadowPath=path.CGPath;
YYImage开源库,对imageView的圆角也可以研究一下。
离屏渲染真的一无是处么?
离屏渲染 离屏渲染并不是一无是处的,虽然会造成很多额外的开销,但也是为了充分利用设备的资源来保证界面的流畅。发生离屏渲染时,是为了引起开发者对性能的关注,减少不必要的透明视图层级。
