![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CO5ITMjFmNmN2MhZTM1ATO4UmZ0Y2YjRzNxYmNjVjYx8CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
CodeHub#7 正式落幕,來自螞蟻集團的技術專家「京君」與掘金社群的開發者們分享了「支付寶」全新的卡片技術棧——魔方卡片(Cube)。
京君圍繞 Cube 技術的架構邏輯,闡述其渲染和生産過程,并指導開發者完成初階的技術調試。
▶
點選播放 ,可觀看 CodeHub#7 完整回放;公衆号mPaaS背景回複“Cube”可擷取講師完整PPT。![]()
Cube 技術解讀 | 詳解「支付寶」全新的卡片技術棧動态卡片的背景魔方卡片(Cube)概要Cube卡片的生産&工作流程核心系統架構線程模型和資料模型高性能清單渲染Native技術優化魔方卡片(Cube)的現狀和規劃
動态卡片的背景
從 Windows 時代開始,應用程式圖示就成了使用者(流量)的主入口,并且一直持續到移動端時代。圖示即入口的方式,缺點是不直覺,最少需要一次點選後才能接觸到想要的資訊。在此背景下,iOS系統和部分Android系統實作了把内容和服務前置的卡片,如下圖所示:
此外,鴻蒙系統也提出了類似的卡片場景,作為某種流量入口。實際上,在應用内部的卡片作為内容展示以及服務入口的場景則更為普遍,比如支付寶首頁和招行銀行的理财頁面,其中每個小矩形都是一個卡片。對于營運來說,卡片樣式和内容可以随時配置,不用等待應用版本更新,也是某種剛需。
魔方卡片(Cube)概要
魔方卡片(Cube)是螞蟻集團内部自研的一套跨平台動态化卡片解決方案,是服務于應用頁面内的區域動态化技術,面向内容營運,幫助産品技術提高開發效率和營運效率。每一個魔方卡片(Cube)獨立嵌在原生頁面内的一個區域,區域内容通過卡片模版進行展示。
這裡展開講下高性能。魔方卡片(Cube)追求的是接近native原生體驗。我們定義了兩個次元:
一個是極緻的性能。在Cube小程式能力的基礎上,我們去掉了一些複雜的css能力,例如僞類僞元素、inline/block等,同時也對js的能力做了限制(魔方卡片使用quickjs作為腳本引擎)。此外,我們還對quickjs做了一些優化,包括不限于離線atom編譯優化,異步gc優化等。我們也引入了wamr作為quickjs的“協處理器”,支援使用者使用javascript和assemblyscript混合開發。這樣使用者可以用assemblyscript一些熱點函數或者子產品。
另一個次元是極緻的記憶體。在資訊瀑布場景無限下拉,魔方卡片的記憶體增長接近Native卡片。我們對卡片的能力做了比較精細分級,通過在開發時配置,減小運作時的記憶體消耗。下圖展示了一個簡單卡片,如圖所示魔方卡片的工程目錄,以及錢包某個卡片的真實代碼和運作效果。
Cube卡片的生産&工作流程
- 研發期
-
- 本地開發
魔方卡片(Cube)配套獨立的開發工具,支援卡片的編譯、日志輸出、實時預覽等功能,vue作為目前開發模版的dsl語言,支援js、css編輯卡片樣式。
-
- 卡片管理
卡片本地開發完成後,通過卡片管理背景将卡片編譯産物上傳釋出,可以對卡片進行版本管理,卡片釋出後就可以在用戶端進行卡片的動态更新。
- 運作期
為了友善端上業務接入魔方卡片,我們引入了一個魔方卡片容器(CardSDK)的概念。CardSDK把一些和業務層/服務端聯系緊密的,且通用能力做了一些封裝。例如我們通過CubeCardSdk從服務端拉取卡片和業務資料。此外CardSDK也負責常用的JSAPI、第三方元件的接入。這樣魔方卡片能夠更專注于卡片産品本身。
核心系統架構
魔方卡片(Cube)的系統架構主要包括JSEngine、CardEngine、RenderEngine和Platform幾部分,絕大部分代碼都是C++實作。
- JSEngine
主要負責卡片js邏輯執行和卡片資料變化監聽,進而支援開發者在卡片内部寫一些業務邏輯能力實作卡片内容和樣式的動态變化。
因為卡片場景對性能要求較高,綜合包大小和性能等方面考慮,我們選擇了quickjs作為我們的js基礎引擎庫,同時實作了一個非常小的js響應式架構(JSFM),用來支援卡片内的邏輯代碼能力。
- CardEngine
主要負責卡片資料的解析和綁定、卡片邏輯渲染、建構DOM指令、JSAPI管理、JSBinding、Native事件通信等。
卡片DOM樹的初始化建構過程,我們并沒有把它放在js運作時,而是在卡片執行個體初始化鍊路中直接通過C++進行指令生成和樹建構,一方面是為了保持js架構更小更快,另一方面C++的運作效率更高。
- RenderEngine
後端渲染底座,負責卡片布局計算、樣式解析、Layer計算、自繪制元件、同層渲染、光栅化上屏等過程,以及手勢、動效等互動效果。
- Platform
平台相關接口,包括原子view封裝、Canvas API、三方元件擴充協定、動畫api等。
線程模型和資料模型
線程模型
魔方卡片(Cube)生命周期内的主要線程包括業務線程和引擎線程,業務線程是卡片資料的初始化階段由業務發起執行,是卡片生命周期的beforeCreate階段。引擎線程是所有卡片生命周期運作階段的共有線程,主要包括Bridge線程、Render線程、Paint線程和UI主線程。
- Bridge線程
js運作時線程,也是Dom節點資料查詢和處理線程,因為基于魔方卡片小、快的定位,js邏輯隻是卡片一個輔助能力,不具備過于複雜業務邏輯能力,是以Bridge線程相對較輕,并設計為單線程模式。
- Render線程
渲染相關資料計算線程,包括渲染樹建構、節點層級計算、Layer分層繪制計算、手勢資料計算以及渲染任務建構,Render過程主要涉及樹的遞歸計算過程,相對渲染過程耗時很短, 設計為單線程模式。
- Paint線程
繪制線程,執行卡片節點分層繪制及光栅化任務。Paint線程并不是一個固定的線程,根據目前任務模型,Paint線程可能是主線程,也可能是一個線程池裡的子線程;在同步渲染模式下,Paint線程直接是主線程;而在異步渲染模式下,通過一個線程池來實作Paint任務的并發渲染,提高渲染效率,例如在清單滑動場景。
- UI主線程
UI操作主線程,即為目前的平台線程,主要包括手勢識别、UI上屏和三方擴充元件的資料更新等。
除了以上涉及的主要線程外,還有埋點和監控相關的playground背景線程,整體優先級比較低。整體的線程模型設計,最大限度減少UI主線程壓力,提高卡片并發渲染效率。但目前還有一些不足,包括UI線程切換頻繁、Bridge線程越來越重等,後面會繼續優化線程模型。
資料模型
和線程模型對應的資料模型主要包括三棵樹:NodeTree、RenderTree、LayerTree,除此之外,還存在一個臨時的PaintTree;
- NodeTree
卡片原始節點樹,對應前端的Dom樹,引擎會根據NodeTree做樣式解析和布局計算;
- RenderTree
渲染資料樹,這是一顆變形樹,很多情況下它的樹層級結構和NodeTree是一樣的,其實當初在設計定義引擎資料模型的時候,我們讨論過到底要不要這棵樹,有沒有必要存在這樣一顆和NodeTree層級一樣的樹,最終我們還是保留了,原因是這棵樹可以比較靈活的調整樹關系,如果把卡片分為布局階段和渲染階段,那麼這顆樹就是渲染階段的源樹。
事實證明我們的決定是正确的, 我們後續支援的zindex/static等能力,都是因為這棵樹的存在可以在引擎層很好的去支援, 而不用在平台層去模拟實作這種層級變更能力進而導緻很有限的場景支援,包括以後我們做渲染快照技術也可以從這顆樹去考慮。
- LayerTree
LayerTree樹,顧名思義就是一個分層樹,在RenderTree基礎上對節點進行分層,同一層的節點在同一個渲染任務管線内做繪制光栅化,不同層之間互相獨立,可以并發渲染。
- PaintTree
PaintTree是一個臨時樹,其實嚴格的說是一個拷貝樹,是通過RenderTree拷貝一個子樹,每次發生渲染時臨時生成,當然也會做些節點優化處理,例如被完全蓋住的節點會被優化調,避免重複渲染。每一個layer上存在一個PaintTree,通過PaintTree進行節點繪制生成光栅化指令或位圖。
高性能清單渲染
對于清單内使用卡片的場景,主要考慮的是卡頓影響,尤其是中低端機裝置。魔方卡片(Cube)支援異步渲染,是以在清單場景下可以很流暢,同時因為支援多線程并發能力,可以多張卡片并發渲染,是以在異步渲染條件下也不會有明顯的白屏效果。
Native技術優化
我們期望卡片服務于頁面内區域化内容動态展現和簡單業務邏輯,更多的是面向移動端開發者。即使我們使用的卡片DSL語言描述是前端語言,我們也希望能夠對CSS的使用做限制、支援有限的CSS能力,但同時也希望盡可能覆寫到一些開發者常用的CSS能力。
是以我們針對CSS能力做了一個專項工作,和前端團隊技術同學一起做了魔方卡片(Cube)CSS能力規範,對CSS能力做了限制限制。即便如此,在Native渲染引擎下,想非常好的去支援這些能力,也是有很多困難,包括zindex的支援、overflow等,是以我們也基于一些依賴的平台能力也做了優化處理。
Layer容器
我們引入Layer容器概念,前面介紹資料模型時,提到了LayerTree,每一個Layer節點是一個獨立的渲染容器,由平台View作為Layer容器來渲染其他虛拟節點。如果按照正常的做法是一個View對應一個渲染容器,我們使用兩個View組合為Layer容器(iOS使用CALayer),将内容層和邏輯層分離,這樣做的好處很多,例如layer節點的shadow繪制限制裁剪問題、内容層的畫布切割優化等。
手勢改造
手勢的優化改造主要為了解決平台系統手勢分發能力的限制,不管是Android平台還是iOS平台,系統對手勢的分發處理都有一些限制,例如兄弟節點不能分發事件(iOS)、超過父節點區域無法接收事件(影響overflow能力)等,是以需要對手勢進行改造。
因為卡片渲染支援三方元件擴充,為了不影響擴充元件的事件響應,我們基于Layer容器接管改造系統手勢行為,内部進行容器節點的手勢分發管理,而對于存在三方元件混合渲染的場景,Layer容器和三方元件之間的手勢分發保持系統行為。
光栅化
魔方卡片(Cube)渲染過程包括指令渲染和位圖渲染兩種渲染模式,這兩種模式會在不同場景條件下切換,用來優化不同場景下性能,例如幀率和記憶體。位圖渲染在Android上相對比較複雜。預設是用Bitmap作為離線渲染的緩存,缺點是引入一次額外cpu/gpu記憶體拷貝并且無法充分利用GPU資源,優勢是相容性好。我們嘗試過使用textureview作為離線渲染緩沖,發現6.0以下裝置存在嚴重的相容性問題,而且不同裝置之間的穩定性差别巨大。
同時光栅化能力依賴平台系統的Canvas API,有些高階方法會涉及硬體加速的限制,包括shadow api以及系統對glRender buffer的限制(Android平台),我們也對大畫布場景做了視圖切割分段渲染來保證渲染性能。我們同時也在着手用Skia Canvas api替代平台層的Canvas API。
同層渲染
魔方卡片(Cube)把三方元件當作獨立一層layer單獨進行資料更新,可以非常友善高效的接入擴充的三方元件。基于系統的UI能力,使擴充元件在卡片内天生統一渲染。同時支援元件在不同卡片上的複用。在實際的業務場景中,同層渲染也帶來了很多額外的問題。諸如地圖/視訊/動畫等元件,一般會伴随着較大的性能記憶體開銷。這些開銷對卡片的渲染會有負面影響,尤其在清單滾動時。對于地圖/視訊元件,我們配合元件提供方case by case的解決問題,并且試圖在卡片上線時設定卡點。對于動畫元件,Cube持續的在擴充屬性動畫/幀動畫能力,并且内置canvas能力。
魔方卡片(Cube)的現狀和規劃
目前魔方卡片(Cube)已經服務「支付寶」的首頁、證券(股票)、卡包、出行等20+的業務場景,日pv超過100億。在未來相當長的一段時間内,「支付寶」内部的業務場景,将逐漸把存量的native卡片和h5卡片Cube化。
是以,我們一方面會持續把開發者體驗做好,諸如開發調試工具鍊條;另一方面我們也将持續的優化基礎性能,諸如追求更小的包體積,更低的記憶體等。
同時,魔方卡片(Cube)Beta 已上線 mPaaS 供廣大移動開發者使用,公測期間,登入 mPaaS 控制台,立即贈送十個卡片模版免費使用。
> > 點選這裡 < <體驗魔方卡片。
卡片未來規劃的另一個方向是物聯網裝置(例如RTOS)的應用開發棧。準确說不是魔方卡片,而是卡片和小程式的某種中間形态。
物聯網裝置的界面一般比較簡單,近似卡片;但是又需要多個“卡片”之間的路由能力,更接近于應用的形态。這樣一個混合形态既能保留魔方卡片(Cube)在記憶體/性能/包體積上的優勢,又能滿足物聯網裝置應用開發的訴求。
根據我們的調研,大部分RTOS應用開發環境還是停留在傳統的C語言,效能和動态性都不理想。是以對于開發者來說,Cube 也許是一個不錯的選擇。
推薦閱讀:
《Cube 技術解讀 | 支付寶新一代動态化技術架構與選型綜述》