導言#
本文將圍繞跨端框架技術的主題,分析其技術目標和 3 種演進方向,接著揭秘業內的自繪跨端方案的技術實現 —— 包括 Kun、WebF、TDF、Weex 2.0、Waft 與 MiniX 等方案,分析各自的特點與不足,總結跨端框架的研發思路與技術要點,最終分享對跨端框架發展趨勢的思考。
分享過程中,會穿插介紹跨端框架的腳本引擎的選型與技術難點、業內各跨端框架各自的困境、分享 Debugger 原理以及一核多生態的工程化思路。
大綱如下:
- 跨端框架的技術目標(略)
- 跨端框架的技術方向
- 跨端框架的技術揭秘
- 跨端框架的技術要點
- 跨端框架的發展趨勢(略)
注 1:本文系線下分享的文字總結版,將省略前情提要、技術背景(如第 1 節、第 5 節)與部分技術細節的擴展(如第 3-4 節部分內容),僅保留核心內容。
注 2:本文材料源於 GMTC 大會跨端主題的公開分享、部分企業的公開微信公眾號文章、框架公開源代碼與個人歷史分享素材,其餘內部分享與內部框架等材料做脫敏處理。
跨端框架的技術方向#
我略微總結了一下,跨端框架有以下 4 種技術方向。
方向 1:基於 WebView 的增強#
基於 WebView 的增強是一個偏前端往客戶端方向靠攏的技術方向,即上層生態依然使用 Web 技術,但是需要依靠客戶端對 WebView 做一些能力補充。
比如:
- Ionic、Cordova 等 Hybrid 框架。
- 業內一眾基於 WebView 的小程序。
- Sonic 等客戶端預加載 WebView 資源的方案。
- App 廠商針對 Web 做的離線包方案。
這類框架都有這幾個特點:
- 基於 WebView 渲染,但補充了一些原生能力增強
- 開發生態基於 Web 前端生態(嚴格來說,小程序也是)
- 想方設法增強 Web 的用戶體驗
方向 2:基於 DSL 的 Native 增強#
基於 DSL 的 Native 增強屬於偏客戶端但往前端方向靠攏的技術方向,即開發生態基於 Native,但是框架設計上參考了 WebView 的一些特性。這些框架總體上都是自定義了 DSL 來實現跨端與動態化的。
比如手淘的無障礙框架 DinamicX、美團的 MTFlexbox、阿里的 Tangram(七巧板)。
瀏覽這些框架的文檔可以發現,它們的設計比較像 React Native,只是上層需要開發者使用 DSL 來接入組件。
方向 3:代碼共享#
代碼共享是終端的開發方案,目前業內熟知的方案是 KMM(Kotlin Multiplatform Mobile),通過 K2 編譯器將 kotlin 源碼編譯成各個平台的目標代碼,從而實現跨端。具體而言,kotlin 通過編譯器前端生成帶有語義信息的 FIR,之後 FIR 交給各平台的編譯器後端來進行優化和生成,如 JVM/LLVM 等,最終生成各平台可執行的目標代碼。
KMM 的工程結構也比較簡單,包括跨端代碼 (Shared module) 與殼工程 (Android/iOS App) 兩部分組成。
目前來看這套方案在生成 iOS 代碼時,對多線程的邏輯處理不是特別好,需要業務方優化。
方向 4:基於 GPL 的 Native 增強#
基於 GPL 的 Native 增強我將其視為大終端的開發方案。所謂大終端是一個融合之後的產物,在早年 PC 時代,大家使用 .NET、JVM、Qt 來開發桌面應用,我們將其稱為終端開發;隨後進入移動端時代,Native 方向的客戶端開發在不斷追逐動態化之路,而跨平台方向的前端開發在不斷追逐性能之路,這兩條道路最終融合成如今這些跨端框架。無論是小程序、Flutter、DSL 開發框架、WASM 均屬於融合演進的產物。
總結一下,方向 4 有以下幾類方案:
- 原生渲染組件:如 React Native / Hippy 1.0 / Weex 1.0 等。
- 自繪引擎:Flutter。暫且將其單獨算作一檔。
- 基於 Flutter 的自繪框架:這裡業內有諸多框架(曾整理過 20+ 框架),如 WebF (Kraken),Kun,FMP,基於 Skyline 的小程序等等。
- 基於系統圖形庫(Skia / Vulkan / Metal / OpenGL)的自繪框架:這裡業內也有不少框架,如 TDF,Hippy 3.0,Weex 2.0,Waft 等等。嚴格來說 Flutter 也屬此類。
前 3 個方向以及第 4 個方向的前兩類框架都是開源的,且業內也有不少文章介紹了它們的原理,這裡就不贅述了。本文主要介紹第 4 個方向後兩類框架的技術方案。
跨端框架的技術揭秘#
本節將挑選幾個有代表性的框架做技術揭秘:
- Kun
- WebF
- Weex 2.0
- TDF(需脫敏,略過)
- Waft
- MiniX(略)
之所以在芸芸框架中挑這幾個,是因為他們的方案在領域細節中屬於典型框架,可關注這些框架的應用開發體系、腳本引擎與渲染引擎的選型。
Kun#
Kun 是閒魚基於 Flutter 開發的一個跨端框架,目前並未開源,網上能學習到的文章只有閒魚公眾號上發表的三篇文章。架構設計比較簡單,雖然沒有源碼也能分析一二,架構圖這裡就不放了,有興趣的同學可以自行點進文章了解。
Kun 的整體設計思路是基於 Flutter 開發一個 JS Runtime,開發者使用前端生態進行頁面開發,JS 解釋器作為膠水層會將源碼翻譯成 Flutter Widget,之後交給 Flutter Engine 做渲染。
JS 引擎他們採用了 QuickJS,但猜測應該是阿里內部的 QKing 引擎(基於 QuickJS)。
Debugger 支持 CDP,Test 基於 Flutter Golden test。
CSS 解析他們先使用 Yoga 做 polyfill,將樣式處理成 css in js,之後解析模塊挪用了 Kraken 的遺產 CSSLib,通過 Dart FFI 將 JS 測的內鏈樣式傳遞給 Dart 側做處理,最終解析成 Flutter Widget。
但是 CSS 的盒模型與文檔流畢竟與 Flutter Widget 的樣式標準格格不入,他們則採用了 Widget 拼接的方式,每一層 Widget 特定處理某類樣式,最終通過層層套娃拼接的方式實現組件樣式。如下圖所示,這是一個 div element 所對應的拼接方式:
總結一下特點:
- 不支持完備的 W3C 標準(也不可能支持,比如 css in js 無法實現偽類),只支持各標準子集,包括:HTML 標籤、CSS 樣式集、WebAPI 標準
- 提供了一些定制的 Element 組件,由 Dart 側實現,業務方也能使用 Dart 側來開發一些定制的 Element。
- 組件的實現上採用 Widget 拼接的方式
本節參考資料:
- 大終端領域的新物種 - KUN
- 三代終端容器 KUN 的首次大考【架構演進】
- 雙十一|探索 KUN 的加載性能與增強體驗
WebF#
WebF 前身是阿里的 Kraken,後團隊解散部分遺產交接給了 Kun,剩余同學出走在開源社區成立了 openwebf,將 Kraken 改名 WebF 繼續維護。
這個是 WebF 的架構圖:
可以看到與 Kun 不同的地方在於除了提供了 JSBinding 之外,團隊還在 Flutter 的 Dart 側做了一些開發,將 RenderObject 的能力做了豐富,以適應 W3C 標準 —— 即在 Dart 層來實現 CSS,C++ 層實現 WebAPI,對標 W3C 標準。
腳本引擎依然是 QuickJS,但是目前做了一些優化,值得學習一波。
其實對比一下 Kun 和 WebF,我們可以發現他們對 CSS 的處理採用了兩種不同的思路。
先說說 Kun 吧,它的方案存在幾個問題:
- 一條渲染鏈路存在兩次 Layout,這是完全沒有必要的,而且 Layout 的更新頻率本身也非常高,兩次 Layout 會帶來額外的性能開銷
- Dart FFI 不足以支撐樣式更新的信息傳遞,樣式更新的數據量很大,會觸及 FFI 的瓶頸
- 內聯樣式的開發體驗不好,很多 CSS 的屬性也會無法實現
那麼 CSS 應該如何實現呢?有兩種比較好的解法:
- CSS 在 Dart 層實現,樣式更新依靠 RenderObject 的 Layout,無需走 FFI
- DOM 與 CSS 全使用 C++ 實現,剝離 Dart 層
解法 1 便是 WebF,解法 2 是後文的 Weex 2.0 與 TDF 等框架。
但解法 1 也存在技術難點,因為引入了 CSS 會導致 RenderObject Tree 難以維護,那麼我們應該如何管理 RenderObject Tree?這也有兩種思路:
- 把 RenderObject 做薄:即 Flutter Widget 做原子級渲染組件,不對 RenderObject 做修改,上層通過組合 RenderObject 來實現複雜功能和樣式。就像 Kun 那樣。
- 把 RenderObject 做厚:集成大量的佈局渲染能力於一身,上層通過樣式表驅動 RenderObject 渲染。
顯而易見的,把 RenderObject 做厚會是更好的方案,因為前者複雜度太高(看前面那段層層嵌套的代碼也可以直觀感受到),每個樣式規則的計算都需要一層一層檢查推斷,導致維護效率下降。
因此,這裡我比較看好 WebF 的方案,並且 WebF 也是目前眾多跨端框架中唯一一個擁抱開源的方案,呼籲有興趣的同學加入 TSC 一起共建。
本節參考資料:
- https://github.com/openwebf/webf
- 晟懷:《WebF 是如何高性能實現 Flutter + Web 融合》(2022 QCon)
Weex 2.0#
Weex 2.0 是阿里內部開源的跨端方案,目前基本上實現了阿里內部的一核多生態體系。技術架構上完全推倒 1.0 重新研發,期間他們也走了不少探索之路。從分享來看,整套方案比較完備,工作量也很大。
這個是 Weex 2.0 的結構圖:
重點介紹一下這幾個組件:
- WeexAbility:容器和能力擴展,URL 攔截、緩存、基礎 API、三方擴展等。
- WeexFramework:通用基礎框架。封裝頁面實例,實現 DOM、CSSOM、WebAPI 標準,解耦腳本引擎和渲染引擎。
- QKing:腳本引擎,基於 QuickJS 的魔改。
- Unicorn:自繪渲染引擎。實現 CSS 能力,包括完整的節點構建、動畫、手勢、佈局、繪製、合成、光柵化渲染管線,可跨平台。
- WeexUIKit:原生 UI 渲染引擎,封裝了原生組件。
2.0 源碼產物和前幾個框架一樣是基於 jsbundle 打出來的 bytecode,但是編譯做了一些 SSA 的優化,此外 JS 運行時也做了許多優化,全鏈路使用 C++ 開發,沒有額外的通信開銷、沒有冗餘的抽象、鏈路更短,同時基於自研的 Unicorn,有著精簡佈局算法、精細的操控手勢和動畫,直通系統圖形庫。整套方案與 1.0 毫無關係,解決了 1.0 的跨語言通信問題、雙端渲染差異問題、佈局算法問題、腳本執行效率問題。
基於 Weex 2.0,阿里解決了煙囱式方案的問題,基於多核同構的內核,推動了基礎能力的統一,以此來支持差異化的業務場景:
本節參考資料:
- 門柳:《淘寶新一代自繪渲染引擎的架構與實踐》(2023 GMTC)
注:騰訊的 TDF 也在致力於類似的工作,此處脫敏不再介紹。
Waft#
Waft 全稱 WebAssembly Framework for Things,是天貓精靈團隊基於 WebAssembly Runtime 與 Skia 開發的一套自繪框架,沒有開源。雖然它目前沒有實現框架,只支持 AIoT 的場景,但是原理上是可以跨端的,因此放在這裡介紹下,以開闊思路。
天貓精靈早期在 AIoT 上有過一些嘗試,最開始做 Android App,但無奈運存太低,只有幾百兆,所以性能受限;後續他們開發了雲應用,效果雖然還可以,但是服務器成本太高,被叫停;於是繼續探索端渲染的道路,研發了 Waft。
這個是 Waft 的架構圖:
他們也重新設計了加載流程和渲染流程:
可見整體工作量比較大,並且也不契合前端標準和生態。
這裡腳本引擎選型 WebAssembly 他們提供了一張對比圖:
這裡我對這個腳本引擎的選型是存疑的,想了想有以下不足之處(也可能他們內部有其他考量):
- fib 的用例太簡單,無法充分發揮 JS 引擎的優勢
- AOT 來對比解釋執行,是明顯不公平
- QuickJS 應該用的原始版本,它還有很大的優化空間
- 用力也沒有去對比其他有 JIT 模式的引擎,比如 V8 和 JSCore 這些
- 這裡沒有說明使用了什麼 wasm 的框架,因為不同 wasm 的實現性能表現是不同的,有的側重於解釋執行的效率,有的則側重於 AOT / JIT 的效率
Waft 本身也有的問題,期待他們後續能優化:
- CSS 僅支持部分子集
- W3C 標準(DOM Elememt、WebAPI)實現欠缺
- 包體積可能偏大,這部分先存疑
所以目前的 Waft 的實現也決定了應用場景,暫且只能支持簡單的 IoT 頁面。
參考資料:
- 聂鑫鑫:《Waft:基於 WebAssembly 和 Skia 的 AIoT 應用開發框架》(2023 GMTC)
跨端框架的技術要點#
動態化#
介紹了以上框架,可以總結下跨端框架的應用場景:
- 動態化
- IoT
- Desktop
- 車機
- 一核多生態
所謂 “沒有動態化能力的跨端技術是沒有靈魂的”,其實我們也可以發現動態化框架和跨端框架很多部分其實是完全重疊的,我之前總結過動態化的五種實現思路:
- 基於 WebView 的增強
- 基於 DSL 的 Native 增強
- 基於 GPL 的 Native 增強
- 插件化(Android)
- 利用 OC 運行時動態化特性(iOS)
我還畫了一張圖來補充說明:
注:這張圖我畫的比較早,其實左上角可以換成 “Flutter 與其他自繪框架”。
他們的核心其實都是要在 Runtime 期間加載可執行代碼,並調用。可以發現前三個動態化的思路和我們總結的跨端框架的技術方向是一模一樣的。
技術要點個人以為有以下幾點:
- 腳本引擎
- 渲染引擎
- 調試器
- 工程化
一一來介紹。
腳本引擎#
腳本引擎的選型思路有以下三個:
- JS 引擎:僅用於膠水語言,對 JIT 不強依賴
- Dart VM:主要是為了利用 Flutter Engine 來渲染,因此使用 Dart 生態
- WARM: 需要設計 DSL 和實現渲染引擎,完善整個生態
如果選擇 JS 引擎,那麼也有以下幾個選型思路:
- 使用雙引擎:即各端使用自己的優勢引擎,Android 使用 V8,引入 j2v8 即可,而 iOS 使用 JSCore 則完全無包增量。但可惜的是直接使用 JSCore 無法開啟 JIT。
- 使用 Hermes 單引擎:Meta 為 React Native 這類 Hybrid 框架專門開發的腳本引擎,開箱即用。
- 使用 QuickJS 單引擎:大神開發的 JS 引擎,勝在體積極小,性能優秀。
- 使用自研 JS 引擎:基本上業內都是基於 QuickJS 做優化的。
小結了一下 QuickJS 目前存在一些問題:
- 沒有 JIT,這個按需實現吧,有 JIT 雖然執行效率上了個數量級,但是作為膠水語言而言看重的不是這些。JIT 會導致冷啟動耗時增加、內存占用變大、體積變大,而且 iOS 還不能用。
- 手動 GC,難以管理和維護,可優化
- 缺失行號記錄
- 缺失 Debugger,目前 github 有一些開源插件實現了
- 缺少 code cache
- 缺少 inline cache
- 缺少內存泄露檢測能力
- Bytecode 有許多優化的空間
渲染引擎#
渲染引擎選型思路有二:
- 基於 Flutter Engine
- 基於系統圖形庫,如 Skia / OpenGL / Metal / Vulkan
不管基於啥,框架的整體思路都是精簡管線,並使用同步光柵化。
調試器#
Debugger 一種可以讓 JavaScript Runtime 進行中斷,並可以實時查看內部運行狀態的應用,是提供開發者使用的工具,作為框架而言必不可少。
目前主要有三種調試協議,剛才介紹的框架都至少實現了其中一種:
- CDP: Chrome DevTools Protocol
- DAP: Debug Adapter Protocol
- 自建協議:微信小程序早期就是自建協議
工程化方案#
工程化至少包括以下工作:
- 資源加載方案
- 降級處理
- 版本管理
- 研發模式
這裡之前 Q 音開發的 Kant 在工程化上有過詳細的設計與實現,此處不展開說了。
總結#
自繪框架常遇到的問題與解題思路:
- 開發體驗差:生態使用前端生態,即提供 JS Runtime;需要提供 Debugger;IDE 需要支持語言服務。
- 文檔寫的不好:寫好文檔。
- CSS 能力不夠用:對齊標準;如果 Dart 側實現 CSS,需要把 RenderObject 做厚。
- 樣式和 H5 不一樣:堆測試用例,配合 WPT 驗證
- Android 和 iOS 不一致:利用已有資源,可基於 Flutter
- 組件太少,沒有生態:對齊 W3C 標準,盡可能完備
- JS 執行性能差:自研 JS 引擎
- 不夠標準,無法復用社區庫:對齊 W3C 標準,盡可能完備
值得學習的一些經驗:
- 標準至上
- 提供豐富的文檔
- 少自研,合理利用已有資源
- 開發體驗很重要
- 關注低端機表現
參考資料與擴展閱讀#
- PPT (已脫敏): https://weekly.ursb.me/slide/cross-end/
- 門柳:《淘寶新一代自繪渲染引擎的架構與實踐》(2023 GMTC)
- 聂鑫鑫:《Waft:基於 WebAssembly 和 Skia 的 AIoT 應用開發框架》(2023 GMTC)
- 晟懷:《WebF 是如何高性能實現 Flutter + Web 融合》(2022 QCon)
- 吉豐:《大終端領域的新物種 - KUN》
- openwebf/WebF
- Airing:《Kant 在「QQ 音樂」的實踐》(未公開)
- Airing:《Flutter 動態化方案》(未公開)