在進行iOS
的應用開發過程中,有時候會出現卡頓的問題,雖然iOS
設備的性能越來越高,但是卡頓的問題還是有可能會出現,而離屏渲染是造成卡頓的原因之一。因此,本文主要分析一下離屏渲染產生的原因及避免的方法,最后介紹一下Xcode
自帶的分析離屏渲染的工具Instruments
的使用。
UIView和CALayer關系
UIView
繼承自UIResponder
,可以處理系統傳遞過來的事件,如:UIApplication
、UIViewController
、UIView
,以及所有從UIView
派生出來的UIKit
類。每個UIView
內部都有一個CALayer
提供內容的繪制和顯示,並且作為內部RootLayer
的代理視圖。
CALayer
繼承自NSObject
類,負責顯示UIView
提供的內容contents
。CALayer
有三個視覺元素:背景色、內容和邊框,其中,內容的本質是一個CGImage
。
下圖為CALayer
的結構圖:
界面渲染過程
RunLoop
有一個60fps
的回調,即每16.7ms
繪制一次屏幕,所以view
的繪制必須在這個時間內完成,view
內容的繪制是CPU
的工作,然后把繪制的內容交給GPU
渲染,包括多個View
的拼接(Compositing
)、紋理的渲染(Texture
)等等,最后顯示在屏幕上。但是,如果無法是16.7ms
內完成繪制,就會出現丟幀的問題,一般情況下,如果幀率保證在30fps
以上,界面卡頓效果不明顯,那么就需要在33.4ms
內完成View
的繪制,而低於這個幀率,就會產生卡頓的效果,影響體驗。
渲染的過程如下:
UIView
的layer
層有一個content
,指向一塊緩存,即backing store
UIView
繪制時,會調用drawRect
方法,通過context
將數據寫入backing store
- 在
backing store
寫完后,通過render server
交給GPU
去渲染,將backing store
中的bitmap
數據顯示在屏幕上
離屏渲染
在使用圓角、陰影和遮罩等視圖功能的時候,圖層屬性的混合體被指定為在未預合成之前不能直接在屏幕中繪制,所有就需要在屏幕外的上下文中渲染,即離屏渲染。
離屏渲染卡頓原因
離屏渲染之所以會特別消耗性能,是因為要創建一個屏幕外的緩沖區,然后從當屏緩沖區切換到屏幕外的緩沖區,然后再完成渲染;其中,創建緩沖區和切換上下文最消耗性能,而繪制其實不是性能損耗的主要原因。
設置了以下屬性時,就會觸發離屏繪制:
- shouldRasterize(光柵化)
- masks(遮罩)
- shadows(陰影)
- edge antialiasing(抗鋸齒)
- group opacity(不透明)
- 復雜形狀設置圓角等
- 漸變
屏幕渲染類型
CPU
計算好顯示內容提交到GPU
,GPU
渲染完成后將渲染結果放入幀緩沖區,隨后視頻控制器會按照 VSync
信號逐行讀取幀緩沖區的數據,經過可能的數模轉換傳遞給顯示器顯示。
屏幕渲染有如下三種:
GPU
中的屏幕渲染:
1、On-Screen Rendering
意為當前屏幕渲染,指的是GPU
的渲染操作是在當前用於顯示的屏幕緩沖區中進行
2、Off-Screen Rendering
意為離屏渲染,指的是GPU
在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作
3、CPU
中的離屏渲染(特殊離屏渲染,即不在GPU
中的渲染)
如果我們重寫了drawRect
方法,並且使用任何Core Graphics
的技術進行了繪制操作,就涉及到了CPU
渲染
CoreGraphic通常是線程安全的,所以可以進行異步繪制,顯示的時候再放回主線程
切圓角優化
切圓角是開發app
過程中經常會用到的功能,但是使用不同的方式,性能損耗也會不同,下面會介紹3
種切圓角的方法;其中,方法三的性能相對最好。
方法一
使用cornerRadius
進行切圓角,在iOS9
之前會產生離屏渲染,比較消耗性能,而之后系統做了優化,則不會產生離屏渲染,但是操作最簡單
iv.layer.cornerRadius = 30;
iv.layer.masksToBounds = YES;
方法二
利用mask
設置圓角,利用的是UIBezierPath
和CAShapeLayer
來完成
CAShapeLayer *mask1 = [[CAShapeLayer alloc] init];
mask1.opacity = 0.5;
mask1.path = [UIBezierPath bezierPathWithOvalInRect:iv.bounds].CGPath;
iv.layer.mask = mask1;
方法三
利用CoreGraphics
畫一個圓形上下文,然后把圖片繪制上去,得到一個圓形的圖片,達到切圓角的目的。
- (UIImage *)drawCircleImage:(UIImage*)image
{
CGFloat side = MIN(image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale);
CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
CGContextClip(UIGraphicsGetCurrentContext());
CGFloat marginX = -(image.size.width - side) * 0.5;
CGFloat marginY = -(image.size.height - side) * 0.5;
[image drawInRect:CGRectMake(marginX, marginY, image.size.width, image.size.height)];
CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Instruments使用
iOS
的性能調試有許多方法,而官方提供的Instruments
是一個強大的性能調試工具,可以分析如下功能:內存、核心動畫、自動化、布局、網絡等等,本文主要介紹一下利用Core Animation
來分析應用的性能問題。
下面介紹一下Core Animation
中的Debug
屬性的部分功能,這些功能在分析離屏渲染等性能問題時十分有用:
Color Blended Layers
這個選項基於渲染程度對屏幕中的混合區域進行綠到紅的高亮(也就是多個半透明圖層的疊加)。由於重繪的原因,混合對GPU
性能會有影響,同時也是滑動或者動畫幀率下降的罪魁禍首之一
Color Hits Green and Misses Red
當設置shouldRasterizep
屬性為YES
的時候,耗時的圖層繪制會被緩存,然后當做一個簡單的扁平圖片呈現。當緩存再生的時候這個選項就用紅色對柵格化圖層進行了高亮。如果緩存頻繁再生的話,就意味着柵格化可能會有負面的性能影響了
Color Offscreen-Rendered Yellow
開啟后會把那些需要離屏渲染的圖層高亮成黃色,這就意味着黃色圖層可能存在性能問題
當然Debug
還有其它的選項,來分析不同的性能問題,如有需求,請參考其它資料。
iOS
離屏渲染的性能分析到此結束,文中如有不足之處,歡迎指出,共同進步。(文中部分圖片來自互聯網,版權歸原作者所有)
參考資料
iOS開發之圖形渲染分析、離屏渲染、當前屏幕渲染、On-Screen Rendering、Off-Screen Rendering