页面卡顿的优化--圆角

2,883 阅读4分钟

今天产品经理告诉我:诶,那个谁,这个界面很卡诶!!!你看看什么情况。。。于是我掏出了Instrument里的Core Animation看看FPS,发现滑动的时候FPS特别低Orz!

产生卡顿的原因

首先,查阅资料看下为什么会产生卡顿的原因。

渲染机制.jpg
在 iOS 系统中,图像内容展示到屏幕的过程需要 CPU 和 GPU 共同参与。CPU 负责计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。之后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。 那GPU什么时候会消耗呢,相对于CPU,GPU做的事情比较单一,接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,然后输出到屏幕上。宽泛的说,大多数 CALayer 的属性都是用 GPU 来绘制。以下操作会降低GPU的性能:

大量几何结构

所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不论是提交到显存的过程,还是 GPU 调整和渲染 Texture 的过程,都要消耗不少 GPU 资源。当在较短时间显示大量图片时(比如 TableView 存在非常多的图片并且快速滑动时),CPU 占用率很低,GPU 占用非常高,界面仍然会掉帧。避免这种情况的方法只能是尽量减少在短时间内大量图片的显示,尽可能将多张图片合成为一张进行显示。 另外当图片过大,超过 GPU 的最大纹理尺寸时,图片需要先由 CPU 进行预处理,这对 CPU 和 GPU 都会带来额外的资源消耗。

视图的混合

当多个视图(或者说 CALayer)重叠在一起显示时,GPU 会首先把他们混合到一起。如果视图结构过于复杂,混合的过程也会消耗很多 GPU 资源。为了减轻这种情况的 GPU 消耗,应用应当尽量减少视图数量和层次,并且减少不必要的透明视图。

离屏渲染

离屏渲染是指图层在被显示之前是在当前屏幕缓冲区以外开辟的一个缓冲区进行渲染操作。 离屏渲染需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动作。 会造成 offscreen rendering 的原因有: 阴影(UIView.layer.shadowOffset/shadowRadius/…) 圆角(当 UIView.layer.cornerRadius 和 UIView.layer.maskToBounds 一起使用时) 图层蒙板 开启光栅化(shouldRasterize = true)

圆角优化

所以我们找到了原因,由于我们的项目是基于地图的,在地图上直接添加页面上去,之前地图就有很多圆角设置,加上cell上的圆角,会造成GPU性能的损耗,所以相对来说页面的滑动会损耗GPU啦。 于是回到了老生常谈的问题了,关于圆角的优化。 之前看文章有说这样去优化:

+ (void)cutRadiousWithView:(UIView *)view radious:(CGFloat)radious {
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radious, radious)];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
    //设置大小
    maskLayer.frame = view.bounds;
    //设置图形样子
    maskLayer.path = maskPath.CGPath;
    view.layer.mask = maskLayer;
}

然而发现并没有什么用,其实mask遮罩还是会发生离屏渲染的。而且亲测这样的FPS貌似更低???(黑人问号脸)

图片圆角优化

于是我继续查资料,发现图片的圆角可以将图片进行进行重绘,得到一张新的图片。方法如下:

+ (UIImage *)cutCircleImageWithImage:(UIImage *)image size:(CGSize)size radious:(CGFloat)radious {
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);

    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    CGContextAddPath(UIGraphicsGetCurrentContext(),
                 [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radious].CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());

    [image drawInRect:rect];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

用这个方法可以得到新的图片,对于图片的圆角处理可以这样做,但是对于View的呢?

View圆角优化

View的话可以给他盖一层ImageView,设置下Imageview的图片,方法如下:

+ (void)cutCicleViewWithView:(UIView *)view radious:(CGFloat)radius {
    CGSize size = view.frame.size;
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    UIColor *bkColor = view.backgroundColor;

    UIImage *image = [[UIImage alloc] init];
    UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, bkColor.CGColor);
    CGContextAddPath(context,
                 [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
    CGContextDrawPath(context, kCGPathFill);
    [image drawInRect:rect];
    UIImage *output = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:rect];
    imageView.image = output;
    [view insertSubview:imageView atIndex:0];
    view.backgroundColor = [UIColor clearColor];
}

总结

相信学过算法的同学都知道,越快的算法,可能空间复杂度越高。当你为了某一性能去优化,必然会损耗其他的性能,所谓的算法优化是为了达到最佳实现效果。所以这样优化GPU的结果就是会损耗CPU。 过早的优化是魔鬼,但是到问题出来的时候 ,有些东西就有必要去做了!

参考资料:

http://www.reviewcode.cn/article.html?reviewId=7 http://ios.jobbole.com/92237/