移動端跨平臺開發的深度解析

戀貓de小郭發表於2019-03-04

跨平臺一直是老生常談的話題,cordova、ionic、react-native、weex、kotlin-native、flutter等跨平臺框架的百花齊放,頗有一股推倒原生開發者的勢頭。(事實上更多是共存發展)看完本篇,相信你會對於當下跨平臺移動開發的現狀、實現原理、框架的選擇等有更深入的理解

全篇內容較多,需耐心食用! (///▽///)

一、前言

為什麼我們需要跨平臺開發? 本質上,跨平臺開發是為了增加程式碼複用,減少開發者對多個平臺差異適配的工作量,降低開發成本,提高業務專注的同時,提供比web更好的體驗。嗯~通俗了說就是:省錢、偷懶

 本篇主要以react-native、weex、flutter,結合資訊展望,深入聊聊當前跨平臺移動開發的實現原理、現狀與未來。至於為什麼只講它們,因為對比ionic、phoneGap,它們更於 “naive” (˶‾᷄ ⁻̫ ‾᷅˵)。

超完整跨平臺開源專案

型別 連結
react-native github.com/CarGuo/GSYG…
weex github.com/CarGuo/GSYG…
Flutter github.com/CarGuo/GSYG…

百花齊放

二、原理與特性

目前移動端跨平臺開發中,大致歸納為以下幾種情況:

  • react nativeweex均使用JavaScript作為程式語言,目前JavaScript在跨平臺開發中,可謂佔據半壁江山,大有“一統天下”的趨勢。

  • kotlin-native開始支援 iOS 和 Web 開發,(kotlin已經成為android的一級語言)也想嘗試“一統天下”。

  • flutter是Google跨平臺移動UI框架,Dart作為谷歌的親兒子,毫無疑問Dart成為flutter的程式語言,如下圖,作為巨頭新生兒,在flutter官網也可以看出,flutter同樣“心懷天下”。

flutter 官網野心勃勃

1、React Native

Facebook 出品,JavaScript語言,JSCore引擎,React設計模式,原生渲染

1.1、理念架構

“Learn once, write anywhere” ,代表著 Facebook對 react native 的定義:學習 react ,同時掌握 webapp 兩種開發技能。 react native 用了 react 的設計模式,但UI渲染、動畫效果、網路請求等均由原生端實現。開發者編寫的js程式碼,通過 react native 的中間層轉化為原生控制元件和操作,比ionic等跨平臺應用,大大提高了的使用者體驗。

總結起來其實就是利用 JS 來呼叫 Native 端的元件,從而實現相應的功能。

 如下圖所示,react native 的跨平臺是實現主要由三層構成,其中 C++ 實現的動態連結庫(.so),作為中間適配層橋接,實現了js端與原生端的雙向通訊互動。這裡最主要是封裝了 JavaScriptCore 執行js的解析,而 react native 執行在JavaScriptCore中,所以不存在瀏覽器相容的問題。

 其中在IOS上直接使用內建的javascriptcore, 在Android 則使用webkit.org官方開源的jsc.so

圖片來源網路

1.2、實現原理

 和前端開發不同,react native 所有的標籤都不是真實控制元件,JS程式碼中所寫控制元件的作用,類似 Map 中的 key 值。JS端通過這個 key 組合的 Dom ,最後Native端會解析這個 Dom ,得到對應的Native控制元件渲染,如 Android 中<view> 標籤對應 ViewGroup 控制元件。

圖片來源網路

 在 react native 中,JS端是執行在獨立的執行緒中(稱為JS Thread )。JS Thread 作為單執行緒邏輯,不可能處理耗時的操作。那麼如 fetch圖片載入資料持久化 等操作,在 Android 中實際對應的是 okhttpFrescoSharedPreferences等。而跨執行緒通訊,也意味著 Js Thread 和原生之間互動與通訊是非同步的。

 可以看出,跨平臺的關鍵在於C++層,開發人員大部分時候,只專注於JS 端的程式碼實現。 在原生端提供的各種 Native Module 模組(如網路請求,ViewGroup控制元件),和 JS 端提供的各種 JS Module(如JS EventEmiter模組),都會在C++實現的so中儲存起來,雙方的通訊通過C++中的儲存的對映,最終實現兩端的互動。通訊的資料和指令,在中間層會被轉為String字串傳輸,雙向的呼叫流程如下圖。

圖片來源網路,js呼叫native

圖片來源網路,native呼叫js

1.3、打包載入

 最終,JS程式碼會被打包成一個 bundle 檔案,自動新增到 App 的資源目錄下。react native 的打包指令碼目錄為/node_modules/react-native/local-cli,打包最後會通過 metro 模組壓縮 bundle 檔案。而bundle檔案只會打包js程式碼,自然不會包含圖片等靜態資源,所以打包後的靜態資源,其實是被拷貝到對應的平臺資原始檔夾中。

 其中圖片等存在資源的對映規則,比如放在 react native 專案根目錄下的 img/pic/logo.png 的資源,編譯時,會被重新命名後,根據大小 merged 到對應的是drawable目錄下,修改名稱為img_pic_logo.png

 打包Android和IOS,肯定需要相應的平臺專案存在,在 react-native init 時建立的專案,就已經包含了 android 和 ios 的模版工程,打包完的工程會載入bundle檔案,然後啟動專案,如下圖。這裡就不展(tou)開(lan)了,有興趣的可以看:React Native For Android 架構初探

圖片來源QQ空間移動開發團隊,完成啟動流程圖

   

2、WEEX

Alibaba 出品,JavaScript語言,JS V8引擎,Vue設計模式,原生渲染

2.1、理念架構

“Write once, run everywhere”, weex的定義就像是:寫個 vue 前端,順便幫你編譯成效能還不錯的 apk 和 ipa(當然,現實有時很骨感)。基於 Vue 設計模式,支援 web、android、ios 三端,原生端同樣通過中間層轉化,將控制元件和操作轉化為原生邏輯來提高使用者體驗。

 在 weex 中,主要包括三大部分:JS BridgeRenderDom,分別對應WXBridgeManagerWXRenderManagerWXDomManager,三部分通過WXSDKManager統一管理。其中 JS BridgeDom 都執行在獨立的 HandlerThread 中,而 Render 執行在 UI 執行緒。

JS Bridge 主要用來和 JS 端實現進行雙向通訊,比如把 JS 端的 dom 結構傳遞給 Dom 執行緒。Dom 主要是用於負責 dom 的解析、對映、新增等等的操作,最後通知UI執行緒更新。而 Render 負責在UI執行緒中對 dom 實現渲染。

圖片來自網路

2.2、實現原理

 和 react native一樣,weex 所有的標籤也不是真實控制元件,JS 程式碼中所生成存的 dom,最後都是由 Native 端解析,再得到對應的Native控制元件渲染,如 Android 中 <text> 標籤對應 WXTextView 控制元件。

 weex 中檔案預設為 .vue ,而 vue 檔案是被無法直接執行的,所以 vue 會被編譯成 .js 格式的檔案,Weex SDK會負責載入渲染這個js檔案。Weex可以做到跨三端的原理在於:在開發過程中,程式碼模式、編譯過程、模板元件、資料繫結、生命週期等上層語法是一致的。不同的是在 JS Framework 層的最後,web 平臺和 Native 平臺,對 Virtual DOM 執行的解析方法是有區別的。

移動端跨平臺開發的深度解析

 實際上,在 Native 中對 bundle 檔案的載入大致經歷以下階段:

  • weex 接收到 js 檔案以後,JS Framework 根據檔案為 Vue 模式,會呼叫weex-vue-framework 中提供的 createInstance方法建立例項。(也可能是Rax模式)

  • createInstance 中會執行 Js Entry 程式碼裡 new Vue() 建立一個元件,通過其 render 函式建立出 Virtual DOM 節點。

  • 由JS V8 引擎上解析 Virtual DOM ,得到 Json 資料傳送至 Dom 線,這裡輸出 Json 也是方便跨端的資料傳輸。

  • Dom 執行緒解析 Json 資料,得到對應的 WxDomObject,然後建立對應的WxComponent 提交 Render 。

  • Render在原生端最終處理處理渲染任務,並回撥裡JS方法。

 得益於上層的統一性,只是通過 weex-vue-framework 判斷是由Vue.js 生成真實的 Dom ,還是通過 Native Api 渲染元件,weex 一定程度上上,用JS 實現了 vue 一統天下的效果。

圖片來源網路

  weex 在原生渲染 Render 時,在接收到渲染指令後,會逐步將資料渲染成原生元件。Render 通過解析渲染資料的描述,然後分發給不同的模組。

  比如 控制元件渲染屬於 dom 模組中,頁面跳轉屬於navigator模組等。模組的渲染過程並非一個執行完,再執行另一個的流程,而是類似流式的過程。如上一個 <text> 的元件還沒渲染好,下一個 <div> 的渲染又發了過來。這樣當一個元件的巢狀元件很多時,或者可以看到這個大元件內的UI,一個一個渲染出來的過程。

 weex 比起react native,主要是在JS V8的引擎上,多了 JS Framework 承當了重要的職責,使得上層具備統一性,可以支援跨三個平臺。總的來說它主要負責是:管理Weex的生命週期解析JS Bundle,轉為Virtual DOM,再通過所在平臺不同的API方法,構建頁面進行雙向的資料互動和響應

移動端跨平臺開發的深度解析

2.3、打包

 weex 作為 react-native 之後出現的跨平臺實現方案,自然可以站在前人的肩膀上優化問題,比如:Bundle檔案過大問題

 Bundle檔案的大小,很大程度上影響了框架的效能,而 weex 選擇將 JS Framework 整合到 WeexSDK 中,一定程度減少了JS Bundle的體積,使得 bundle 裡面只保留業務程式碼。

 打包時,weex 是通過 webpack 打包出 bundle 檔案的。bundle 檔案的打包和 entry.js 檔案的配置數量有關,預設情況下之後一個 entry 檔案,自然也就只有一個bundle檔案。

 在 weex 專案的 webpack.common.conf.js 中可以看到,其實打包也是區分了 webConfigweexConfig 的不同打包方式。如下圖,其中weexEntry 就是 weex 打包配置的地方,可以看到本來已經有 indexentry.js 存在了。如果有需要,可配置上你需要的打包頁面,具體這裡就不詳細展開了。有興趣可看:Weex原理之帶你去蹲坑

移動端跨平臺開發的深度解析

   

3、Flutter

Google 出品,Dart語言,Flutter Engine引擎,響應式設計模式,原生渲染

 Flutter 是谷歌2018年釋出的跨平臺移動UI框架。相較於本人已經在專案中使用過 react native 和 Weex,Flutter目前僅僅是簡單執行過Demo,畢竟還是beta 階段,這裡更多的聊一下它的實現機制和效果。

 與 react native 和 weex 的通過 Javascript 開發不同,Flutter 的程式語言是Drat,(谷歌親兒子,據說是因為 Drat 專案組就在 Flutter 隔壁而被選上(◐‿◑))所以執行時並不需要 Javascript 引擎,但實際效果最終也通過原生渲染。

圖片來源網路

 如上圖,Flutter 主要分為 FrameworkEngine,我們基於Framework 開發App,執行在 Engine 上。Engine 是 Flutter 的獨立虛擬機器,由它適配和提供跨平臺支援,目前猜測 Flutter 應用程式在 Android 上,是直接執行 Engine 上 所以在是不需要Dalvik虛擬機器。(這是比kotlin更徹底,拋棄JVM的糾纏?)

 如下圖,得益於 Engine 層,Flutter 甚至不使用移動平臺的原生控制元件, 而是使用自己 Engine 來繪製 Widget (Flutter的顯示單元),而 Dart 程式碼都是通過 AOT 編譯為平臺的原生程式碼,所以 Flutter 可以 直接與平臺通訊,不需要JS引擎的橋接。同時 Flutter 唯一要求系統提供的是 canvas,以實現UI的繪製。咦?這麼想來,支援web端也沒問題吧!

圖片來源網路

 在Flutter中,大多數東西都是widget,而Widget是不可變的,僅支援一幀,並且在每一幀上不會直接更新,要更新而必須使用Widget的狀態。無狀態和有狀態 widget 的核心特性是相同的,每一幀它們都會重新構建,有一個State物件,它可以跨幀儲存狀態資料並恢復它。

 Flutter 上 Android 自帶了 Skia,Skia是一個 2D的繪圖引擎庫,跨平臺,所以可以被嵌入到 Flutter的 iOS SDK中,也使得 Flutter Android SDK要比 iOS SDK小很多。

三、對比

 這算是互相傷害的環節了吧。(///▽///)

型別 React Native Weex Flutter
平臺實現 JavaScript JavaScript 無橋接,原生編碼
引擎 JSCore JS V8 Flutter engine
核心語言 React Vue Dart
Apk大小 (Release) 7.6M 10.6M 8.1M
bundle檔案大小 預設單一、較大 較小、多頁面可多檔案 不需要
上手難度 稍高? 容易 一般
框架程度 較重 較輕
特點(不侷限) 適合開發整體App 適合單頁面 適合開發整體App
社群 豐富,Facebook重點維護 有點殘念,託管apache 剛剛出道小鮮肉,擁護者眾多
支援 Android、IOS Android、IOS、Web Android、IOS(並不止?)

1、大小

 上面Apk大小是通過 react-native initweex create 和 flutter 建立出的工程後,直接不新增任何程式碼,打包出來的 release 簽名 apk 大小。從下圖可以看出,其中大比例都是在so庫。

移動端跨平臺開發的深度解析

2、社群

 react native 作為 Facebook 主力開源專案之一,至今已有各類豐富的第三方庫,甚至如 realmlottie 等開源專案也有 react native 相關的版本,社群實際無需質疑。當然,因為並完全正統開發平臺,第三庫的健壯性和相容性有時候總是良莠不齊。

 weex 其實有種生錯在國內的感覺。其實 weex 的設計和理念都很優秀,效能也不錯,但是對比 react native 的第三方支援,就顯得有點後媽養的。2016年開源至今,社群和各類文件都顯得有點疲弱,作為跨平臺開發人員,大多時候肯定不會希望,需要頻繁的自己增加原生功能支援,因為這樣的工作一多,反而會與跨平臺開發的理念背道而馳,帶來開發成本被維護難度增加。

 Flutter目前還處理beta階段,但是谷歌的號召力一直很可觀,這一點無需質疑。

3、效能

 理論上 flutter 的效能應該是最好的,但是目前實際體驗中,卻並沒有感受出來太大的差距,和 react native(0.5.0之後)、weex 在效能上個人體驗差異不是很大。當然,這裡並沒有實測渲染的毫秒時間和幀率資料。

4、其他區別

  • Weex的多頁面實現問題

 weex 在 native 端是不支援 <keep-alive> 的,這一點和 react-native 不同在與,如果在 native 需要實現頁面跳轉,使用 vue-router 將會慘不忍睹:返回後頁面不做特別處理時,是會空白一片。參考官方Demo playground,native 端 的採用 weex.requireModule('navigator') 跳轉 Activity 是才正確實現。

 同時,weex中 navigator 跳轉的設計,也導致了多頁面的頁面間通訊的差異。weex在多頁面下的資料通訊,是通過url實現的,比如file://assets/dist/SecondPage.js?params=0,而vuex和vue-router在跨頁面是無法共用的;而 react native 在跨 Actvity 使用時,因為是同一個bundle檔案,只要 manager 相同,那麼 router 和 store 時可以照樣使用的,資料通訊方式也和當個 Actvity 沒區別。

  • 專案模板

  weex 和 react native 模板程式碼模式也不同。weex 的模板是從 cordova 模式修改過來的,根據platform需求,用命令新增固定模組,而在 .gitignore 對 platforms 資料夾是忽略跟蹤。 react native 在專案建立時模版就存在了,特別是新增第三方外掛原生端支援時,會直接修改模板程式碼,git程式碼中也會新增跟蹤修改。

四、未來趨勢

  我們選擇框架的時候,很多時候會關注框架的成熟度和生命力不是麼(◐‿◑)。

1、React Native

“Airbnb 宣佈放棄使用 React Native,迴歸使用原生技術” : Airbnb 作為 react native 平臺上最大的支持者之一,其開源的lottie 同樣是支援原生和 react native。

 Airbnb 在宣佈放棄的文中,也對 react native 的表示了很大量的肯定,而使得他們放棄的理由,其實主要還是集中於專案龐大之後的維護困難,第三方庫的良莠不齊,相容上需要耗費更多的精力導致放棄。

ps:( Lottie庫Airbnb出的是一個能夠幫助解析AE匯出的包含動畫資訊的json檔案。這很好的解決了一個矛盾,設計師可以更專注的設計出各種炫酷的動畫效果,而開發只需要將其加入支援即可。)

Facebook 正在重構 React Native,將重寫大量底層。在經歷了開源協議風波後,可以看出 Facebook 對於 react native 還是很看重的, 這些底層重構優化的地方,主要集中於:

 首先,改變執行緒模型。UI 更新不再需要在三個不同的執行緒上執行,而是可以在任意執行緒上同步呼叫 JavaScript 進行優先更新,同時將低優先順序工作推出主執行緒,以便保持對 UI 的響應。  其次,將非同步渲染功能引入 React Native 中,允許執行多個渲染並簡化非同步資料處理。  最後,簡化橋接,讓它更快、更輕量。原生和 JavaScript 之間的直接呼叫效率更高,並且可以更輕鬆地構建除錯工具,如跨語言堆疊跟蹤。

2、Weex

沒有死!阿里公開Weex技術架構,還開源了一大波元件。 2018年初的新聞可以看出,weex 的遭遇有點類似曾經的 Duddo(Dubbo因為內部競爭被阿里一度放棄維護),這波詐屍後 weex 被託管到了Apache,而github的 weexteam 如今也還保持著更新,希望後續能有多好的發展,拭目以待吧。

3、Flutter

 Flutter 是 Google 跨平臺移動UI框架,Dart作為谷歌的親兒子在 Flutter 中使用,並且谷歌新作業系統 Fuchsia 支援 Dart,使用 Flutter 作為操作UI框架。這些集合到一起難免讓你懷疑 Android 是否要被谷歌拋棄的想法。

 或者如今先 Android 等平臺上推廣 Flutter 與 Dart,就是為了以後跟好的過渡到新系統上,畢竟開發者是作業系統的生命源泉之一。而 Java 與 JVM 或者可以被谷歌完全拋開。當然,目前看起來 Flutter 貌似還缺少一些語法糖,巢狀下來的程式碼有點不忍直視,或者到正式版之後,我們更能感受出它的美麗吧。

最後

 內容有點長,其實很多點並沒有細緻的展開說明,但是通過本文,對於移動端跨平臺的現狀與未來,希望可能給你帶來一點幫助。

超完整跨平臺開源專案

型別 連結
react-native github.com/CarGuo/GSYG…
weex github.com/CarGuo/GSYG…
Flutter github.com/CarGuo/GSYG…

其他React Native相關文章:

其他Weex相關文章:

擼文真的好累

相關文章