美團 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]。