我相信大多数做过iOS开发的都应该知道离屏渲染,也知道常见的造成离屏渲染的方式,但是对于它的原理可能了解的并不是很多,接下来我带大家了解一下。
一、了解离屏渲染
正常渲染流程如下:
图片来自网络
在正常情况下,经过CPU的计算以及GPU的渲染之后,会将结果存放到帧缓存区,随后视频控制器会读取帧缓存区的数据,经过数模转换,再逐行显示到屏幕上。
离屏渲染流程如下:图片来自网络
但是在有些情况下,系统会在屏幕以外开辟缓存区来存放一些中间状态的数据,等待全部的图层都渲染到离屏缓存区之后,分别从各个离屏缓存区取出数据,分别做相应的操作(裁剪等)之后,组合存入帧缓存区,再等待屏幕控制器的读取和屏幕刷新。
二、离屏渲染的利弊
1.劣势:
离屏渲染其实是加大了系统的负担,确实会造成性能上的损耗。主要表现在以下几个方面。
离屏渲染需要额外的存储空间,存储空间大小的上限是2.5倍的屏幕像素大小,一旦超过,则无法进行复用
离屏渲染缓存内容有时间限制,缓存内容100ms如果没有使用,那么它就会被丢弃,无法进行复用
容易掉帧:一旦因为离屏渲染导致最终存入帧缓存区的时候,超过了16.67ms,则会出现掉帧的情况
2.优势
虽然离屏渲染会需要多开辟出新的临时缓存区来存储中间状态,但是对于多次出现在屏幕上的数据,可以提前渲染好,从而达到复用的目的,这样CPU/GPU就不用做一些重复的计算。
其实在很多iOS开发的需求背景之下,比如 一些特殊动画效果的开发,此时需要多图层以及离屏缓存区保存中间状态,这种情况下就不得不使用离屏渲染
三、常见触发离屏渲染的几种方式:
1.使用了 mask 的 layer (layer.mask)
2.需要进行裁剪的 layer (layer.masksToBounds /view.clipsToBounds)
3.设置了组透明度为 YES,并且透明度不为 1 的layer (layer.allowsGroupOpacity/ layer.opacity)
4.添加了投影的 layer (layer.shadow*)
5.采用了光栅化的 layer (layer.shouldRasterize)
6.绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
在使用光栅化的时候,有以下建议:
1、如果layer不能被重用,则没必要使用光栅化;
2、如果我们更新已光栅化的layer,会造成大量的离屏渲染。例如UITableViewCell因为复用的原因,重绘是很频繁的。如果此时设置了光栅化,反而会造成大量离屏渲染,降低性能;
3、离屏渲染的缓存是有时间限制的,100ms内如果缓存的内容没有被复用,则会被丢弃,也就无法复用了;
4、离屏渲染的空间有限,超过2.5倍屏幕像素的大小,离屏渲染也会失效,无法复用
四、分析圆角触发离屏渲染案例
1.代码如下:
//1.按钮存在背景图片
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
btn1.frame=CGRectMake(100,30,100,100);
btn1.layer.cornerRadius = 50;
[self.view addSubview:btn1];
[btn1setImage:[UIImage imageNamed:@"btn.png"] forState:UIControlStateNormal];
btn1.clipsToBounds=YES;
//2.按钮不存在背景图片
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
btn2.frame=CGRectMake(100,180,100,100);
btn2.layer.cornerRadius = 50;
btn2.backgroundColor = [UIColor blueColor];
[self.view addSubview:btn2];
btn2.clipsToBounds=YES;
//3.UIImageView 设置了图片+背景色;
UIImageView *img1 = [[UIImageView alloc]init];
img1.frame=CGRectMake(100,320,100,100);
img1.backgroundColor = [UIColor blueColor];
[self.view addSubview:img1];
img1.layer.cornerRadius = 50;
img1.layer.masksToBounds = YES;
img1.image= [UIImage imageNamed:@"btn.png"];
//4.UIImageView 只设置了图片,无背景色;
UIImageView *img2 = [[UIImageView alloc]init];
img2.frame=CGRectMake(100,480,100,100);
[self.view addSubview:img2];
img2.layer.cornerRadius = 50;
img2.layer.masksToBounds = YES;
img2.image= [UIImage imageNamed:@"btn.png"];
我们可以发现上面的1和3会造成离屏渲染
五、如何检测离屏渲染呢
六、圆角导致的离屏渲染解决方案
方案一:
_imageView.clipsToBounds=YES;
_imageView.layer.cornerRadius=4.0;
方案二:
方案三:
方案四:
最后大家也可以参考下YYImage中对于圆角的处理代码:
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor*)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin {
if (corners != UIRectCornerAllCorners) {
UIRectCorner tmp = 0;
if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
corners = tmp;
}
UIGraphicsBeginImageContextWithOptions(self.size,NO,self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRectrect = CGRectMake(0,0,self.size.width,self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloatminSize =MIN(self.size.width,self.size.height);
if(borderWidth < minSize /2) {
UIBezierPath*path = [UIBezierPathbezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth)byRoundingCorners:corners
cornerRadii:CGSizeMake(radius, borderWidth)];
[pathclosePath];
CGContextSaveGState(context);
[pathaddClip];
CGContextDrawImage(context, rect,self.CGImage);
CGContextRestoreGState(context);
}
if(borderColor && borderWidth < minSize /2&& borderWidth >0) {
CGFloatstrokeInset = (floor(borderWidth *self.scale) +0.5) /self.scale;
CGRectstrokeRect =CGRectInset(rect, strokeInset, strokeInset);
CGFloatstrokeRadius = radius >self.scale /2? radius -self.scale /2:0;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius,
borderWidth)];
[pathclosePath];
path.lineWidth= borderWidth;
path.lineJoinStyle= borderLineJoin;
[borderColorsetStroke];
}
网友评论