美團 iOS 端開源框架 Graver 在動態化上的探索與實踐
- 2019 年 11 月 1 日
- 筆記
近些年,移動端動態化技術可謂是“百花齊放”,其中的渲染性能也是動態化技術一直在探索、研究的課題。美團的開源框架 Graver 也為解決動態化框架的渲染性能問題提供了一種新思路:關於布局,我們可以採用“畫控件”方案替代傳統的“拼控件”方式。本文嘗試給出一些探索思考與實踐經驗的分享。
前言
動態化技術指的是不依賴程序安裝包,就能進行動態實時更新頁面的技術。特別是對於電商、社交等需要快速迭代、實時調整的強運營類業務來說,動態化具有非常重要意義。它的優勢主要表現為:提高人效、縮短迭代試錯周期、解決版本長尾問題、減少包大小等等。
從2018年開始,移動端設備的增長紅利不再,整個生態增長趨勢開始由高走低,與之對應的開發生態在 Native 技術方向也逐漸開始進入低迷階段,大方向在向跨平台演進,方案上已經是“百花齊放”。現有的客戶端動態化技術主要可以劃分為以基於 Webview 的 Web 頁面動態化加載、本地內置多個模板支持動態切換、支持動態 DSL 的布局引擎以及基於虛擬機等四類。
動態化方案的渲染引擎多數是基於原生 UI 控件搭建動態化頁面。基於 Webview 的 Web 頁面動態化,實質是基於瀏覽器運行網頁,頁面繪製效率、運行效率相對較低一些。而後三種解決方案,分別通過建立映射表、布局引擎、虛擬機與客戶端渲染引擎通訊及調用關係,渲染引擎則都是基於原生 UI 控件搭建動態化頁面。由於操作系統提供的 UI 控件布局/繪製僅支持主線程訪問,大量原生 UI 控件操作導致 CPU/GPU 負擔過重,所以在構建複雜的動態化頁面上存在效率和性能瓶頸。因此,渲染性能是動態化技術一直在探索、研究的課題。本文嘗試給出一些探索思考與實踐經驗的分享。
動態布局框架
如前言部分的圖1所示,MTFlexbox 是美團點評自研的一款跨平台動態布局框架,它遵循了 CSS3 中提出的 Flexbox 規範來抹平多端差異。美團 App 首頁、搜索結果頁等業務有一個共同點,就是面向的業務方比較多,承載了流量輸送變現的能力。在視圖層面呈現輕交互、重展示的特徵。頻繁變動 UI,快速上線是一個剛需,MTFlexbox 正是滿足了這樣一個剛需。
由於本文側重對 MTFlexbox 的渲染性能優化,故僅對 MTFlexbox 做概括介紹。MTFlexbox 首先定義了一份跨平台統一的 DSL 布局描述文件,前端通過編輯器編輯產生布局文件並上傳到雲端,客戶端下載布局文件然後根據布局中的描述信息綁定業務數據,最後基於原生 UI 控件搭建視圖並渲染展示。MTFlexbox 的工作原理如下圖所示:
業務痛點
然而,隨着業務的迭代演變,美團 App 首頁、搜索結果頁等業務視圖卡片樣式越來越多,展示也越來越複雜。樣式種類多意味着視圖復用率低,極端場景下甚至無法進行復用。展示複雜,同時也意味着控件數量多、布局複雜、層級深。如果大量複雜操作都發生在主線程,難免造成渲染卡頓等用戶體驗方面的問題。
針對上述問題,外賣終端用戶研發組、美團終端技術研發組、美團終端業務研發組合作共贏,三方協調資源成立了跨部門、跨事業部的虛擬專項聯合項目組,三方精誠合作,在技術上不斷追求卓越,力求同時保證穩定性、動態化和高性能。
思路分析
動態布局框架 MTFlexbox 通過系統 UIKit 搭建視圖並渲染展示,其測量、布局、繪製過程均發生在主線程。而作為一款 iOS 端高效的 UI 異步渲染框架 Graver,其布局計算、渲染過程完全異步化,整個過程結束後才通知 UI 線程進行展示。這給我們解決動態化框架的渲染性能問題打開了新思路:關於布局,我們可以採用“畫控件”方案替代傳統的“拼控件”方式。Graver 已經在美團 App 的外賣頻道、獨立外賣 App 核心業務場景的多個業務中經歷了一年多的實踐檢驗。良好的穩定性和出色的渲染性能,也得到了美團外賣內部技術團隊的認可和肯定。關於 Graver 更多的內容這裡不再贅述,詳細介紹請參考另一篇技術博客:《美團開源 Graver 框架:用“雕刻”詮釋 iOS 端 UI 界面的高效渲染》 。
如何構建基於 Graver 進行異步渲染的動態化框架(MTFlexbox),成為首先需要解決的問題。
“拼控件”到“畫控件”
通過對系統 UI 渲染流程分析不難發現:唯一確定一個視圖展示僅需要確定視圖布局信息、內容信息、渲染信息三個要素。含義如下:
- 布局信息:UI 控件的大小、位置和展示層級。
- 內容信息:UI 展示的文本、圖片等。
- 渲染信息:包括UI控件的背景色、展示字體字號、透明度、邊框等控件視覺屬性。
Graver 的每個繪製元素通過 WMMutableAttributedItem 來表達內容信息、渲染信息,CGRect 表達繪製元素的大小和位置。渲染整個過程除畫板視圖外,完全沒有使用 UIKit 控件,最終產出的結果是一張位圖(Bitmap)。如果能通過一棵樹形結構組織所有的繪製元素即繪製結點樹,即可按照遞歸遍歷的方式“畫控件”來轉義“拼控件”構建視圖。接下來,我們需要思考如何建立 MTFlexbox 的數據結構與繪製結點樹之間的關係,並且保證該轉化過程完全異步化。
構建虛擬結點樹
如開篇動態布局框架章節 MTFlexbox 的原理所描述:在相繼完成模板樹構建、數據綁定之後即進行了視圖樹構建。然而,出於功能劃分考慮、兼顧保留 MTFlexbox 的系統 UI 渲染引擎能力以及構建繪製結點樹需要的必要信息考慮,需要構建一個中間數據結構:虛擬結點樹。它應包含樹形結構的層級信息、Flex 屬性信息、數據解析處理後的內容信息以及基本的渲染信息。虛擬結點樹是既能構建 UI 控件樹也能構建繪製結點樹的“橋樑”。
設計數據流
通過上述思路分析,確定了關鍵數據結構:虛擬結點樹、繪製結點樹。接下來,我們需要思考如何構建虛擬結點樹到繪製結點樹的數據流。
在前端有兩個重要的概念:迴流、重繪。
- 迴流:當我們對結點樹的修改引發了幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然後再將計算的結果繪製出來,這個過程就是迴流(也叫重排)。
- 重繪:當我們對結點樹的修改導致了樣式的變化,卻並未影響其幾何屬性(比如修改了顏色或背景色)時,不需重新計算元素的幾何屬性,可以直接為該元素繪製新的樣式。
參考前端技術思想以及考慮單一職責原則,在虛擬結點樹與繪製結點樹中間構建 Fat 型渲染結點樹和 Thin 型渲染結點樹。Fat 型渲染結點樹負責保存原始數據以便做邏輯處理,Thin 型渲染結點樹負責保存位置、大小和內容信息。當有修改不影響幾何尺寸變化的情況下,僅重新生成 Thin 型渲染結點樹的內容信息即可。
線程安全考慮
提高渲染性能的關鍵,即是全力保證主線程的最小資源開銷。因此,需要思考如何保證虛擬結點樹到繪製結點樹的轉換過程是線程安全的。Facebook 開源的跨平檯布局引擎 Yoga,提供了通過 UI 視圖樹中 Flex 屬性計算得出每個 UI 控件的位置和大小。然而,提供給 iOS 平台的插頭類是基於 UIView 的,即布局計算過程必須在主線程。需要基於 Yoga 核心邏輯重新封裝基於渲染結點樹的計算邏輯,以保證布局計算是線程安全的。如下圖所示:
架構設計
有了上述的思路分析,接下來我們開始着手 Graver 接入 MTFlexbox 的架構設計。Graver 需要作為獨立渲染引擎存在,並保留接入多種動態化框架的可能,這是出於架構設計的靈活性和擴展性的考慮。接入層命名為 M-Graver,其上層基於 MTFlexbox 進行擴展但可靈活插拔,下層基於 Graver 渲染引擎,如下圖10所示:
M-Graver 是線程安全的,其主要分為解析層、聚合層、布局層和預備層。下面對各層分別做簡單的介紹:
- 解析層:關於輸入定義了一個不依賴於上層 DSL 描述語言的標準化虛擬結點樹結構;解析層主要進行虛擬結點樹到渲染結點樹的轉換,涉及標籤解析、渲染信息解析、統計信息解析以及 Flex 屬性解析。
- 聚合層:完成渲染結點樹的最後構造,涉及可見性、交互性等處理;關於事件綁定也是在聚合層完成,每個事件都以行為參數化的形式綁定到相應渲染結點上。
- 布局層:利用 Yoga 完成每個結點的 Frame 計算以及層級信息處理,同時將渲染信息等內容轉換到 Graver 框架可識別的數據。
- 預備層:進行坐標系轉換,並拍平帶有視圖層級結構信息的渲染結點樹,剔除無效渲染結點(如可見性、size 為0等),屏蔽掉由於視圖層級原因導致被完全遮擋的渲染結點,最後根據渲染結點生成繪製結點構建繪製結點樹,交由 Graver 提供的畫板視圖進行繪製。
技術難點問題
按照上述思路分析完成架構設計,但在實施部署的過程中也遇到了不少的技術難點和問題。如:動態布局框架 MTFlexbox 創建至今已兩年有餘,因業務的快速發展而產生了一些技術“負債”。為了保證不影響線上原有的業務邏輯,所以在進行 MTFlexbox 的模板樹到虛擬結點樹,再到 UI 視圖樹的技術升級改造過程時,尤其需要關注各種“蛛絲馬跡”式的細微邏輯。
另外,在將異步渲染引擎 Graver 接入 MTFlexbox 的過程中也遇到了諸多問題,包括如何構建基於位圖的事件處理系統,跨渲染引擎的技術融合,一些極端場景下的繪製效率瓶頸等等。下面將逐一展開闡述。
1. 如何基於位圖進行事件處理
由於視圖最終通過渲染位圖來呈現,這就需要建立基於位圖的事件處理系統。如前文所述,渲染結點樹記錄了每個控件的位置、大小信息以及層級結構,基於此可仿照系統事件處理邏輯進行基於位圖的事件處理系統設計。在視圖展示期間,畫板視圖收到事件響應通知後(如點擊了畫板視圖中標號為5的紅色按鈕),根據位圖對應的渲染結點樹存儲的各控件布局、層級和渲染信息,逐層遍歷找到需要響應的渲染結點,如果涉及信息修改則變更其在渲染結點樹中的渲染信息,觸發再次渲染的同時執行該渲染結點綁定的事件方法。遍歷渲染結點樹的輸入是系統基於畫板視圖返回的點擊位置,其遍歷過程與系統 UI 事件查找過程比較相似。事件處理過程如下圖11所示:
2. 系統 UI 控件與繪製元素的融合問題
從美團業務特徵出發,圖文組合佔據多數 UI 場景。然而也存在諸如動效等無法依託 Graver 進行圖文渲染的情況。因此,需要考慮跨渲染引擎的渲染融合問題。在 M-Graver 的預備層遍歷渲染結點樹時,可以根據當前結點是否為原生結點決議樹拆分,如果是原生結點,將該結點連同其子樹“直系”繪製結點從渲染結點樹中拆分下來,以該結點為根結點的子渲染結點樹,生成對應的繪製結點樹,多個子渲染結點樹的根結點,構成了以畫板視圖為單元的畫板視圖樹。如下圖12所示:
為了便於理解,我們給出以下幾個名詞的解釋說明:
- 繪製結點:渲染結點樹中的結點如果可以轉化成繪製結點樹中的結點,則稱之為繪製結點,最後通過 Graver 進行渲染。
- 原生結點:渲染結點樹中的結點如果不能轉化成繪製結點樹中的結點,只能轉化成系統 UI 控件則稱之為原生結點。
- 結點變異:以一個二叉渲染結點樹為例,左子結點是原生結點,右子結點是繪製結點的情況下,由於左子結點先於右子結點添加到父節點,可能存在層級顛倒問題。這時右子結點需要強制轉為原生結點,維持正確的層級順序,即結點變異。
- 畫板視圖:繼承自 UIView 的普通視圖,其內部封裝了一系列的基於 Graver 的渲染邏輯。
樹拆分的過程還涉及到兄弟結點層級顛倒以及布局交叉等問題。兄弟結點層級顛倒問題通過結點變異來解決。布局交叉問題存在於判定渲染結點樹的結點是繪製結點或原生結點之前,由於布局原因存在視圖交叉。布局交叉問題通過新建畫板視圖插入來保證層級正確以及繪製正確。由於篇幅有限,這裡不再贅述。
3. 極端場景下的繪製效率瓶頸問題
從產品交互層面看,為了提高屏效往往存在多向滑動的視圖組件場景。如橫滑 Scroll 組件,其特點是需要通過滑動才能逐漸看到所有的視圖內容。通過異步渲染繪製位圖來實現的情況下,存在單一併發渲染任務計算邏輯繁重的問題,從用戶體驗層面看容易造成“白屏”現象。為解決該問題,將視圖卡片渲染過程分解,進行增量渲染,採用漸進式的方式減少空白頁面等待時間。根據待展示區域在屏幕中相對位置進行區塊劃分,通過隊列集中控制繪製操作。以此進一步提高並發效率,並減少渲染過程的非必要系統資源消耗。
區塊劃分
區塊劃分策略的實質是繪製結點樹的拆分,將繪製結點樹中不存在布局交叉的子結點樹進行逐一拆分,每個拆分下來的繪製結點子樹即為一個區塊,同時要設置最小塊策略,否則拆分粒度太小反而會因為過多的線程並發造成性能瓶頸。
分塊繪製
以下圖為例說明分塊繪製邏輯。在滑動過程中,若本地緩存有此區域繪製結果,讀取緩存並直接通知主線程展示,如例4中 X4’。否則,將該區域加入隊列,以塊為單元進行並發繪製,繪製完成後更新緩存,再通知主線程展示,如例1中 X1’,例2中 X2‘,例3中 X3’。對划到屏幕外的區域,從隊列中清除,終止繪製操作;若此區域已繪製完成,則通知主線程清除此區域的展示,如例2中 X2,例3中 X3,例4中 X4。
業務應用
在完成“拼控件”到“畫控件”的思路探索與技術落地之後,需要發揮其價值,將其部署到線上進行業務實踐應用。動態布局框架 MTFlexbox 的跨平台代碼復用能力對業務開發效率有了大幅提升。從產品層面看,在原有資源不變的情況下,達到了高效支撐業務迭代的效果。MTFlexbox 動態布局框架在經歷了一次聯合共建的“洗禮”後渲染性能得到大幅提高,變得愈加成熟、完善。在過去的半年多期間,我們採用異步渲染引擎 Graver 的 MTFlexbox 已先後應用在搜索結果頁、美團首頁等核心流量區業務。下面列舉部分應用案例:
採用異步渲染引擎 Graver 的 MTFlexbox 絕大多數應用場景為列表級應用。如上圖所示,所有視圖卡片均為採用 M-Graver 實現的動態模板。截止到發稿,覆蓋搜索結果頁36個動態模板,覆蓋首頁42個動態模板,業務應用累計覆蓋78個動態模板。
數據指標
以業務應用美團 App 新版首頁為例,分類頻道卡片以下全部為 MTFlexbox 實現的動態模板視圖卡片。由於採用異步渲染引擎 Graver 的 MTFlexbox 具備了在線程安全條件下進行測量、布局、渲染,美團首頁接入後滾動 FPS 提升明顯,對於上拉加載過程的 FPS 提升更為明顯。因此,列表使用體驗變動更加順暢。美團首頁的50分位滾動 FPS 接近59,上拉加載 FPS 接近滿幀。詳細數據如下圖所示:
總結復盤
從業務場景作為出發點和原始驅動力,如何改善動態布局框架的渲染性能問題,從本質上講是解決業務迭代演變時帶來的用戶體驗問題。這裡有以下幾點經驗可供大家參考:
- 在項目設計階段要權衡考慮技術方案全景,作為技術方向規劃,不做臨時方案;架構設計要兼顧合理性、靈活性、擴展性。
- 期間也會遇到諸如原生結點和繪製結點如何融合、事件處理系統怎樣建設、如何分區繪製等一系列問題。保持開放心態,作為探索性項目在方案細節上有很多可行性,充分討論、盯緊目標,不走極端。
- 在跨部門協作項目中,尤其要關注項目管理、會議記錄、里程碑等,同時保持高頻的溝通。
最後,借用朱光潛先生在《藝文雜談·談對話體》中提到的一句話作為結尾:“疑難是思想的起點與核心。”
參考資料
作者簡介
- 洋洋,美團點評資深工程師。
- 柏泉,美團點評高級技術專家。
- 曉宇,美團點評研發工程師。
招聘
美團外賣長期招聘 Android、iOS、FE 高級/資深工程師和技術專家,Base 北京、上海、成都,歡迎有興趣的同學投遞簡歷到[email protected]。