參考文章:參考文章
1. 檢測工具:Core Animation工具檢測離屏渲染
可以在Xcode->Open Develeper Tools->Instruments中找到(或者直接 ?cmd + i ),如下圖
我們需要了解兩個區(qū)域:
1. 這里記錄了實時的fps數(shù)值陡舅,有些地方是0是因為屏幕沒有滑動
2. 這是重中之重
有過游戲經(jīng)驗的人也許對fps這個概念比較熟悉。我們知道任何屏幕總是有一個刷新率,比如iphone推薦的刷新率是60Hz扫夜,也就是說GPU每秒鐘刷新屏幕60次厌小,因此兩次刷新之間的間隔為16.67ms绷耍。這段時間內(nèi)屏幕內(nèi)容保持不變锌蓄,稱為一幀(frame)安岂,fps表示frames per second驾荣,也就是每秒鐘顯示多少幀畫面外构。對于靜止不變的內(nèi)容,我們不需要考慮它的刷新率播掷,但在執(zhí)行動畫或滑動時审编,fps的值直接反映出滑動的流暢程度。
Color Blended Layers:正是用于檢測哪里發(fā)生了圖層混合歧匈,并用紅色標記出來垒酬。因此我們需要盡可能減少看到的紅色區(qū)域。一旦發(fā)現(xiàn)應該想法設法消除它件炉。
Color Hits Green and Misses Red:如果勾選這個選項勘究,且當我們代碼中有設置xxx.layer.shouldRasterize為YES,那么紅色代表沒有復用離屏渲染的緩存斟冕,綠色則表示復用了緩存口糕。顯然綠色越多越好,紅色越少越好磕蛇。我們當然希望能夠復用景描。
Color Copied Images:按照官方的說法,當圖片的顏色格式GPU不支持的時候秀撇,Core Animation會拷貝一份數(shù)據(jù)讓CPU進行轉(zhuǎn)化超棺。
Color Immediately:默認情況下Core Animation工具以每毫秒10次的頻率更新圖層調(diào)試顏色,如果勾選這個選項則移除10ms的延遲捌袜。對某些情況需要這樣说搅,但是有可能影響正常幀數(shù)的測試。
Color Misaligned Images:勾選此項虏等,如果圖片需要縮放則標記為黃色弄唧,如果沒有像素對齊則標記為紫色适肠。像素對齊我們已經(jīng)在上面有所介紹。
Color Offscreen-Rendered Yellow:用來檢測離屏渲染的候引,如果顯示黃色侯养,表示有離屏渲染。當然還要結(jié)合Color Hits Green and Misses Red來看澄干,是否復用了緩存逛揩。大部分情況下我們需要盡可能避免黃色的出現(xiàn)。離屏渲染可能會自動觸發(fā)麸俘,也可以手動觸發(fā)辩稽。以下情況可能會導致觸發(fā)離屏渲染:
1. 重寫drawRect方法
2. 有mask或者是陰影(layer.masksToBounds, layer.shadow*),模糊效果也是一種mask
3. layer.shouldRasterize = true
前兩者會自動觸發(fā)離屏渲染从媚,第三種方法是手動開啟離屏渲染逞泄。
Color Compositing Fast-Path Blue:用于標記由硬件繪制的路徑,藍色越多越好拜效。如果不顯示藍色則表示使用了CPU渲染喷众,繪制在了屏幕外,顯示藍色表示正常紧憾。
Flash Updated Regions:用于標記發(fā)生重繪的區(qū)域, 當對圖層重繪的時候回顯示黃色到千,如果頻繁發(fā)生則會影響性能。一個典型的例子是系統(tǒng)的時鐘應用赴穗,絕大多數(shù)時候只有顯示秒針的區(qū)域需要重繪憔四。
2. 為什么會有離屏渲染:
高中物理應該學過顯示器是如何顯示圖像的:需要顯示的圖像經(jīng)過CRT電子槍以極快的速度一行一行的掃描,掃描出來就呈現(xiàn)了一幀畫面望抽,隨后電子槍又會回到初始位置循環(huán)掃描加矛,形成了我們看到的圖片或視頻。
為了讓顯示器的顯示跟視頻控制器同步煤篙,當電子槍新掃描一行的時候斟览,準備掃描的時發(fā)送一個水平同步信號(HSync信號),顯示器的刷新頻率就是HSync信號產(chǎn)生的頻率辑奈。然后CPU計算好frame等屬性苛茂,將計算好的內(nèi)容交給GPU去渲染,GPU渲染好之后就會放入幀緩沖區(qū)鸠窗。然后視頻控制器會按照HSync信號逐行讀取幀緩沖區(qū)的數(shù)據(jù)妓羊,經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器,就顯示出來了稍计。具體的大家自行查找資料或詢問相關(guān)專業(yè)人士躁绸,這里只參考網(wǎng)上資料做一個簡單的描述。
離屏渲染的代價很高,想要進行離屏渲染净刮,首選要創(chuàng)建一個新的緩沖區(qū)剥哑,屏幕渲染會有一個上下文環(huán)境的一個概念,離屏渲染的整個過程需要切換上下文環(huán)境淹父,先從當前屏幕切換到離屏株婴,等結(jié)束后,又要將上下文環(huán)境切換回來暑认。這也是為什么會消耗性能的原因了困介。
由于垂直同步的機制,如果在一個 HSync 時間內(nèi)蘸际,CPU 或者 GPU 沒有完成內(nèi)容提交座哩,則那一幀就會被丟棄,等待下一次機會再顯示粮彤,而這時顯示屏會保留之前的內(nèi)容不變八回。這就是界面卡頓的原因。
3. 屏幕渲染的方式:
OpenGL中驾诈,GPU屏幕渲染有兩種方式:
(1)On-Screen Rendering (當前屏幕渲染)
指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區(qū)進行。
(2)Off-Screen Rendering (離屏渲染)
指的是在GPU在當前屏幕緩沖區(qū)以外開辟一個緩沖區(qū)進行渲染操作溶浴。
相比于當前屏幕渲染乍迄,離屏渲染的代價是很高的,主要體現(xiàn)在兩個方面:
(1)創(chuàng)建新緩沖區(qū)
要想進行離屏渲染士败,首先要創(chuàng)建一個新的緩沖區(qū)闯两。
(2)上下文切換
離屏渲染的整個過程,需要多次切換上下文環(huán)境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen)谅将,等到離屏渲染結(jié)束以后漾狼,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上有需要將上下文環(huán)境從離屏切換到當前屏幕。而上下文環(huán)境的切換是要付出很大代價的饥臂。
特殊的“離屏渲染”:CPU渲染
如果我們重寫了drawRect方法焕檬,并且使用任何Core Graphics的技術(shù)進行了繪制操作誉简,就涉及到了CPU渲染。整個渲染過程由CPU在App內(nèi)同步地完成,渲染得到的bitmap(位圖)最后再交由GPU用于顯示懂昂。
4. 下面的情況或操作會引發(fā)離屏渲染:
為圖層設置遮罩(layer.mask)
將圖層的layer.masksToBounds / view.clipsToBounds屬性設置為true
將圖層layer.allowsGroupOpacity屬性設置為YES和layer.opacity小于1.0
為圖層設置陰影(layer.shadow *)。
為圖層設置layer.shouldRasterize=true
具有l(wèi)ayer.cornerRadius磁携,layer.edgeAntialiasingMask酌摇,layer.allowsEdgeAntialiasing的圖層
文本(任何種類,包括UILabel驰坊,CATextLayer匾二,Core Text等)。
使用CGContext在drawRect :方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現(xiàn)察藐。
5.?優(yōu)化方案:
蘋果官方:
iOS 9.0 之前UIimageView跟UIButton設置圓角都會觸發(fā)離屏渲染皮璧。
iOS 9.0 之后UIButton設置圓角會觸發(fā)離屏渲染,而UIImageView里png圖片設置圓角不會觸發(fā)離屏渲染了转培,如果設置其他陰影效果之類的還是會觸發(fā)離屏渲染的恶导。
圓角優(yōu)化:
方案1. 使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角
UIImageView *imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImageimageNamed:@"myImg"];
//開始對imageView進行畫圖
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size,NO,1.0);
//使用貝塞爾曲線畫出一個圓形圖
[[UIBezierPathbezierPathWithRoundedRect:imageView.boundscornerRadius:imageView.frame.size.width]addClip];
[imageViewdrawRect:imageView.bounds];
imageView.image=UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束畫圖
UIGraphicsEndImageContext();
[self.viewaddSubview:imageView];
方案2:使用CAShapeLayer和UIBezierPath設置圓角
UIImageView *imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImageimageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayeralloc]init];
//設置大小
maskLayer.frame=imageView.bounds;
//設置圖形樣子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.viewaddSubview:imageView];
[說明]:
CAShapeLayer繼承于CALayer,可以使用CALayer的所有屬性值;
CAShapeLayer需要貝塞爾曲線配合使用才有意義(也就是說才有效果)
使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
CAShapeLayer動畫渲染直接提交到手機的GPU當中浸须,相較于view的drawRect方法使用CPU渲染而言惨寿,其效率極高,能大大優(yōu)化內(nèi)存使用情況删窒。
總的來說就是用CAShapeLayer的內(nèi)存消耗少裂垦,渲染速度快,建議使用方案2肌索。
總結(jié):
避免圖層混合
1. 確苯堵#控件的opaque屬性設置為true,確保backgroundColor和父視圖顏色一致且不透明
2. 如無特殊需要诚亚,不要設置低于1的alpha值
3. 確保UIImage沒有alpha通道
避免臨時轉(zhuǎn)換
1. 確保圖片大小和frame一致晕换,不要在滑動時縮放圖片
2. 確保圖片顏色格式被GPU支持,避免勞煩CPU轉(zhuǎn)換