美團開源Graver框架:用“雕刻”詮釋iOS端UI介面的高效渲染
Graver 是一款高效的 UI 渲染框架,它以更低的資源消耗來構建十分流暢的 UI 介面。Graver 獨創性的採用了基於繪製的視覺元素分解方式來構建介面,得益於此,該框架能讓 UI 渲染過程變得更加簡單、靈活。目前,該框架已經在美團 App 的外賣頻道、獨立外賣 App 核心業務場景的大多數業務中進行了應用,同時也得到美團外賣內部技術團隊的認可和肯定。
App 渲染效能優化是一個普遍存在的問題,為了惠及更多的前端開發同學,美團外賣 iOS 開發團隊將其進行開源,Github 專案地址與使用文件詳見:https://github.com/Meituan-Dianping/Graver 。我們希望該框架能夠應用到更廣闊的業務場景。當然,我們也知道該框架尚有待完善之處,也希望能與更多技術同行一起交流、探討、共建。
前言
我們為什麼需要關注介面的渲染效能?App 使用體驗主要包含產品功能、互動視覺、前端效能,而使用體驗的好與壞,直接影響使用者持續使用還是轉而使用其他 App,所以我們非常關注 App 的渲染效能。而且在網際網路產品流量競爭愈發激烈的大背景下,優質的使用體驗可以為現有使用者提供更好的服務,進而提高使用者轉化和留存,這也意味著創收、盈利。
背景
美團外賣 App 從2013年成立至今,已經走過了五個春秋,在技術層面先後經歷了快速驗證、模組化、精細化和平臺化四個階段,產品形態上也日趨成熟。在此期間,我們構建並完善了監控、報警、容災、備份等各項基礎設施,Metrics 即是其中的效能監控系統。
曾經一段時間,我們以外賣 App 首頁商家卡片列表為例,通過 Metrics 效能監控系統發現其在 FPS、CPU、Memory 等方面的各項指標並不理想。於是,通過 Xcode 自帶的 TimeProfile 等效能檢測工具,然後結合程式碼分析等手段找到了現存效能瓶頸。與此同時,我們梳理其近半年的迭代版本需求發現,UI 往往需要根據不同場景甚至不同使用者展示不同的內容。為了不斷迎合使用者的需求,快速應對市場變化,這種特徵還會持續存在。然而,它會帶來以下問題:
- 檢視層級愈加複雜、檢視數量愈加眾多,從版本長期迭代來看是潛在的效能瓶頸點。
- 如何快速、高效支撐 UI 變化,同時保證不會二次引入效能瓶頸。
Graver 介紹
為了解決現存的效能瓶頸以及後續潛在的效能瓶頸,我們期望構建一套解決方案,該方案能在充分滿足外賣業務特徵的前提下,以標準化、一站式的方式解決 iOS 端 App 的渲染效能問題,並且對研發效率有一定提升, Graver(雕工)框架應運而生。
因為 Graver 獨創性地採用了全新的視覺元素分解思路,所以該框架使用起來十分靈活、簡單。我們先來看一下 Graver 的主要特點:
效能表現優異
以外賣 App 首頁商家列表為例,應用 Graver 之後5分位滾動幀率從滿幀的84%提升至96%,50分位幾乎滿幀;CPU 佔用率下降了近6個百分點,有效提升了空閒 CPU 的資源利用率,降低了峰值 CPU 的佔用率。如圖3所示:
“一站式”非同步化
Graver 從文字計算、樣式排版渲染、圖片解碼,再到繪製,實現了全程非同步化,並且是執行緒安全的。使用 Graver 可以一站式獲得全部效能優化點,可以讓我們:
- 不再擔心散點式的“遇見一處改一處”的麻煩。
- 不再擔心離屏渲染等各種可能導致效能瓶頸的問題,以及令人頭痛的解決辦法。
- 不再擔心優化會有遺漏、優化不到位。
- 不再擔心未來變化可能帶來的任何效能瓶頸。
效能消耗的“邊際成本”幾乎為零
Graver 渲染整個過程除畫板檢視外完全沒有使用 UIKit 控制元件,最終產出的結果是一張點陣圖(Bitmap),檢視層級、數量大幅降低。以外賣 App 首頁鉑金展位檢視為例,原有方案由58個控制元件、12層級拼接而成;而應用 Graver 後僅需1個檢視、1級層級繪製而成。 伴隨著需求迭代、視覺元素變化,效能消耗恆屬常數級。如圖4所示:
渲染速度快
Graver 併發進行多個畫板檢視的渲染、顯示工作。得益於圖文混排技術的應用,達到了記憶體佔用低,渲染速度快的效果。由於排版資料是不變的,所以內部會進行快取、複用,這又進一步促進了整體渲染效率。Graver 既做到了高效渲染,又保證了低時延頁面載入。
以“少”勝“繁”
Graver 重新抽象封裝 CoreText、CoreGraphic 等系統基礎能力,通過少量系統標準圖形繪製介面即可實現複雜介面展示。
基於點陣圖(Bitmap)的輕量事件互動系統
如上述所說,介面展示從傳統的檢視樹轉變為一張點陣圖,而點陣圖不能響應、區分內部具體位置的點選事件。Graver 提供了基於點陣圖的輕量事件互動系統,可以準確識別點選位置發生在點陣圖的哪一塊“繪製單元”內。該“繪製單元”可以理解為與我們一貫使用的某個具體 UI 控制元件相對應的視覺展示。使用 Graver 為某一視覺展示新增事件如同使用系統 UIButton 新增事件一樣簡單。
全新的視覺元素分解思路
Graver 一改介面程式設計思路,與傳統的通過控制元件“拼接”、“新增”,檢視排列組合方式構建介面不同,它提供了靈活、便捷的介面讓我們以“視覺所見”的方式構建介面。這一特點在下文Graver使用中詳細闡述,正是因為該特點實現了研發效率的提升。
Graver 使用
Graver 引入了全新的視覺元素分解的思路。藉助該思路可以實現通過一種物件來表達任一視覺元素、甚至是任一視覺元素的組合,從而消除介面佈局的複雜性。
我們先來回顧下傳統介面的構建方式,以外賣 App 商家卡片其中一種樣式為例,如圖6所示:
在實現商家卡片的介面樣式時,通常會根據視覺上的識別、互動要求來建立介面展示與系統提供的 UI 控制元件間的對映關係。以標號②位置的樣式為例,在考慮複用的情況下通常這部分會使用三個系統控制元件來完成,分別是左側藍底的“預訂”使用 UILabel 控制元件、右側的藍色邊框“2.26.21:30起送”使用 UILabel 控制元件、把左右兩側 UILabel 控制元件裝起來的 UIView 控制元件;在確定好採用的 UI 控制元件之後,需要針對展示樣式分門別類的設定各個控制元件的渲染屬性來實現圖示 UI 效果,渲染屬性通常一部分預設,一部分根據業務資料的不同再進行二次設定;其次,設定各個控制元件的內容屬性實現業務資料內容的展示,展示的內容一般是網路業務資料經邏輯處理、加工後的資料。如果涉及到點選事件,還需要新增手勢或者更換成 UIButton 控制元件。接下來,需要根據視覺要求實現排版邏輯,以標號⑧、⑨為例,當標號⑧位置的資料沒有的情況下,需要上提標號⑨位置的“美團專送”到圖示標號⑧位置。諸如類似的排版邏輯隨處可見。對於圖示任一位置的展示內容都存在上述的迴圈思考、編寫工作。隨著介面元素的增加、變化,問題會變得更加複雜。
傳統的介面構建方式其實是在 UI控制元件的維度去分解視覺元素,具體是做以下四方面的編寫工作:
- 控制元件選擇:根據展示內容、樣式、互動要求確定採用哪種系統控制元件。
- 佈局資訊:UI 控制元件的大小、位置,即 Frame。
- 內容資訊:UI 控制元件展示出來的業務資料,如標號①位置的“星巴克咖啡店”。
- 渲染資訊:UI 控制元件展示出來的效果,如字型、字號、透明度、邊框、顏色等。
最後,將各個控制元件以排列組合方式合成為一棵檢視樹。
Graver 框架提供了以畫板檢視為基礎,通過對更底層的 CoreText、CoreGraphic 框架封裝,以更貼近“視覺所見”的角度定義了全新視覺元素分解、介面展示構建的過程。
通常“視覺所見”可劃分為兩部分:靜態展示、動態展示。靜態展示包含圖片、文字;動態展示包含視訊、動畫等。在視覺展示全部為靜態內容的時候,一個 Cell 即是一個畫布,除此以外沒有任何 UI 控制元件;否則,可以按需靈活的進行畫布拆分來滿足動畫、視訊等需要。
以圖6商家卡片中標號②、⑧為例,新實現方式的虛擬碼是這樣的:
WMMutableAttributedItem *item = [[WMMutableAttributedItem alloc] init];
[[[[item appendImage:[[UIImage wmg_imageWithColor:"blue"] wmg_drawText:"預訂"]]
appendImage:[[UIImage wmg_imageWithColor:"clear" borderWidth:1 borderColor:"blue"] wmg_drawText:"2.26.21:30起送"] appendWhiteSpaceWithWidth:"width"]//總體寬度減去②和⑧的寬度總和剩餘部分 apendText:"50分鐘|2.5km"];
上述實現方式即是把標號②、⑧部分作為一個整體來實現,任何單一系統控制元件都無法做到這一點。
Graver 渲染原理
如圖8所示,Graver 涉及多個佇列間的互動,以外賣 App 商家列表為例,整體流程如下:
- 主執行緒構建請求引數,建立請求任務並放入網路執行緒佇列中,發起網路請求。
- 網路執行緒向後端服務發起請求,獲得對應的業務模型資料(如包含了店鋪名稱,商家頭圖,評分,配送時長,客單價,優惠活動等店鋪屬性的商家卡片列表)。
- 網路執行緒建立包含業務模型資料(如商家卡片列表)的排版任務,提交到預排版執行緒處理,進入預排版流程。預排版佇列取出排版任務,交由佈局引擎計算 UI 佈局,將業務模型解析成可被渲染引擎直接處理的,包含佈局、層級、渲染資訊的排版模型。解析結束後,通知主執行緒排版完成。
- 主執行緒獲取排版模型後,隨即觸發內容顯示。根據相對螢幕位置及出現的先後順序,建立包含將需要顯示區域資訊的繪製任務,放入非同步繪製執行緒佇列中,發起繪製流程。
- 非同步繪製執行緒佇列取出繪製任務,進行圖文繪製,最終輸出一張包含了圖文內容(如商家卡片)的圖片。繪製任務結束後,通知主執行緒隊繪製完成,主執行緒隨後展示繪製區域。
整體按照佇列間序列、佇列內並行的方式執行。
業務應用
Graver 在外賣內部發布之後,我們也將其推廣到更多的業務線,並希望 Graver 能夠成為對業務開展有重要保障的一項基礎服務。經過半年多的內部試用,Graver 的可靠性、渲染效能、業務適應能力也受到外賣內部的肯定和認可。截止發稿時,Graver 已經基本覆蓋了美團 App 的外賣頻道、獨立外賣 App 核心業務場景的大多數業務。下面列舉 Graver 在外賣業務的部分應用案例:
經驗總結
總結一下,對於介面渲染效能優化而言,要站在一個更高角度來思考問題的解決方案。橫向上,從普適性角度解決效能瓶頸點,避免其他人遇到類似問題的重複工作;縱向上,從長遠考慮問題做到防微杜漸,一次優化,長期受益。基於此,我們提出一站式、標準化的渲染效能解決方案。誠然,這會遇到很多難點。面對介面樣式構建的問題,系統 UIKit 框架著實為我們提供了便利,然而有時候我們需要跳出固有思維,嘗試建立一套全新介面構建、視覺元素分解的思路。
參考資料
作者簡介
洋洋,美團高階工程師。2018年加入美團,目前負責【美團外賣】和【美團外賣頻道】的 iOS 客戶端首頁業務,以及支撐首頁業務的技術架構、工具和系統的開發和維護工作。
招聘
美團外賣長期招聘 Android、iOS、FE 高階/資深工程師和技術專家,Base 北京、上海、成都,歡迎有興趣的同學投遞簡歷到chenhang03#meituan.com。