跨平臺解決方案的技術分析
背景
近 20 年是中國互聯(lián)網(wǎng)蓬勃發(fā)展的時代,以 2010 年為界限,前 10 年是 PC 互聯(lián)網(wǎng)時代,PC 互聯(lián)網(wǎng)時代培養(yǎng)了國民上網(wǎng)沖浪的用戶習慣,為后 10 多年的以智能手機為終端的移動互聯(lián)網(wǎng)時代帶來豐厚的人口紅利,而在移動互聯(lián)網(wǎng)時代,以智能手機為依托的軟硬件也就成為各大互聯(lián)網(wǎng)公司爭奪流量的焦點戰(zhàn)場。
移動互聯(lián)網(wǎng)發(fā)展早期,或是依托母公司的強大技術實力支撐,或是抓住時代發(fā)展機遇,或是努力創(chuàng)新求變,一批又一批現(xiàn)象級的移動應用成為廣大用戶的生活工作等方面難以分割的一部分,類似微信之于社交通訊、支付寶之于支付理財、頭條之于資訊創(chuàng)作、美團之于生活服務等等不一而足。移動互聯(lián)網(wǎng)發(fā)展至今,早已是一片紅海市場,早期發(fā)展起來的 APP 要么成為國民級別的超級 APP,要么在垂直領域深耕難以撼動。
那么,行業(yè)龍頭型 APP 如何持續(xù)拓展服務邊界,快速響應市場需求變化以保持競爭優(yōu)勢,后進的 APP 如何通過產(chǎn)品、商業(yè)模式創(chuàng)新,迅速切入市場,提高研發(fā)的靈活機動性同時不降低產(chǎn)品的用戶體驗。
針對當前移動互聯(lián)網(wǎng)的發(fā)展現(xiàn)狀,跨平臺開發(fā)的概念和解決方案應運而生??缙脚_開發(fā)的誕生使命就是圍繞著研發(fā)效能和用戶體驗兩個主題去打造的,但是就如同一個符合特定場景和高效算法在時間和空間上的 trade-off,跨平臺解決方案的不同實現(xiàn)在研發(fā)效能和用戶體驗上同樣面臨權衡取舍。
本文旨在介紹不同跨平臺解決方案的技術架構和特點,分析各個解決方案的優(yōu)勢和不足之處,以便對業(yè)界當前的跨平臺技術方案建立起整體的認知和對團隊的技術選型提供一定的參考作用。注意的是,這里的跨平臺特指的是針對 iOS 和安卓進行的跨平臺開發(fā)。
跨平臺解決方案
根據(jù)采用的渲染技術不同,跨平臺解決方案可分為以下三類:
Web 渲染方案 原生渲染方案 自建渲染引擎渲染方案
Web 渲染方案
Web 渲染方案主要是使用原生 WebView 控件渲染 HTML 頁面,并在原生應用中定義可供 H5 頁面訪問原生部分能力的接口 JSBridge,從而實現(xiàn) H5 和 Native 雙向通信,也使得 H5 的能力向端側進一步擴展。

Web 渲染方案本質上是依托原生應用的內嵌瀏覽器控件 WebView 去渲染 H5 頁面,因此 h5 App 的渲染流水線和 Web 頁面渲染相一致,能力也局限在 WebView 這一沙箱。下圖描述從 WebView 初始化到 H5 頁面最終渲染的全過程。

從上圖上看,Web 渲染方案的性能瓶頸和 Web 頁面開發(fā)中遇到的類似,即首屏渲染優(yōu)化問題,同時多出了一個 WebView 初始化的特有問題。
對于 WebView 初始化所帶來的性能開銷,不少公司針對自身的 APP 進行內核的定制化改造,諸如騰訊的 X5 內核以及阿里 UC 技術團隊的 UCWebView 等。
針對資源加載所帶來的白屏問題,業(yè)界又提出了離線包的優(yōu)化方案。所謂離線包機制,大體思路就是將原有從線上加載 H5 應用,提前下發(fā)到本地,通過 FileIO 或是內存等方式直接進行頁面渲染,達到接近原生的用戶體驗。
上面所描述的是最為原始的 Web 渲染方案,在這基礎上業(yè)內又提出 h5 容器的技術解決方案,h5 容器提供豐富的內置 JSAPI,增強版的 WebView 控件以及插件機制等能力,對原始版本的方案做了進一步功能高內聚和模塊低耦合。

下面以 Cordova 為例,概述一下 H5 容器的大致架構,Cordova 是 Apache 一個開源的移動開發(fā)框架,這一框架的核心實現(xiàn)原理就是基于 Web 渲染技術。

圖片來源:Cordova 官網(wǎng)
Cordova 應用程序由幾部分組成:
Web App
應用程序代碼的實現(xiàn)地方,采用的是 Web 技術,應用運行在原生控件 WebView 中
HTML Rendering Engine
應用的渲染引擎,即 WebView,該渲染引擎是頁面和 Native 實現(xiàn)雙向通信的橋梁
Cordova 插件
提供了 Cordova 和原生組件相互通信的接口并綁定到了標準的設備API上。這使你能夠通過JavaScript 調用原生代碼,這些核心插件包括的應用程序訪問設備功能,比如:電源,相機,聯(lián)系人等。
Mobile OS
原生系統(tǒng)層,提供系統(tǒng)能力
小程序
小程序是微信在 2017 年提出一項創(chuàng)新性的輕應用,不需要下載安裝即可使用。記得 Brendan Eich 曾對 JavaScript 的評價它的原創(chuàng)之處并不優(yōu)秀,它的優(yōu)秀之處并非原創(chuàng)。我想微信小程序也是這樣,因為小程序采用的技術手段仍脫離不了 Web 渲染方案,即采用 WebView 作為渲染引擎、JSBridge 的封裝和離線包機制等,但是其最大創(chuàng)新之處在于將渲染層和邏輯層進行了分離,提供一個干凈純粹的 JavaScript 運行時,多 WebView 的架構使得用戶體驗進一步逼近原生體驗。

圖片來源:微信小程序官網(wǎng)
具體來看,小程序的渲染層和邏輯層分別由兩個線程管理,渲染層采用 WebView 進行頁面渲染(iOS 使用 UIWebView/WKWebView,Android 使用 WebView),小程序的多頁面也由多 WebView 接管。邏輯層從 WebView 分離,使用 JavaScript 引擎(iOS 使用 JavaScriptCore,Android 使用 V8)單獨開啟一個 Worker 線程去執(zhí)行 JavaScript 代碼。邏輯層和渲染層之間的通信經(jīng)由 Native 層中轉,網(wǎng)絡 IO 也通過 Native 層進行轉發(fā)。

和之前的 Web 渲染技術相比較來看,小程序采用多 WebView + 雙線程模型的架構。由多 WebView 構成的視圖層為頁面性能賦予更加接近原生的用戶體驗,單個 WebView 承載更加輕量的頁面渲染任務,JavaScript 腳本單獨抽離在 Worker 線程限制了開發(fā)者直接操作頁面的能力,進一步約束在微信小程序的規(guī)范下,這也是小程序無法直接操作 DOM 的緣由。
這里多提一點的是,小程序的組件分為原生組件和非原生組件,對于原生組件而言,這就脫離的 Web 渲染方案的范疇,屬于原生渲染方案的一部分,所以從這點上看,小程序也可以算得上是 Web 渲染和原生渲染的融合解決方案。綜上來看,Web 渲染跨平臺方案經(jīng)歷了三個階段性的發(fā)展,從原始時期的 h5 + JSBridge + WebView,到 h5 容器的抽象提升,再到目前如火如荼的小程序。不難看出,Web 渲染方案的有如下特點:
開發(fā)效率高
采用 Web 技術,技術門檻相對較低,技術人員積累豐厚,社區(qū)資源豐富,對前端友好,一次開發(fā),多端運行
動態(tài)化好
Web 技術的天然動態(tài)特性支持,無需發(fā)版
表現(xiàn)一致性佳
Web 頁面除了個別元素和屬性的差異、多屏適配外,其雙端表現(xiàn)相對一致
性能較差
頁面采用 WebView 渲染,頁面加載耗時長,功能受限于沙箱,能力有限,難以承接復雜交互或是需要高性能的任務,整體用戶體驗差

原生渲染方案
Web 渲染方案的致命弱點在于無法出色地完成高性能和體驗的目標,但是其良好的社區(qū)生態(tài)、跨平臺一致性和高研發(fā)效率都是其無法忽視的優(yōu)勢,那么如何做到二者的平衡,答案就是原生渲染方案。
原生渲染方案的基本思路是在 UI 層采用前端框架,然后通過 JavaScript 引擎解析 JS 代碼,JS 代碼通過 Bridge 層調用原生組件和能力,代表的框架是 React Native 和 Weex。
從原生渲染方案的實現(xiàn)思路不難看出,頂層采用類 Web 框架用于降低開發(fā)成本和統(tǒng)一技術棧,UI 的渲染通過 JSBridge 由原生控件直接接管,從而獲得性能和體驗的提升。
下面以 React Native 為例,具體展開講解一下原生渲染方案,React Native 的整體架構圖如下:

React 層
最頂層是 React 層,利用 React 框架進行 UI 的數(shù)據(jù)描述,開發(fā)者使用 Class Component 或 Functional Component 進行頁面開發(fā),框架內部將會把頁面描述轉化為 ReactElement 這一代表的虛擬 DOM 的數(shù)據(jù)結構,用于運行時的 Diff 對比和消息收發(fā)等
[JS Bundle 中間產(chǎn)物]
React Native 通過 metro 打包功能直接將整個 RN 應用打包為一個 JSBundle,通過 Bridge 層在 RN 應用初始化時加載整個 JS 包進來
Bridge 層
Bridge 是連接 React 和 Native 的中間層,React 層的 UI 需要通過 Bridge 層的 UIManager 接口實現(xiàn)原生控件的創(chuàng)建和更新,通過 NativeModules 接口實現(xiàn)原生能力的調用
Native 層
在 Native 層中,Native Modules 實現(xiàn)了與上層交互的原生能力接口,Native UI 實現(xiàn)終端實際的控件展示,Yoga 跨平臺布局引擎實現(xiàn)了基于 Flexbox 布局系統(tǒng)的 JS 和 Native 的鏡像映射關系。值得注意的是,整個 RN 架構中,存在以下 UI 視圖數(shù)據(jù)結構:

下面從線程模型角度,分析一下 RN 的運行機制:
UI 線程
應用的主線程,用于處理原生控件的繪制
JS 線程
React 構成的 JS 代碼運行在此線程
Shadow 線程
主要用于構建 JS 與原生控件的布局鏡像數(shù)據(jù)
Native Modules 線程
提供原生能力,這里采用的是多線程模型,iOS 端通過 GCD 實現(xiàn),Android 端通過 AsyncTask 實現(xiàn)
RN 應用在 UI 線程進行初始化,初始化的內容包括加載 JSBundle、初始化 Native Modules 等原生能力模塊、創(chuàng)建 JSC/Hermes JavaScript 引擎,執(zhí)行 JS 代碼。
創(chuàng)建的 JS 引擎獨立在一個 JS 線程,解釋執(zhí)行 React 代碼,并將生成的布局或邏輯信息序列化后經(jīng)由 Bridge 發(fā)送給 Native。
React 代碼中視圖層的渲染通過 UIManager 調 createView/updateView 等方法,基于 Yoga 布局引擎創(chuàng)建對應的 shadowView;邏輯層中涉及原生能力調用的部分通過 RCTBridge 對象轉發(fā)到相應的原生接口。Native 接收到 Bridge 層的消息,進行視圖的更新或是功能處理。
原生渲染方案通過直接接管渲染層的方案,彌補了 Web 渲染方法在性能和體驗上的不足,同時在頂層采用類 Web 的語法集,將開發(fā)技術邊界延展至 Web 領域,同時可以很好的復用當前前端主流 UI 框架 React/Vue 的繁榮生態(tài)系統(tǒng)。
雖然原生渲染方案有上述的優(yōu)勢,但是有一個致命的弱點就是 Native 層和 JS 層的通信所帶來的性能瓶頸。一方面頁面的更新和事件的響應經(jīng)由 Native 觸達 JS 層,再由 JS 層返回給 Native 層需要來回的時間成本,另一方面數(shù)據(jù)的交互需要頻繁進行序列化和反序列化的轉換。因此,在一些 UI 線程和 JS 線程存在持續(xù)頻繁交互的場景(動畫、滾動)等,RN 表現(xiàn)就不盡如人意。
自建渲染引擎渲染方案
自建渲染引擎渲染方案,是有別于 Web 渲染采用 WebView 容器進行渲染 UI、原生渲染通過 Bridge 方式轉化為原生控件渲染 UI 等方案,另辟蹊徑通過自建渲染引擎方式,直接從底層渲染上實現(xiàn) UI 的繪制,而 Flutter 就是跨平臺、自渲染的代表。Flutter 的架構設計如下所示:

整體來看,F(xiàn)lutter 應用可以分為四層:
Dart App 層
最頂層是 Dart App 層,以 Widget 為基本視圖描述單元,構建起 UI 體系
Flutter Framework 層
內置基礎的 Flutter 組件,并根據(jù)不同平臺的視覺風格體系,封裝 Material 和 Cupertino 兩套 UI 庫供上層使用
Flutter Engine 層
Flutter 框架的核心所在,包括 Dart 虛擬機、Skia 跨平臺渲染引擎、文字排版、平臺通道等,通過 Engine 層,建立起 Dart App 層和原生平臺之間聯(lián)系,從而實現(xiàn)二者的雙向通信
Embedder 層
平臺嵌入層為 Flutter App 提供宿主環(huán)境、線程創(chuàng)建以及基于插件機制的原生能力擴展等 Flutter 在打包的時候,將 Dart 業(yè)務代碼和 Flutter Engine 代碼基于 iOS/Android 不同平臺分別進行打包。Native 在啟動時會通過調用 C++ 的各自實現(xiàn)(Java 通過 JNI,OC 天然支持)初始化 Flutter Engine 層提供的接口,創(chuàng)建 UI/GPU/IO 三個線程和實例化 Dart VM。Dart 業(yè)務代碼在 Release 模式下采用 AOT 的方式進行編譯,并運行在 Dart VM 中。下面從線程模型機制,分析一下 Flutter App 的運行機制:

Platform 線程
Flutter 的主線程,由 Native 創(chuàng)建。負責平臺 vsync 信號的回調注冊,即當接收到從顯示設備的 vsync 信號后,Platform 線程驅動 UI 線程的執(zhí)行
UI 線程
負責響應 vsync 信號,執(zhí)行 Dart 層代碼,驅動渲染管線的運行,將 Widget Tree 生成 Layer Tree 并提交給 GPU 線程做進一步處理
GPU 線程
GPU 線程將 Layer Tree 轉化為具體的繪制指令,并調用 skia 跨平臺渲染引擎進行光柵化上屏
IO 線程
主要負責請求圖片資源并完成解碼,然后將解碼的圖片生成紋理并傳遞給 GPU 線程
顯示器在一幀 vblank 后,會向 GPU 發(fā)送 vsync 信號,Native 的 Plaform 線程接收到 vsync 信號后,執(zhí)行繪制幀回調方法,即驅動 UI 線程進行 UI 繪制。
UI 線程中,Native 通過調用 C++ 的各自實現(xiàn),將繪制指令通過 window 對象發(fā)送給 Dart 層,Dart 層會重構代表 UI 的數(shù)據(jù)樹(Widget Tree,Element Tree 和 RenderObject Tree)并生成布局信息。根據(jù)布局信息生成一系列繪制指令的 Layer Tree,并通過 window 對象傳遞給 GPU 線程。
這里多提一句,Dart 層通過三棵樹去描述 UI 的視圖結構。 Widget Tree 是直接面向開發(fā)者的 UI 元素的配置信息,Widget 是 Immutable 的,如果 Widget 的狀態(tài)發(fā)生更新,會發(fā)生重建。實際業(yè)務場景中,Widget 會頻繁觸發(fā)重建。 Element Tree 是 Widget Tree 和 RenderObject Tree 的橋梁,當Widget 發(fā)生變化后,會將其 Element 標記為 Dirty Element,在下一次 vsync 信號到來時進行渲染。當 Widget 掛載到 Widget Tree 時,會調用 widget.createElement 方法,創(chuàng)建其對應的 Element,F(xiàn)lutter 再講這個 Element 掛載到 Element Tree 并持有有創(chuàng)建它的 Widget 的引用 RenderObject Tree 是真正執(zhí)行組件布局渲染的工作,通過 RenderObjectToWidgetAdapter 這個 RenderObjectWidget 建立起 Widget 、Element 和 RenderObject 三者之間的聯(lián)系
GPU 線程從 UI 線程獲取 Layer Tree 構成的繪制指令,通過 Skia 這一跨平臺渲染引擎進行光柵化,繪制成幀數(shù)據(jù),將幀數(shù)據(jù)放在幀緩沖區(qū),然后等待顯示器上屏。綜上來看,以 Flutter 為代表的的自建渲染引擎方案的優(yōu)勢在于:
UI 控件是直接采用 Skia 這一跨平臺渲染引擎進行繪制
頂層使用 Dart 的語法進行 UI 的配置信息描述,并通過 Diff 算法優(yōu)化渲染流程,生成 Layer Tree 后,再調用 C++ 的代碼將布局信息發(fā)送給 Flutter Engine,F(xiàn)lutter Engine 直接通過 Skia 將 UI 控件繪制上屏。這里與原生渲染方案最大的不同點在于,Native 應用僅作為宿主環(huán)境,UI 控件不需要轉化為原生控件,直接采用渲染引擎進行繪制,從而保證了雙端的一致性和良好的性能與體驗。
Dart 在 Release下采用 AOT 的 編譯模式
Dart 代碼在 Release 采用 AOT 的編譯模式轉化為二進制代碼,從而在 Dart 運行時環(huán)境中執(zhí)行效率更高,性能也更為卓越。對比 React Native 來說,由于打包的是 JSBundle,所以在運行時仍是基于 JavaScript 運行時進行解釋執(zhí)行 JS 代碼,因而產(chǎn)生較大的性能瓶頸。
UI 層與原生層的數(shù)據(jù)交換性能更高
跨平臺技術發(fā)展現(xiàn)狀與展望
通過上文的講述,我們對不同跨平臺的技術實現(xiàn)方案有了基本了解,落實到實際業(yè)務研發(fā)層面看,這幾種方案目前都是有各自的用武之地。
對于中小型公司而言,內部技術實力不足以支撐多端研發(fā),Web 渲染方案是一種現(xiàn)實的解決措施。對于大公司來說,在 Web 渲染方案上,更是可以通過小程序框架的搭建,從而基于自家 APP 打造周邊輕應用的生態(tài)閉環(huán),同時在性能和體驗方面更進一步。
原生渲染和自建渲染引擎渲染方案對于在性能和體驗方面有著更高要求的產(chǎn)品來說,是一個合適的選擇,當然自建渲染引擎的性能上限更高更為出色。
通過分析不同的跨平臺解決方案,單純性能和體驗上考慮,自建渲染引擎是當前的一個較優(yōu)解,雖然目前 Flutter 的動態(tài)化能力還不算出色,但是其架構思路或許能夠啟發(fā)我們,去設計一套權衡不同維度的新的框架出來,以下是筆者的一種設想:
最頂層是 Web App,采用前端 DSL 開發(fā),Renderer Framework 是將前端的 UI 信息通過 JS 綁定的 C++ 層的接口經(jīng)由 JS VM 傳遞給引擎層,引擎層再調用 Skia 進行 UI 的繪制。這樣一來,就能實現(xiàn)跨平臺研發(fā)、多端表現(xiàn)一致、動態(tài)化、性能和體驗高效的目標。
參考資料
H5 容器簡介[1] 離線包介紹[2] Hybrid App 離線包方案實踐[3] Cordova 架構[4] 小程序架構[5] 微信小程序技術原理分析[6] 小程序同層渲染原理剖析[7] React Native 架構一覽[8] 「ReactNative 原理」啟動流程[9] React Native 新架構分析[10] [譯] Flutter 的編譯模式[11] Flutter 跨平臺演進及架構開篇[12] 超詳解析 Flutter 渲染引擎|業(yè)務想創(chuàng)新,不了解底層原理怎么行?[13]
參考資料
H5 容器簡介: https://tech.antfin.com/docs/2/59192
[2]離線包介紹: https://tech.antfin.com/docs/2/59594
[3]Hybrid App 離線包方案實踐: https://juejin.cn/post/6844904031773523976
[4]Cordova 架構: https://cordova.axuer.com/docs/zh-cn/6.x/guide/overview/
[5]小程序架構: https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/#小程序簡介
[6]微信小程序技術原理分析: https://zhaomenghuan.js.org/blog/wechat-miniprogram-principle-analysis.html
[7]小程序同層渲染原理剖析: https://developers.weixin.qq.com/community/develop/article/doc/000c4e433707c072c1793e56f5c813
[8]React Native 架構一覽: http://www.ayqy.net/blog/react-native-architecture-overview/
[9]「ReactNative 原理」啟動流程: https://juejin.cn/post/6844904184500715527
[10]React Native 新架構分析: https://juejin.cn/post/6893032764124168206#heading-9
[11][譯] Flutter 的編譯模式: https://zhuanlan.zhihu.com/p/61903658
[12]Flutter 跨平臺演進及架構開篇: http://gityuan.com/flutter/
[13]超詳解析 Flutter 渲染引擎|業(yè)務想創(chuàng)新,不了解底層原理怎么行?: https://developer.aliyun.com/article/759901?spm=a2c6h.12883283.1362933.27.7bd3201cRSybz5#
內推社群
我組建了一個氛圍特別好的騰訊內推社群,如果你對加入騰訊感興趣的話(后續(xù)有計劃也可以),我們可以一起進行面試相關的答疑、聊聊面試的故事、并且在你準備好的時候隨時幫你內推。下方加 winty 好友回復「面試」即可。
