繪圖和動(dòng)畫(huà)有兩種處理方式钳幅,CPU和GPU(圖形處理器)。CPU理論上可以做任何事情炎滞,但對(duì)于圖像處理敢艰,通常硬件會(huì)更快,因?yàn)镚PU使用圖像對(duì)高度并行的浮點(diǎn)運(yùn)算做了優(yōu)化册赛。通常會(huì)把屏幕渲染工作交給GPU來(lái)處理钠导,但當(dāng)GPU資源用完的話,性能就逐漸下降了森瘪。
大多數(shù)動(dòng)畫(huà)性能優(yōu)化都是關(guān)于對(duì)GPU和CPU使用的優(yōu)化牡属,不超出負(fù)荷。
當(dāng)運(yùn)行一段動(dòng)畫(huà)時(shí)扼睬,分為四個(gè)階段
- 1.布局 - 確定視圖層級(jí)關(guān)系逮栅,以及設(shè)置圖層屬性(位置,背景色痰驱,邊框等)证芭。
- 2.顯示 - 繪制圖層寄宿圖片,繪制有可能涉及你的
- drawRect:
和-drawLayer:inContext:
方法的調(diào)用路徑担映。 - 3.準(zhǔn)備 - 準(zhǔn)備發(fā)送動(dòng)畫(huà)數(shù)據(jù)到渲染服務(wù)废士。
- 4.提交 - Core Animation 打包所有圖層和動(dòng)畫(huà)屬性,然后發(fā)送到渲染服務(wù)顯示蝇完。
在動(dòng)畫(huà)在屏幕上顯示之前仍然有更多的工作官硝。一旦打包的圖層和動(dòng)畫(huà)到達(dá)渲染服務(wù)進(jìn)程矗蕊,他們會(huì)被反序列化來(lái)形成另一個(gè)叫做渲染樹(shù)的圖層樹(shù)。使用這個(gè)樹(shù)狀結(jié)構(gòu)氢架,渲染服務(wù)對(duì)動(dòng)畫(huà)的每一幀做出如下工作: - 5.對(duì)所有的圖層屬性計(jì)算中間值傻咖,設(shè)置OpenGL幾何形狀(紋理化的三角形)來(lái)執(zhí)行渲染
- 6.在屏幕上渲染可見(jiàn)的三角形
注:最后5,6兩個(gè)階段在動(dòng)畫(huà)過(guò)程中不斷地重復(fù)執(zhí)行岖研,其中前五個(gè)階段在軟件層面通過(guò)CPU處理卿操,最后一個(gè)渲染通過(guò)GPU來(lái)執(zhí)行。開(kāi)發(fā)者可以控制的只有前兩個(gè)階段:布局和顯示孙援。開(kāi)發(fā)者可以再這兩個(gè)階段決定哪些由CPU執(zhí)行害淤,哪些由GPU處理。
如何對(duì)GPU操作進(jìn)行優(yōu)化
GPU 的優(yōu)化體現(xiàn)在采集圖片和形狀(三角形)拓售,運(yùn)行變換窥摄,應(yīng)用紋理及混合然后輸送到屏幕的過(guò)程。如果想要在這些操作上有更多的靈活性础淤,可以讓開(kāi)Core Animation自己實(shí)現(xiàn)OpenGL著色器崭放,從根本上解決硬件加速問(wèn)題。我們這里只考慮如何從軟件層面優(yōu)化
1.一般來(lái)說(shuō)CALayer 的屬性都是用GPU來(lái)繪制的鸽凶。比如設(shè)置圖層背景币砂,邊框顏,這些可以通過(guò)著色三角板實(shí)時(shí)繪制出來(lái)吱瘩。如果對(duì)一個(gè)contents屬性設(shè)置一張圖片道伟,然后剪裁,就是通過(guò)GPU直接繪制使碾。
避免降低GPU效率的圖層繪制
- 避免太多的幾何結(jié)構(gòu)蜜徽,在Core Animation中幾何結(jié)構(gòu)并不是GPU的瓶頸,但太多的圖層會(huì)引起CPU的瓶頸票摇,限制一次展示的圖層個(gè)數(shù)
- 避免每一幀用相同的像素填充多次拘鞋,即重繪,主要由重疊的半透明圖層引起矢门。GPU的填充比率(用顏色填充像素的比率)是有限的盆色,所以要避免重繪。當(dāng)然在硬件的提升下祟剔,適量的重繪不影響性能隔躲。
- 避免離屏繪制,離屏繪制一般發(fā)生在當(dāng)不能直接在屏幕上繪制物延,并且必須繪制到離屏圖片的上下文的時(shí)候宣旱。離屏繪制發(fā)生在基于CPU或者GPU渲染或者是為離屏圖片分配額外內(nèi)存,以及切換繪制上下文叛薯,這些都會(huì)降低GPU性能浑吟。對(duì)于特定圖層效果的使用笙纤,比如圓角,圖層遮罩组力,陰影或者圖層光柵化都會(huì)強(qiáng)制Core Animation提前渲染圖層的離屏繪制省容。但這不意味著你需要避免使用這些效果,只是需要知道這會(huì)帶來(lái)負(fù)面影響燎字。
- 避免過(guò)大的圖片腥椒,如果視圖繪制超過(guò)GPU支持的20482048或者40964096尺寸的紋理,就必須要CPU在圖層沒(méi)實(shí)現(xiàn)是之前對(duì)圖片進(jìn)行預(yù)處理轩触,同樣也會(huì)降低性能寞酿。
如何對(duì)CPU操作進(jìn)行優(yōu)化
一般CPU處理都會(huì)在動(dòng)畫(huà)開(kāi)始之前家夺,不會(huì)影響到幀率脱柱,但是會(huì)延遲動(dòng)畫(huà)開(kāi)始的時(shí)間,使界面看起來(lái)遲緩拉馋。
影響動(dòng)畫(huà)開(kāi)始時(shí)間的CPU操作有:
- 布局計(jì)算榨为,當(dāng)視圖層級(jí)過(guò)于復(fù)雜,當(dāng)視圖呈現(xiàn)或者修改時(shí)煌茴,計(jì)算圖層幀率就會(huì)消耗一部分時(shí)間随闺。特別是使用AutoLayout尤為明顯,AutoLayout比以前的AutoResizing消耗更多的CPU蔓腐。
- 視圖的懶加載矩乐,一個(gè)試圖將要顯示到屏幕時(shí)才會(huì)加載它。者對(duì)于內(nèi)存使用和縮短程序啟動(dòng)時(shí)間很有幫助回论,但是當(dāng)呈現(xiàn)到屏幕上之前散罕,按下按鈕導(dǎo)致的許多工作都會(huì)不能被及時(shí)響應(yīng)。比如控制器從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)傀蓉,或者視圖從一個(gè)nib文件中加載欧漱,或者涉及IO的圖片顯示(見(jiàn)后續(xù)“IO相關(guān)操作”),都會(huì)比CPU正常操作慢得多葬燎。
- Core Graphics繪制 - 如果對(duì)視圖實(shí)現(xiàn)了-drawRect:方法误甚,或者CALayerDelegate的-drawLayer:inContext:方法,那么在繪制任何東西之前都會(huì)產(chǎn)生一個(gè)巨大的性能開(kāi)銷谱净。為了支持對(duì)圖層內(nèi)容的任意繪制窑邦,Core Animation必須創(chuàng)建一個(gè)內(nèi)存中等大小的寄宿圖片。然后一旦繪制結(jié)束之后壕探,必須把圖片數(shù)據(jù)通過(guò)IPC傳到渲染服務(wù)器冈钦。在此基礎(chǔ)上,Core Graphics繪制就會(huì)變得十分緩慢浩蓉,所以在一個(gè)對(duì)性能十分挑剔的場(chǎng)景下這樣做十分不好派继。
- 解壓圖片 - PNG或者JPEG壓縮之后的圖片文件會(huì)比同質(zhì)量的位圖小得多宾袜。但是在圖片繪制到屏幕上之前,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長(zhǎng) x 4個(gè)字節(jié))驾窟。為了節(jié)省內(nèi)存庆猫,iOS通常直到真正繪制的時(shí)候才去解碼圖片。根據(jù)你加載圖片的方式绅络,第一次對(duì)圖層內(nèi)容賦值的時(shí)候(直接或者間接使用UIImageView
)或者把它繪制到Core Graphics中月培,都需要對(duì)它解壓,這樣的話恩急,對(duì)于一個(gè)較大的圖片杉畜,都會(huì)占用一定的時(shí)間。 - I/O相關(guān)的優(yōu)化
如果動(dòng)畫(huà)需要I/O操作來(lái)加載衷恭,
IO比內(nèi)存訪問(wèn)更慢此叠,所以如果動(dòng)畫(huà)涉及到IO,就是一個(gè)大問(wèn)題随珠∶鹪總的來(lái)說(shuō),這就需要使用聰敏但尷尬的技術(shù)窗看,也就是多線程茸歧,緩存和投機(jī)加載(提前加載當(dāng)前不需要的資源,但是之后可能需要用到)显沈。
如何測(cè)量是否有必要優(yōu)化
- 真機(jī)提供參數(shù)的真實(shí)性软瞎。不同硬件上測(cè)試,關(guān)注用戶的設(shè)備以及系統(tǒng)拉讯。
- 保持一定的幀率涤浇,為了做到動(dòng)畫(huà)的平滑,需要以60FPS(幀每秒)的速度運(yùn)行遂唧,同步屏幕刷新頻率芙代。如果不保持60FPS的速率,可能隨機(jī)丟幀盖彭,影響體驗(yàn)纹烹。
Core Animation Debug Options & 優(yōu)化
Color Blended Layers (圖層混合)
界面都是會(huì)出現(xiàn)多個(gè)UI控件疊加的情況,如果有透明或者半透明的控件召边,那么GPU會(huì)去計(jì)算這些這些layer最終的顯示的顏色铺呵。基于渲染程度對(duì)屏幕中混合區(qū)域進(jìn)行綠到紅的高亮顯示隧熙,越紅表示性能越差片挂,會(huì)對(duì)幀率等指標(biāo)造成較大影響。
優(yōu)化方法:
如果出現(xiàn)圖層混合了,如何優(yōu)化呢音念?
(1)設(shè)置opaque 屬性為true沪饺。(2)調(diào)整布局(3)給View設(shè)置一個(gè)不透明的顏色,沒(méi)有特殊需要設(shè)置白色即可闷愤。
label.backgroundColor = [UIColor whiteColor];
label.layer.masksToBounds = YES;
如果label的內(nèi)容是中文整葡,label實(shí)際渲染區(qū)域要大于label的size,最外層多了一個(gè)sublayer讥脐,如果不設(shè)置masksToBounds遭居,邊緣外層會(huì)出現(xiàn)圖層混合的紅色。
單獨(dú)使用layer.masksToBounds = YES是不會(huì)發(fā)生離屏渲染旬渠。
UIImageView控件比較特殊俱萍,不僅需要自身這個(gè)容器是不透明的,并且imageView包含的內(nèi)容圖片也必須是不透明的告丢,如果你自己的圖片出現(xiàn)了圖層混合紅色枪蘑,如果確認(rèn)代碼沒(méi)問(wèn)題,就是圖片自身的問(wèn)題
Color Hits Green and Misses Red(光柵化)
當(dāng)UIView.layer.shouldRasterize = YES 時(shí)芋齿,耗時(shí)的圖片繪制會(huì)被緩存腥寇,并當(dāng)做一個(gè)簡(jiǎn)單的扁平圖片來(lái)呈現(xiàn)。
適用情況:一般在圖像內(nèi)容不變的情況下才使用光柵化觅捆,例如設(shè)置陰影耗費(fèi)資源比較多的靜態(tài)內(nèi)容,如果使用光柵化對(duì)性能的提升有一定幫助麻敌。
不適用的情況:如果內(nèi)容會(huì)經(jīng)常變動(dòng),這個(gè)時(shí)候不要開(kāi)啟,否則會(huì)造成性能的浪費(fèi)栅炒。例如我們?cè)谑褂胻ableViewCell中,一般不要用光柵化术羔,因?yàn)閠ableViewCell的繪制非常頻繁赢赊,內(nèi)容在不斷的變化,如果使用了光柵化级历,會(huì)造成大量的離屏渲染降低性能释移。
(1)系統(tǒng)給光柵化緩存分配了一個(gè)固定的大小,因此不能過(guò)度使用寥殖,如果超出了緩存也會(huì)造成離屏渲染玩讳。(2)緩存的時(shí)間為100ms,因此如果在100ms內(nèi)沒(méi)有使用緩存的對(duì)象嚼贡,則會(huì)從緩存中清除熏纯。
Color Copied Images(圖片顏色格式)
如果使用Color Copied Images去調(diào)試發(fā)現(xiàn)是藍(lán)色,可以去找UI重新做一張粤策。蘋(píng)果的GPU只解析32bit的顏色格式樟澜,對(duì)于GPU不支持的色彩格式的圖片只能由CPU來(lái)處理,這樣的圖片為藍(lán)色,藍(lán)色越多性能越差秩贰。因?yàn)槿绻跐L動(dòng)加載大量圖片時(shí)霹俺,由CPU來(lái)處理圖片,可能會(huì)阻塞主線程毒费。
知識(shí)擴(kuò)展:32bit指的是圖片顏色深度吭服,用“位”來(lái)表示,用來(lái)表示顯示顏色數(shù)量蝗罗,例如一個(gè)圖片支持256種顏色艇棕,那么就需要256個(gè)不同的值來(lái)表示不同的顏色,也就是從0到255串塑,二進(jìn)制表示就是從00000000到11111111沼琉,一共需要8位二進(jìn)制數(shù),所以顏色深度是8桩匪。通常32bit色彩中使用三個(gè)8bit分別表示R紅G綠B藍(lán),還有一個(gè)8bit常用來(lái)表示透明度(Alpha)打瘪。
Color Immediately(顏色刷新頻率)
通常Core Animation Instruments以每毫秒10次的頻率更新圖層調(diào)試顏色,如果某些效果覺(jué)得不夠用傻昙,這個(gè)選項(xiàng)可以設(shè)置每幀都更新(不建議使用闺骚,可能會(huì)影響到渲染性能)
Color Misaligned Images(圖片大小)
這個(gè)選項(xiàng)檢查了image size和imageView size不匹配,圖片是否被縮放妆档,以及像素是否對(duì)齊僻爽。被放縮的圖片會(huì)被標(biāo)記為黃色,像素不對(duì)齊則會(huì)標(biāo)注為紫色贾惦。黃色胸梆、紫色越多,性能越差须板。
Color Offscreen-Rendered Yellow(離屏渲染)
將離屏渲染標(biāo)為黃色碰镜,黃色越多性能越差,可以用shadowPath 或者 shouldRasterize 來(lái)優(yōu)化习瑰。
觸發(fā)離屏渲染Offscreen rendering的行為:
(1)drawRect:方法
(2)layer.shadow
(3)layer.allowsGroupOpacity or layer.allowsEdgeAntialiasing
(4)layer.shouldRasterize
(5)layer.mask
(6)layer.masksToBounds && layer.cornerRadius
這里有需要注意的是第三條layer.shouldRasterize 绪颖,其實(shí)就是我們本文講的第三個(gè)選項(xiàng)光柵化,光柵化會(huì)觸發(fā)離屏渲染甜奄,因此光柵化慎用柠横。
第六條設(shè)置圓角會(huì)觸發(fā)離屏渲染,如果在某個(gè)頁(yè)面大量使用了圓角贺嫂,會(huì)非常消耗性能造成FPS急劇下降滓鸠,設(shè)置圓角觸發(fā)離屏渲染要同時(shí)滿足下面兩個(gè)條件:
layer.masksToBounds = YES;
layer.cornerRadius = 5;
為了盡可能避免觸發(fā)離屏渲染,我們可以換其他手段來(lái)實(shí)現(xiàn)必要的功能:
/**
* Method : CAShapeLayer設(shè)置圓角
* rectCorner : UIRectCorner
* 需要導(dǎo)入<AVFoundation/AVFoundation.h>
*/
- (void)shapeLayerCornerRadiusWithView:(UIView *)view withRectCorner:(UIRectCorner)rectCorner
{
//使用UIBezierPath 和 CAShapeLayer 設(shè)置圓角 內(nèi)存占用最小且渲染快速
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:view.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//設(shè)置大小
maskLayer.frame = view.bounds;
//設(shè)置圖形樣子
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
}
/**
* Method : shadowPath設(shè)置圓角
* Return : return
*/
- (void)shadowPathOfView:(UIView *)view{
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, view.bounds);
view.layer.shadowPath = squarePath;
CGPathRelease(squarePath);
}
Color Compositing Fast-Path Blue (快速路徑)
將直接使用OpenGL繪制的圖層顯示為藍(lán)色第喳。藍(lán)色越多糜俗,性能越好。如果使用CLKView或者CAEAGLLayer,但不顯示藍(lán)色則說(shuō)明你正在強(qiáng)制CPU渲染額外的紋理悠抹,而不是繪制到屏幕珠月。
Flash Updated Regions (重繪區(qū)域)
將重新繪制的內(nèi)容顯示為黃色,盡量減少不需要的重新繪制楔敌。這種繪圖的速度很慢啤挎。如果頻繁發(fā)生這種情況的話,這意味著有一個(gè)隱藏的bug或者說(shuō)通過(guò)增加緩存或者使用替代方案會(huì)有提升性能的空間卵凑。
OpenGL ES Analysis 測(cè)量GPU的利用率
OpenGL ES Analysis
Renderer Utilization - 如果這個(gè)值超過(guò)了~50%庆聘,就意味著你的動(dòng)畫(huà)可能對(duì)幀率有所限制,很可能因?yàn)殡x屏渲染或者是重繪導(dǎo)致的過(guò)度混合勺卢。
Tiler Utilization - 如果這個(gè)值超過(guò)了~50%伙判,就意味著你的動(dòng)畫(huà)可能限制于幾何結(jié)構(gòu)方面,也就是在屏幕上有太多的圖層占用了黑忱。