离屏渲染产生的原因?
- 非离屏渲染流程:
%E6%BB%91%E5%8A%A8%E5%8D%A1%E9%A1%BF%E4%BC%98%E5%8C%96%E7%A6%BB%E5%B1%8F%E6%B8%B2%E6%9F%93/screen1.jpg)
- 离屏渲染流程:
%E6%BB%91%E5%8A%A8%E5%8D%A1%E9%A1%BF%E4%BC%98%E5%8C%96%E7%A6%BB%E5%B1%8F%E6%B8%B2%E6%9F%93/screen2.jpg)
- 如果触发了离屏渲染,那么在显示这个页面的每一帧都会去做离屏渲染的处理,比如切圆角,显示阴影等,如果每一帧都要重新去处理,那么对于CPU以及GPU都带来很大的负担。所以引入了离屏缓冲区,将处理好的图层直接丢到离屏缓冲区,下次渲染的时候直接拿出来显示到屏幕上
- 离屏缓存区也是有限制的,
- 时间限制:缓存的内容超过100ms没有被使用,内容将会被丢弃;
- 空间限制:超过屏幕像素大小的2.5倍,将不能再存储新的数据,离屏渲染会失效
离屏检测?
- 打开模拟器后,选择菜单栏
Debug->Color off-screen rendered,开启后,如果有离屏渲染,则会显示成黄色%E6%BB%91%E5%8A%A8%E5%8D%A1%E9%A1%BF%E4%BC%98%E5%8C%96%E7%A6%BB%E5%B1%8F%E6%B8%B2%E6%9F%93/screen3.jpg)
触发离屏渲染的场景
layer.cornerRadius+layer.masksToBounds- 如果只有一个图层,就算同时设置 cornerRadius + masksToBounds 也并不会触发离屏渲染,只有多个图层发生叠加的时候才会触发离屏渲染
- 只设置cornerRadius不会触发离屏渲染,因为cornerRadius只会设置backgroundColor和border的圆角,并不会对content设置圆角,CALayer大致分为三层(backgroundColor层、content层、border层),
- 关于圆角,iOS 9及之后的系统版本,苹果进行了一些优化,只设置contents或者UIImageView的image,并加上圆角+裁剪,是不会产生离屏渲染的。但如果加上了背景色、边框或其他有图像内容的图层,还是会产生离屏渲染。
- 为图层设置遮罩
layer.mask layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0- 为图层设置阴影(
layer.shadow)- 使用阴影必须保证 layer 的masksToBounds = false,因此阴影与系统圆角不兼容
- 给阴影指定了路径,就不会触发离屏渲染了,例如,这样设置阴影:
1
2
3
4
5
6self.view1.layer.shadowColor = [UIColor greenColor].CGColor;
self.view1.layer.shadowOffset = CGSizeMake(5, 5);
self.view1.layer.shadowOpacity = 0.5;
self.view1.layer.shadowRadius = 3;
UIBezierPath * path = [UIBezierPath bezierPathWithRect:self.view1.bounds];
self.view1.layer.shadowPath = path.CGPath; - 使用
CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现
离屏渲染优化
-
关于圆角,iOS 9及之后的系统版本,苹果进行了一些优化,只设置contents或者UIImageView的image,并加上圆角+裁剪,是不会产生离屏渲染的。但如果加上了背景色、边框或其他有图像内容的图层,还是会产生离屏渲染。
-
利用UIBezierPath(CoreGraphics框架)画出来圆角图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17- (UIImage *)imageWithCornerRadius:(CGFloat)radius ofSize:(CGSize)size{
/* 当前UIImage的可见绘制区域 */
CGRect rect = (CGRect){0.f,0.f,size};
/* 创建基于位图的上下文 */
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
/* 在当前位图上下文添加圆角绘制路径 */
CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
/* 当前绘制路径和原绘制路径相交得到最终裁剪绘制路径 */
CGContextClip(UIGraphicsGetCurrentContext());
/* 绘制 */
[self drawInRect:rect];
/* 取得裁剪后的image */
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
/* 关闭当前位图上下文 */
UIGraphicsEndImageContext();
return image;
} -
YYImage对图片圆角的处理方法是值得推荐的,附上实现源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49- (UIImage *)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();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
[path closePath];
CGContextSaveGState(context);
[path addClip];
CGContextDrawImage(context, rect, self.CGImage);
CGContextRestoreGState(context);
}
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
[path closePath];
path.lineWidth = borderWidth;
path.lineJoinStyle = borderLineJoin;
[borderColor setStroke];
[path stroke];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
} -
AsyncDisplayKit指定layer阴影效果路径使用异步进行layer渲染