美文网首页
iOS 离屏渲染认知及解决方案

iOS 离屏渲染认知及解决方案

作者: 丸疯 | 来源:发表于2020-07-07 16:53 被阅读0次

    离屏渲染

    要理解离屏渲染,需要先了解两个名词,OffScreen Buffer(离屏缓冲区),Frame Buffer(帧缓冲区)。

    • 一般情况下,系统把需要显示位图的直接放到Frame Buffer,然后显示出来。
      一般情况下显示流程
    • 当出现离屏渲染之后,会出现一个一个新的缓冲区OffScreen Buffer,在OffScreen Buffer完成在Frame Buffer完成不了的操作,然后提交到帧缓冲区,最后显示出来
      当出现离屏渲染的时候的流程

    为什么离屏渲染会产生性能问题?

    1. 产生了离屏渲染,会开额外的内存空间OffScreen Buffer
    2. OffScreen Buffer进行渲染然后提交到Frame Buffer需要时间

    • OffScreen Buffer空间是有大小限制的,屏幕像素的2.5倍

    产生的原因

    可能大家都知道设置圆角就有可能会产生离屏渲染,可是为什么设置圆角就有可能会产生离屏渲染呢?我们结合下面两张图来理解:

    Apple关于核心动画,给的一个图层介绍
    我们在显示一个图层的时候其实包括了三部分内容,backgroundColor,contents,borderWith/borderColor

    Apple对cornerRadius的解释

    Apple对cornerRadius的解释
    我们可以看到,我们通过cornerRadius设置圆角的时候,其实只是对background起作用,如果contents也需要设置圆角,需要设置maskToBounds属性。
    接下来在代码中来看一下这个问题
    两张ImageView设置圆角,一个出现了离屏渲染,一个没有
    看下代码
        // 设置了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];
    

    当我们只设置了cornerRadiusmaskToBounds并没有发生离屏渲染,但当我们同时设置了background这时就会产生离屏渲染。

    根本原因就是我们在没有设置backgroundColor的时候,默认是不需要对background层进行处理,在imageView里面我们只需要渲染出content,然后进行切maskToBounds,放入Frame Buffer能然后就能直接显示出来了。一旦我们设置了background,我们需要对background进行渲染,对contents进行渲染,然后进行混合运算后做圆角处理,再提交到Frame Buffer,进行显示。因为在同一layer下出现了多个图层,圆角的剪切是需要进行混合运算再剪切,通过上面的图例我们知道ImageView其实是有background, contentsmaskToBounds其实是基于cornerRadius来做出运算后对contents切圆角,当我们没有设置backgroundColor也就没有必要去渲染background,对contents基于cornerRadius运算后得出圆角提交帧缓冲区(Frame Buffer)就行了。这也默认是对内存的一种优化,只有当遇到同一Layer下多层图层,直接提交Screen Buffer处理不了的融合显示,才会产生离屏渲染,开辟OffScreen Buffer,在OffScreen Buffer进行相应的各种算法。

    哪些情况会触发离屏渲染

    1. layer使用了mask(遮罩)。
    2. layer使用了masksToBoundsclipsToBounds
    3. layer添加了投影。
    4. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)。
    5. 设置了不为1的组透明度。
      因为透明度不为1,像素点颜色的混合运算,需要等这几个不同透明度的图层都渲染好了,提交到Screen Buffer再进行运算,最后提交Frame Buffer进行显示。
    6. 采用了光栅化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];
    

    使用CAShapeLayerUIBezierPath设置圆角

    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];
    

    我们可以知道CAShapeLayerCALayer的子类,可以使用CALayer的所有属性,但是只有配合UIBezierPath才会起作用,使用CAShapeLayer(属于CoreAnimation)与贝塞尔曲线可以实现不在viewdrawRect(继承于CoreGraphics走的是CPU,消耗的性能较大)方法中画出一些想要的图形,CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于viewdrawRect方法使用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的圆角也可以研究一下。

    离屏渲染真的一无是处么?

    离屏渲染
    离屏渲染并不是一无是处的,虽然会造成很多额外的开销,但也是为了充分利用设备的资源来保证界面的流畅。发生离屏渲染时,是为了引起开发者对性能的关注,减少不必要的透明视图层级。

    怎么查看当前显示是否有发生离屏渲染

    Simulator菜单Debug模式中打开Color Off-Screen Rendered

    查看是否产生离屏渲染的方法.png

    相关文章

      网友评论

          本文标题:iOS 离屏渲染认知及解决方案

          本文链接:https://www.haomeiwen.com/subject/hkwgqktx.html