README:
引言: 一款優(yōu)秀的app,流暢很關(guān)鍵,用戶使用60的fps的app,跟使用30的fps的app感受是完全不一樣的.類似于 半糖 這種優(yōu)秀的應(yīng)用肯定花了大把精力去優(yōu)化界面.網(wǎng)上關(guān)于優(yōu)化的界面的文章一搜一大把.本文并不是講界面優(yōu)化的,優(yōu)化的話推薦下面幾篇文章;
YYKit作者: "iOS 保持界面流暢的技巧" (我相信認(rèn)真看一定有收獲!)
離屏渲染的優(yōu)化 (這篇文章很強(qiáng))
有些東西研究起來挺費(fèi)勁的.但是想要更多的收獲,還真就必須得研究.知識(shí)零零散散,時(shí)間久了再次用起來難免會(huì)生疏,又得重新找資料,看文章.很麻煩.索性就整理個(gè)比較全面的知識(shí)點(diǎn)供以后復(fù)習(xí).
-
屏幕渲染
- OpenGL中,GPU屏幕渲染有兩種方式.
-
On-Screen Rendering (當(dāng)前屏幕渲染)
指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行.
-
Off-Screen Rendering (離屏渲染)
指的是在GPU在當(dāng)前屏幕緩沖區(qū)以外開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作.
幾個(gè)名詞 "GPU" " 緩沖區(qū)" .
不知道GPU的就自行百度吧 (- - 寶寶說不清). 說下緩沖區(qū).要明白緩沖區(qū),首先就得要知道圖像顯示出來的原理,本文的第一篇博客里面介紹的很詳細(xì).
顯示器 顯示出來的圖像是經(jīng)過 CRT電子槍一行一行的掃描.(可以是橫向的也可以是縱向 ,具體CRT電子槍又是什么,百度文庫(kù)介紹的很詳細(xì).),掃描出來就呈現(xiàn)了一幀畫面,隨后電子槍又會(huì)回到初始位置循環(huán)掃描,為了讓顯示器的顯示跟視頻控制器同步,當(dāng)電子槍新掃描一行的時(shí)候.準(zhǔn)備掃描的時(shí)候,會(huì)發(fā)送一個(gè) 水平同步信號(hào)(HSync信號(hào)),而當(dāng)一幀畫面繪制完成后,電子槍回復(fù)到原位丰辣,準(zhǔn)備畫下一幀前骡尽,顯示器會(huì)發(fā)出一個(gè)垂直同步信號(hào)(vertical synchronization簡(jiǎn)稱 VSync),顯示器一般是固定刷新頻率的,這個(gè)刷新的頻率其實(shí)就是VSync信號(hào)產(chǎn)生的頻率. 然后CPU計(jì)算好frame等屬性,就將計(jì)算好的內(nèi)容提交給GPU去渲染,GPU渲染完成之后就會(huì)放入幀緩沖區(qū),然后視頻控制器會(huì)按照VSync信號(hào)逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器.就顯示出來了.
原理圖就不放了,過一遍概念.
離屏渲染的代價(jià)很高,想要進(jìn)行離屏渲染,首選要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū),屏幕渲染會(huì)有一個(gè)上下文環(huán)境的一個(gè)概念,離屏渲染的整個(gè)過程需要切換上下文環(huán)境,先從 當(dāng)前屏幕切換到離屏,等結(jié)束后,又要將上下文環(huán)境切換回來.這也是為什么會(huì)消耗性能的原因了.
孝凌。由于垂直同步的機(jī)制叙量,如果在一個(gè) VSync 時(shí)間內(nèi)虹脯,CPU 或者 GPU 沒有完成內(nèi)容提交吴趴,則那一幀就會(huì)被丟棄涝婉,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變倦沧。這就是界面卡頓的原因唇撬。
那有個(gè)問題: 為什么離屏渲染這么耗性能,為什么有這套機(jī)制呢?
當(dāng)使用圓角,陰影展融,遮罩的時(shí)候窖认,圖層屬性的混合體被指定為在未預(yù)合成之前(下一個(gè)VSync信號(hào)開始前)不能直接在屏幕中繪制,所以就需要屏幕外渲染告希。 你可以這么理解. 老板叫我短時(shí)間間內(nèi)做一個(gè) app.我一個(gè)人能做,但是時(shí)間太短,所以我得讓我朋友一起來幫著我做.(性能消耗: 也就是耗 你跟你朋友之間溝通的這些成本,多浪費(fèi)啊).但是沒辦法 誰(shuí)讓你做不完呢.
這么一講會(huì)不會(huì)比較明白點(diǎn).
-
哪些操作會(huì)觸發(fā) 離屏渲染?
官方公開的的資料里關(guān)于離屏渲染的信息最早是在 2011年的 WWDC扑浸, 在多個(gè) session 里都提到了盡量避免會(huì)觸發(fā)離屏渲染的效果,包括:mask, shadow, group opacity, edge antialiasing燕偶。
shouldRasterize(光柵化)
masks(遮罩)
shadows(陰影)
edge antialiasing(抗鋸齒)
group opacity(不透明)
復(fù)雜形狀設(shè)置圓角等
漸變
Text(UILabel, CATextLayer, Core Text, etc)...
介紹一些屬性.
- shouldRasterize(光柵化): 將圖轉(zhuǎn)化為一個(gè)個(gè)柵格組成的圖象喝噪。 光柵化特點(diǎn):每個(gè)元素對(duì)應(yīng)幀緩沖區(qū)中的一像素。
shouldRasterize = YES
在其它屬性觸發(fā)離屏渲染的同時(shí),會(huì)將光柵化后的內(nèi)容緩存起來,如果對(duì)應(yīng)的layer
或者 sublayers
沒有發(fā)生改變,在下一幀的時(shí)候可以直接復(fù)用,從而減少渲染的頻率.
當(dāng)使用光柵化是, 可以開啟 "Color Hits Green and Misses Red"來檢查該場(chǎng)景下是否適合選擇光柵化,綠色表示緩存被復(fù)用,紅色表示緩存在被重復(fù)創(chuàng)建.對(duì)于經(jīng)常變動(dòng)的內(nèi)容,不要開啟,否則會(huì)造成性能的浪費(fèi).
如果cell里面的內(nèi)容不斷變化(cell的復(fù)用),如果設(shè)置了cell.layer.shouldRaseterize = YES
則會(huì)降低圖形性能,造成離屏渲染.
- mask(遮罩).
mask是layer的一個(gè)屬性.
/ A layer whose alpha channel is used as a mask to select between the
* layer's background and the result of compositing the layer's
* contents with its filtered background. Defaults to nil. When used as
* a mask the layer's `compositingFilter' and `backgroundFilters'
* properties are ignored. When setting the mask to a new layer, the
* new layer must have a nil superlayer, otherwise the behavior is
* undefined. Nested masks (mask layers with their own masks) are
* unsupported. */
@property(nullable, strong) CALayer *mask;
大概的意思. 當(dāng)透明度改變的時(shí)候,這個(gè) mask 就是覆蓋上去的那個(gè)陰影.該層的layer的alpha決定了多少層背景跟內(nèi)容通過并顯示,完全,或者部分不透明的像素允許潛在的內(nèi)容 通過并顯示.
默認(rèn)是nil,當(dāng)配置一個(gè) 遮罩的時(shí)候,記得設(shè)置 遮罩的大小,位置.已確保跟蓋圖層對(duì)齊.(這是官方文檔說的 如果不對(duì)齊會(huì)怎樣.you can try. 我試過是只能顯示對(duì)齊的那一部分.)
如果你想給這個(gè)屬性賦值,前提是必須沒有 superLayer,如果有superLayer,這個(gè)行為則是無效的.(你也可以嘗試一下反的.)
tip:用mask可以做一些轉(zhuǎn)場(chǎng)動(dòng)畫.這里就不介紹了.
那說了這么多,他就是生來會(huì)觸發(fā)離屏渲染的.所以要謹(jǐn)慎 設(shè)置透明度. 提到透明度,另外補(bǔ)充一個(gè)概念.)
Color Blended Layers
用jim的話來介紹它就是
屏幕上的每個(gè)像素點(diǎn)的顏色是由當(dāng)前像素點(diǎn)上的多層layer(如果存在)共同決定的指么,GPU會(huì)進(jìn)行計(jì)算出混合顏色的RGB值酝惧,最終顯示在屏幕上榴鼎。而這需要讓GPU計(jì)算,所以我們要盡量避免設(shè)置alpha晚唇,這樣GPU會(huì)忽略下面所有的layer巫财,節(jié)約計(jì)算量。再提一下opaque這個(gè)屬性缺亮,網(wǎng)上普遍認(rèn)為view.opaque = YES翁涤,GPU就不會(huì)進(jìn)行圖層混合計(jì)算了。而這個(gè)結(jié)論是錯(cuò)誤的萌踱,其實(shí)view.opaque事實(shí)上并沒什么卵用葵礼。
如果你真的想達(dá)到這個(gè)效果,可以用layer.opaque,這個(gè)才是正確的做法
- shadows(陰影.)
略過.... (自己可以隨便嘗試一下)
再介紹一下edge antialiasing(抗鋸齒) 這個(gè)吧. 因?yàn)橹白约阂矝]接觸過,很多人估計(jì)也是沒接觸過吧.
翻譯:
是否允許執(zhí)行反鋸齒邊緣并鸵。
默認(rèn)的值是 NO.(不使用抗鋸齒,也有人叫反鋸齒),當(dāng) Value 為YES的時(shí)候, 在layer的 edgeAntialiasingMask屬性layer依照這個(gè)值允許抗鋸齒邊緣,(參照這個(gè)值) 可以在info.plist里面開啟這個(gè)屬性.
放一個(gè) plist 常見的key值表 info.plist配置表 (樓主真的好...)
說了這么多. 那抗鋸齒又是啥????
抗鋸齒的概念 (隨便看看.)
在我們iOS中表現(xiàn) 參考這里
CALayer *layer = [CALayer layer];
layer.position = CGPointMake(100, 100);
layer.bounds = CGRectMake(0,0, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
//layer.allowsEdgeAntialiasing = YES;
[self.view.layer addSublayer:layer];
正常添加layer 在view上是這樣的.
下一步.
CGFloat angle = M_PI / 30.0;
[layer setTransform:CATransform3DRotate(layer.transform, angle, 0.0, 0.0, 1.0)];
在模擬器表現(xiàn)是這樣的.
如果 layer.allowsEdgeAntialiasing = YES;
--- 在模擬器上是
--- 在真機(jī)上.
可見真機(jī)效果跟模擬器還是有差距的,(模擬器邊緣比真機(jī)模糊,)官方文檔也有提到這點(diǎn)
Use antialiasing when drawing a layer that is not aligned to pixel boundaries. This option allows for more sophisticated rendering in the simulator but can have a noticeable impact on performance.
這是UIView的抗鋸齒,在模擬器上還是有性能的消耗的.
看看效果就行,具體的不研究太多了,知道怎么避免就行.
- group opacity(不透明)
大概的意思就是 這個(gè)屬性決定了Core Animation框架下 子layers從他們Superlayer.繼承過來的不透明度.
iOS 6之前是默認(rèn)NO,iOS7以后就默認(rèn) 是YES. 文檔也是說可以在模擬器上呈現(xiàn).但是對(duì)性能有明顯的影響.
這里我就不測(cè)試了.這個(gè)屬性過一遍,重點(diǎn)是下一個(gè)屬性.
- 復(fù)雜形狀設(shè)置圓角.
我們?cè)陂_發(fā)中經(jīng)常會(huì)對(duì)一些圖片或者按鈕進(jìn)行圓角處理,需求還是特別多的,設(shè)置圓角有多種方法,我列一下常見的方式.
- 設(shè)置layer層的圓角大小.經(jīng)常我們還會(huì)設(shè)置masksToBounds,
//按正方形來算.長(zhǎng)的一半就是半徑.按照這個(gè)去設(shè)置就是圓角了,長(zhǎng)方形的話則按短的那一邊
_imageView.layer.cornerRadius = iamgeView.width/2;
_imageView.layer.masksToBounds = YES;
這樣做對(duì)于少量的圖片鸳粉,這個(gè)沒有什么問題,但是數(shù)量比較多的時(shí)候,UITableView滑動(dòng)可能不是那么流暢园担,屏幕的幀數(shù)下降届谈,影響用戶體驗(yàn)。
使用layer的mask遮罩和CAShapLayer
創(chuàng)建圓形的CAShapeLaer對(duì)象,設(shè)置為View的mask屬性,這樣也可以達(dá)到圓角的效果,但是前面提到過了,使用mask屬性會(huì)離屏渲染,不僅這樣,還曾加了一個(gè) CAShapLayer對(duì)象.著實(shí)不可以取.使用帶圓形的透明圖片.(求個(gè)切圖大師 - - ).
CoreGraphics自定義繪制圓角.
提到CoreGraphics,還有一種 特殊的"離屏渲染"方式 不得不提,那就是drawRect方法.觸發(fā)的方式:
如果我們重寫了 drawRect方法,并且使用CoreGraphics技術(shù)去繪制.就設(shè)計(jì)到了CPU渲染,整個(gè)渲染,由CPU在app內(nèi)同步完成,渲染之后再交給GPU顯示.(這種方式對(duì)性能的影響不是很高)
tip:CoreGraphic通常是線程安全的弯汰,所以可以進(jìn)行異步繪制艰山,然后在主線程上更新.
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
-
如何檢測(cè)我的項(xiàng)目里面離屏渲染了
- 之前看了一些文章說在intruments里面的 CoreAnimation里面有工具.檢測(cè).(沒找著.求補(bǔ)充)
打開的正確方式:
模擬器的 debug -> 選取 color Offscreen-Rendered.
開啟后會(huì)把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在性能問題咏闪。
正常:是這樣的
有問題的圖層:
可以看見我設(shè)置了圓角的imageView有問題.
項(xiàng)目開發(fā)中怎么去處理?
拋出一個(gè)問題: 需求就是有很多圓角那我們項(xiàng)目中應(yīng)該怎么去處理圓角呢?
相信看完兩篇文章,多少都會(huì)能收獲一點(diǎn)!
有些人說:
iOS 9.0 之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染曙搬,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的(這句話不知道誰(shuí)說的.自己有沒有去嘗試呢???)
結(jié)論: 經(jīng)過測(cè)試
大家可以看到,
UIButton 的 masksToBounds = YES下發(fā)生離屏渲染與 背景圖存不存在有關(guān)系, 如果沒有給按鈕設(shè)置 btn.image = [UIImage imageName:@"xxxxx"];
是不會(huì)產(chǎn)生離屏渲染的 .
關(guān)于 UIImageView,現(xiàn)在測(cè)試發(fā)現(xiàn)(現(xiàn)版本: iOS10),在性能的范圍之內(nèi),給UIImageView設(shè)置圓角是不會(huì)觸發(fā)離屏渲染的,但是同時(shí)給UIImageView設(shè)置背景色則肯定會(huì)觸發(fā).觸發(fā)離屏渲染跟 png.jpg格式并無關(guān)聯(lián)(可能采取的壓縮格式不同,這里不做探討,這里我給出結(jié)果是沒有關(guān)系)
-
總結(jié):
- 對(duì)于網(wǎng)上一些文章得出的結(jié)論,我覺得大家得理性分析,并不是每個(gè)人都是對(duì)的,只有經(jīng)過自己實(shí)踐才能得出較好的定論.本文也是,我希望哪里有理解錯(cuò)或者其它什么錯(cuò)誤請(qǐng)?zhí)岢?認(rèn)真臉.),人無完人,我希望在學(xué)習(xí)的道路上能碰見更多一起進(jìn)步的人!