iOS - 渲染原理

Head

在性能優(yōu)化中饿这,有一個重要的知識點(diǎn)就是卡頓優(yōu)化,我們以FPS(每秒傳輸幀數(shù)(Frames Per Second))來衡量它的流暢度撞秋,蘋果的iPhone推薦的刷新率是60Hz长捧,也就是說GPU每秒鐘刷新屏幕60次,這每刷新一次就是一幀frame吻贿,每一幀大概在1/60 = 16.67ms畫面最佳串结,靜止不變的頁面FPS值是0,這個值是沒有參考意義的舅列,只有當(dāng)頁面在執(zhí)行動畫或者滑動的時候肌割,F(xiàn)PS值才具有參考價值,F(xiàn)PS值的大小體現(xiàn)了頁面的流暢程度高低帐要,當(dāng)?shù)陀?5的時候卡頓會比較明顯

屏幕呈像原理

我們所看到的動態(tài)的屏幕的成像其實(shí)和視頻一樣也是一幀一幀組成的把敞。為了把顯示器的顯示過程和系統(tǒng)的視頻控制器進(jìn)行同步,顯示器(或者其他硬件)會用硬件時鐘產(chǎn)生一系列的定時信號宠叼。當(dāng)電子槍換行進(jìn)行掃描時先巴,顯示器會發(fā)出一個水平同步信號(horizonal synchronization)其爵,簡稱 HSync冒冬;而當(dāng)一幀畫面繪制完成后伸蚯,電子槍回復(fù)到原位,準(zhǔn)備畫下一幀前简烤,顯示器會發(fā)出一個垂直同步信號(vertical synchronization)剂邮,簡稱 VSync。顯示器通常以固定頻率進(jìn)行刷新横侦,這個刷新率就是 VSync 信號產(chǎn)生的頻率挥萌。

屏幕呈像原理

卡頓的產(chǎn)生

接下來介紹完成顯示信息的過程是:CPU 計算數(shù)據(jù) -> GPU 進(jìn)行渲染 -> 渲染結(jié)果存入幀緩沖區(qū) -> 視頻控制器會按照 VSync 信號逐幀讀取幀緩沖區(qū)的數(shù)據(jù) -> 成像,假如屏幕已經(jīng)發(fā)出了 VSync 但 GPU 還沒有渲染完成枉侧,則只能將上一次的數(shù)據(jù)顯示出來引瀑,以致于當(dāng)前計算的幀數(shù)據(jù)丟失,這樣就產(chǎn)生了卡頓榨馁,當(dāng)前的幀數(shù)據(jù)計算好后只能等待下一個周期去渲染憨栽。

整體流程

卡頓原因

卡頓的優(yōu)化

那么,解決卡頓的方案就很是要在下一次VSync到來之前翼虫,盡可能減少這一幀 CPU 和 GPU 資源的消耗屑柔,要減少的話我們就得先了解這兩者在渲染中的具體分工是什么,和iOS中視圖的產(chǎn)生過程

UIView 和 CALayer

我們都知道珍剑,視圖的職責(zé)是 創(chuàng)建并管理 圖層掸宛,以確保當(dāng)子視圖在層級關(guān)系中 添加或被移除 時,其關(guān)聯(lián)的圖層在圖層樹中也有相同的操作招拙,即保證視圖樹和圖層樹在結(jié)構(gòu)上的一致性唧瘾,那么為什么 iOS 要基于 UIViewCALayer 提供兩個平行的層級關(guān)系呢?其原因在于要做 職責(zé)分離别凤,這樣也能避免很多重復(fù)代碼劈愚。在 iOS 和 Mac OS X 兩個平臺上,事件和用戶交互有很多地方的不同闻妓,基于多點(diǎn)觸控的用戶界面和基于鼠標(biāo)鍵盤的交互有著本質(zhì)的區(qū)別菌羽,這就是為什么 iOS 有 UIKitUIView,對應(yīng) Mac OS X 有 AppKitNSView 的原因由缆。它們在功能上很相似注祖,但是在實(shí)現(xiàn)上有著顯著的區(qū)別。

CALayer

那么為什么 CALayer 可以呈現(xiàn)可視化內(nèi)容呢均唉?因?yàn)?CALayer 基本等同于一個 紋理是晨。紋理是 GPU 進(jìn)行圖像渲染的重要依據(jù),紋理本質(zhì)上就是一張圖片舔箭,因此 CALayer 也包含一個 contents 屬性指向一塊緩存區(qū)罩缴,稱為 backing store蚊逢,可以存放位圖(Bitmap)。iOS 中將該緩存區(qū)保存的圖片稱為 寄宿圖

CALayer

在實(shí)際開發(fā)中箫章,繪制界面有兩種方式:一種是 手動繪制烙荷;另一種是 使用圖片
對此檬寂,iOS 中也有兩種相應(yīng)的實(shí)現(xiàn)方式:

  • 使用圖片:contents image
  • 手動繪制:custom drawing

Contents Image

Contents Image 是指通過 CALayer 的 contents 屬性來配置圖片终抽。然而,contents 屬性的類型為 id桶至。在這種情況下昼伴,可以給 contents 屬性賦予任何值,app 仍可以編譯通過镣屹。但是在實(shí)踐中圃郊,如果 content 的值不是 CGImage ,得到的圖層將是空白的

    // Contents Image
    UIImage *image = [UIImage imageNamed:@"cat.JPG"];
    UIView *v = [UIView new];
    v.layer.contents = (__bridge id _Nullable)(image.CGImage);
    v.frame = CGRectMake(100, 100, 100, 100);
    [self.view addSubview:v];
Contents Image

我們可以看到女蜈,這樣就可以使用圖片繪制到view上面去

Custom Drawing

Custom Drawing 是指使用 Core Graphics 直接繪制寄宿圖持舆。實(shí)際開發(fā)中,一般通過繼承 UIView 并實(shí)現(xiàn) -drawRect: 方法來自定義繪制鞭光。

  • UIView 有一個關(guān)聯(lián)圖層吏廉,即 CALayer
  • CALayer 有一個可選的 delegate 屬性惰许,實(shí)現(xiàn)了 CALayerDelegate 協(xié)議席覆。UIView 作為 CALayer 的代理實(shí)現(xiàn)了CALayerDelegae 協(xié)議。
  • 當(dāng)需要重繪時汹买,即調(diào)用 -drawRect:佩伤,CALayer 請求其代理給予一個寄宿圖來顯示。
  • CALayer 首先會嘗試調(diào)用 -displayLayer: 方法晦毙,此時代理可以直接設(shè)置 contents 屬性生巡。
- (void)displayLayer:(CALayer *)layer;
  • 如果代理沒有實(shí)現(xiàn) -displayLayer: 方法,CALayer 則會嘗試調(diào)用 -drawLayer:inContext:方法见妒。在調(diào)用該方法前孤荣,CALayer 會創(chuàng)建一個空的寄宿圖(尺寸由 bounds 和 contentScale 決定)和一個 Core Graphics 的繪制上下文,為繪制寄宿圖做準(zhǔn)備须揣,作為 ctx 參數(shù)傳入盐股。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
  • 最后,由 Core Graphics 繪制生成的寄宿圖會存入 backing store耻卡。

    Custom Drawing

    若UIView的子類重寫了drawRect疯汁,則UIView執(zhí)行完drawRect后,系統(tǒng)會為器layer的content開辟一塊緩存卵酪,用來存放drawRect繪制的內(nèi)容幌蚊。
    即使重寫的drawRect啥也沒做谤碳,也會開辟緩存,消耗內(nèi)存溢豆,所以盡量不要隨便重寫drawRect卻啥也不做蜒简。

  • 其實(shí),當(dāng)在操作 UI 時沫换,比如改變了 Frame臭蚁、更新了 UIView/CALayer 的層次時最铁,或者手動調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后讯赏,在此過程中 app 可能需要更新 視圖樹,相應(yīng)地冷尉,圖層樹 也會被更新

  • 其次漱挎,CPU計算要顯示的內(nèi)容,包括布局計算(Layout)雀哨、視圖繪制(Display)磕谅、圖片解碼(Prepare)
    當(dāng)runloopBeforeWaiting(即將進(jìn)入休眠)Exit (即將退出Loop) 時,會通知注冊的監(jiān)聽雾棺,然后對圖層打包(Commit)膊夹,打包完后,將打包的數(shù)據(jù)(backing store)發(fā)送給一個獨(dú)立負(fù)責(zé)渲染的進(jìn)程 Render Server

  • 數(shù)據(jù)到達(dá)Render Server 后會被反序列化捌浩,得到圖層樹放刨,按照圖層樹中圖層順序、RBGA值尸饺、圖層frame過濾圖中被遮擋的部分进统,過濾后將圖層樹轉(zhuǎn)成渲染樹,渲染樹的信息會轉(zhuǎn)給 OpenGL ES/Metal

至此浪听,前面CPU 所處理的這些事情統(tǒng)稱為 Commit Transaction

流程

Render Server

Render Server 會調(diào)用 GPU螟碎,GPU 開始進(jìn)行頂點(diǎn)著色器形狀裝配迹栓、幾何著色器掉分、光柵化片段著色器克伊、測試與混合六個階段酥郭。完成這六個階段的工作后,再將 CPU 和 GPU 計算后的數(shù)據(jù)顯示在屏幕的每個像素點(diǎn)上

  • 頂點(diǎn)著色器(Vertex Shader)
  • 形狀裝配(Shape Assembly)答毫,又稱 圖元裝配
  • 幾何著色器(Geometry Shader)
  • 光柵化(Rasterization)
  • 片段著色器(Fragment Shader)
  • 測試與混合(Tests and Blending)
GPU

第一階段褥民,頂點(diǎn)著色器。該階段的輸入是 頂點(diǎn)數(shù)據(jù)(Vertex Data) 數(shù)據(jù)洗搂,比如以數(shù)組的形式傳遞 3 個 3D 坐標(biāo)用來表示一個三角形消返。頂點(diǎn)數(shù)據(jù)是一系列頂點(diǎn)的集合载弄。頂點(diǎn)著色器主要的目的是把 3D 坐標(biāo)轉(zhuǎn)為另一種 3D 坐標(biāo)撵颊,同時頂點(diǎn)著色器可以對頂點(diǎn)屬性進(jìn)行一些基本處理宇攻。

第二階段逞刷,形狀(圖元)裝配亿胸。該階段將頂點(diǎn)著色器輸出的所有頂點(diǎn)作為輸入,并將所有的點(diǎn)裝配成指定圖元的形狀洋丐。圖中則是一個三角形。圖元(Primitive) 用于表示如何渲染頂點(diǎn)數(shù)據(jù),如:點(diǎn)锦爵、線埠啃、三角形台盯。

第三階段蚣常,幾何著色器贞绳。該階段把圖元形式的一系列頂點(diǎn)的集合作為輸入遇八,它可以通過產(chǎn)生新頂點(diǎn)構(gòu)造出新的(或是其它的)圖元來生成其他形狀本昏。例子中,它生成了另一個三角形赖捌。

第四階段祝沸,光柵化。該階段會把圖元映射為最終屏幕上相應(yīng)的像素越庇,生成片段罩锐。片段(Fragment) 是渲染一個像素所需要的所有數(shù)據(jù)。

第五階段卤唉,片段著色器涩惑。該階段首先會對輸入的片段進(jìn)行 裁切(Clipping)。裁切會丟棄超出視圖以外的所有像素搬味,用來提升執(zhí)行效率境氢。

第六階段,測試與混合碰纬。該階段會檢測片段的對應(yīng)的深度值(z 坐標(biāo))萍聊,判斷這個像素位于其它物體的前面還是后面,決定是否應(yīng)該丟棄悦析。此外寿桨,該階段還會檢查 alpha 值( alpha 值定義了一個物體的透明度),從而對物體進(jìn)行混合。因此亭螟,即使在片段著色器中計算出來了一個像素輸出的顏色挡鞍,在渲染多個三角形的時候最后的像素顏色也可能完全不同。
公式為:

R = S + D * (1 - Sa)

假設(shè)有兩個像素 S(source) 和 D(destination)预烙,S 在 z 軸方向相對靠前(在上面)墨微,D 在 z 軸方向相對靠后(在下面),那么最終的顏色值就是 S(上面像素) 的顏色 + D(下面像素) 的顏色 * (1 - S(上面像素) 顏色的透明度)

所以扁掸,才需要我們在做頁面的時候翘县,盡量控制少的圖層數(shù)、還有盡量不要使用alpha

  • 最終谴分,GPU通過Frame Buffer(幀緩沖區(qū)锈麸、 雙緩沖機(jī)制)視頻控制器等相關(guān)部件牺蹄,將圖像顯示在屏幕上忘伞。

至此,原生的渲染流程到此結(jié)束沙兰。

原生渲染卡頓優(yōu)化方案

所以解決卡頓現(xiàn)象的主要思路就是:盡可能減少 CPUGPU 資源的消耗氓奈。

CPU
  • 盡量用輕量級的對象 如:不用處理事件的 UI 控件可以考慮使用 CALayer;
  • 不要頻繁地調(diào)用 UIView 的相關(guān)屬性 如:frame僧凰、bounds探颈、transform 等;
  • 盡量提前計算好布局训措,在有需要的時候一次性調(diào)整對應(yīng)屬性,不要多次修改光羞;
  • Autolayout 會比直接設(shè)置 frame 消耗更多的 CPU 資源绩鸣;
  • 圖片的 size 和 UIImageView 的 size 保持一致;
  • 控制線程的最大并發(fā)數(shù)量纱兑;
  • 耗時操作放入子線程呀闻;如文本的尺寸計算、繪制潜慎,圖片的解碼捡多、繪制等;
GPU
  • 盡量避免短時間內(nèi)大量圖片顯示铐炫;
  • GPU 能處理的最大紋理尺寸是 4096 * 4096垒手,超過這個尺寸就會占用 CPU 資源,所以紋理不能超過這個尺寸倒信;
  • 盡量減少透視圖的數(shù)量和層次科贬;
  • 減少透明的視圖(alpha < 1),不透明的就設(shè)置 opaque 為 YES鳖悠;
  • 盡量避免離屏渲染榜掌;

大前端渲染

大前端的開發(fā)框架主要分為兩類:第一類是基于 WebView 的优妙,第二類是類似 React Native 的。

對于第一類 WebView 的大前端渲染憎账,主要工作在 WebKit 中完成套硼。WebKit 的渲染層來自以前 macOS 的 Layer Rendering 架構(gòu),而 iOS 也是基于這一套架構(gòu)胞皱。所以熟菲,從本質(zhì)上來看,WebKit 和 iOS 原生渲染差別不大朴恳。

第二類的類 React Native 更簡單抄罕,渲染直接走的是 iOS 原生的渲染。那么于颖,我們?yōu)槭裁磿杏X WebView 和類 React Native 比原生渲染得慢呢呆贿?

從第一次內(nèi)容加載來看,即使是本地加載森渐,大前端也要比原生多出腳本代碼解析的工作做入。

WebView 需要額外解析 HTML + CSS + JavaScript 代碼,而類 React Native 方案則需要解析 JSON + JavaScript同衣。HTML + CSS 的復(fù)雜度要高于 JSON竟块,所以解析起來會比 JSON 慢。也就是說耐齐,首次內(nèi)容加載時浪秘,WebView會比類 React Native 慢。

從語言本身的解釋執(zhí)行性能來看埠况,大前端加載后的界面更新會通過 JavaScript解釋執(zhí)行耸携,而 JavaScript 解釋執(zhí)行性能要比原生差,特別是解釋執(zhí)行復(fù)雜邏輯或大量計算時辕翰。所以夺衍,大前端的運(yùn)算速度,要比原生慢不少喜命。

說完了大前端的渲染沟沙,你會發(fā)現(xiàn),相對于原生渲染壁榕,無論是 WebView 還是類 React Native 都會因?yàn)槟_本語言本身的性能問題而在存在性能差距矛紫。那么,對于 Flutter 這種沒有使用腳本語言护桦,并且渲染引擎也是全新的框架含衔,其渲染方式有什么不同,性能又怎樣呢?

Flutter 渲染

Flutter 界面是由 Widget 組成的贪染,所有 Widget 組成 Widget Tree缓呛,界面更新時會更新 Widget Tree,然后再更新 Element Tree杭隙,最后更新 RenderObject Tree哟绊。

接下來的渲染流程,F(xiàn)lutter 渲染在 Framework 層會有 Build痰憎、Wiget Tree票髓、Element TreeRenderObject Tree铣耘、Layout洽沟、PaintComposited Layer 等幾個階段蜗细。將 Layer 進(jìn)行組合裆操,生成紋理,使用 OpenGL 的接口向 GPU 提交渲染內(nèi)容進(jìn)行光柵化與合成炉媒,是在 Flutter 的 C++ 層踪区,使用的是 Skia 庫。包括提交到 GPU 進(jìn)程后吊骤,合成計算缎岗,顯示屏幕的過程和 iOS 原生基本是類似的,因此性能也差不多白粉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末传泊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜗元,更是在濱河造成了極大的恐慌或渤,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕扣,死亡現(xiàn)場離奇詭異,居然都是意外死亡掌敬,警方通過查閱死者的電腦和手機(jī)惯豆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奔害,“玉大人楷兽,你說我怎么就攤上這事』伲” “怎么了芯杀?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我揭厚,道長却特,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任筛圆,我火速辦了婚禮裂明,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘太援。我一直安慰自己闽晦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布提岔。 她就那樣靜靜地躺著仙蛉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碱蒙。 梳的紋絲不亂的頭發(fā)上荠瘪,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音振亮,去河邊找鬼巧还。 笑死,一個胖子當(dāng)著我的面吹牛坊秸,可吹牛的內(nèi)容都是我干的麸祷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼褒搔,長吁一口氣:“原來是場噩夢啊……” “哼阶牍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起星瘾,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤走孽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后琳状,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磕瓷,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年念逞,在試婚紗的時候發(fā)現(xiàn)自己被綠了困食。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡翎承,死狀恐怖硕盹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叨咖,我是刑警寧澤瘩例,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布啊胶,位于F島的核電站,受9級特大地震影響垛贤,放射性物質(zhì)發(fā)生泄漏焰坪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一南吮、第九天 我趴在偏房一處隱蔽的房頂上張望琳彩。 院中可真熱鬧,春花似錦部凑、人聲如沸露乏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘟仿。三九已至,卻和暖如春比勉,著一層夾襖步出監(jiān)牢的瞬間劳较,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工浩聋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留观蜗,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓衣洁,卻偏偏與公主長得像墓捻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坊夫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容