注:本文為轉載文章,部分內容參考移動端跨平臺開發的深度解析,並做了精簡和加工。
概述
移動跨平臺開發一直是移動開發者和前端開發者追求的的話題,從早期的cordova、ionic,到如今的react native、weex、kotlin native和flutter等,可以說如今的跨平臺框架可謂百花齊放,頗有一股推倒原生開發者的勢頭。
如果要對目前的跨平臺的方案進行一個總結,大致可以分為以下幾個流派:
JavaScript流派:這一流派中,最明顯的特徵是使用JavaScript作為程式語言,react native、weex均屬於這一流派。和其他跨平臺方案相比,JavaScript在跨平臺開發中,使用者最多,大有“一統天下”的趨勢。
VM虛擬機器:與其他方案不同,kotlin提供的kotlin-native技術擁有自己的VM,可以同時支援Android、iOS 和 Web 開發。
Flutter:Futter是Google開源的移動跨平臺UI框架,使用的是Google自己的Dart程式語言,由於是Google推出的產品,因而也受到很多開發者的喜愛。
不過,綜合對比開發現,目前最火的跨平臺開發方案,特別是已經商用的跨平臺開發框架中,react native無疑是老大,其次是Weex(畢竟是阿里的產品),最後可能是最近才火起來的Flutter。
React Native
曾經,React Native的口號是“Learn once, write anywhere”,這句話代表了FaceBook對React Native設計的初衷:學習 react ,同時掌握 web 與 app 兩種開發技能。
藉助FaceBook旗下的React的設計模式 , React Native使用的UI渲染、動畫效果、網路請求等會轉換成原生端的實現。也就是說,開發者編寫的js程式碼,通過 react native 的中間層(JavaScriptCore)轉化為原生控制元件和操作,這就最大程度的接近原生應用的使用者體驗,並提高了開發的效率。
React Native的結構
React Native的跨平臺是實現主要由三層構成,其中 C++ 實現的動態連結庫(.so),作為中間適配層橋接,實現了js端與原生端的雙向通訊互動。
這裡最主要是封裝了 JavaScriptCore 執行js的解析,而 react native 執行在JavaScriptCore中,所以不存在瀏覽器相容的問題。其結構如如下圖:
原理
React Native實現的原理其實就是利用JS 呼叫Native 端的元件,並使用Native的元件來繪製介面,從而達到媲美原生應用的效果。
和前端開發不同,React Native 所使用的標籤並不是真實的控制元件,React Native提供的元件會Dom 轉換為Native的控制元件進行渲染。例如<Text>
標籤會被轉換為Android 中對應 TextView 控制元件。
需要說明的是,在React Native 中,JS端是執行在獨立的執行緒中(稱為JS Thread ),JS Thread 作為單執行緒邏輯,不可能處理耗時的操作。那麼如 fetch 、圖片載入 、 資料持久化等操作,在 Android 中實際對應的是 okhttp 、Fresco 、SharedPreferences等。而跨執行緒通訊,也意味著 Js Thread 和原生之間互動與通訊是非同步的。
由此可以看出,跨平臺的關鍵在於C++層,開發人員大部分時候,只專注於JS 端的程式碼實現即可,無線瞭解底層的實現細節。 而如果要實現和原生模組的互動,只需要在原生端提供的各種 Native Module 模組(如網路請求,ViewGroup控制元件)即可,然後通過 JS 端提供的各種 JS Module(如JS EventEmiter模組)來實現呼叫。並且這些呼叫都會在C++實現的so中儲存起來,雙方的通訊通過C++中的儲存的對映,最終實現兩端的互動,通訊的資料和指令,在中間層會被轉為String字串傳輸,雙向的呼叫流程如下圖。
打包與釋出
在React Native混合專案中,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。
Weex
Weex是阿里巴巴開源的一套移動跨平臺開發框架,能夠完美兼顧效能與動態性,讓移動開發者通過簡捷的前端語法寫出Native級別的效能體驗,並支援iOS、安卓、YunOS及Web等多端部署。目前,使用Weex框架的多半是阿里系的產品和一些創業的公司。
Weex架構
Weex的口號是“Write once, run everywhere”,Weex使用的耳熟能詳的Vue,阿里的思維是:寫個 vue 前端,順便完成一個apk 和 ipa,但其實還是有差距的。Weex支援 web、android、ios 三端,原生端同樣通過中間層轉化,將控制元件和操作轉化為原生邏輯來提高使用者體驗。。
在 Weex的架構層次中,主要包括三大部分:JS Bridge、Render、Dom,分別對應WXBridgeManager、WXRenderManager、WXDomManager,三部分通過WXSDKManager統一管理。其中 JS Bridge 和 Dom 都執行在獨立的 HandlerThread 中,而 Render 執行在 UI 執行緒。關於Weex架構分層的內容讀者可以自行檢視官方資料的介紹。
其中,JS Bridge 主要用來和 JS 端實現進行雙向通訊,比如把 JS 端的 dom 結構傳遞給 Dom 執行緒。Dom 主要是用於負責 dom 的解析、對映、新增等等的操作,最後通知UI執行緒更新。而 Render 負責在UI執行緒中對 dom 實現渲染。
實現原理
和 React Native一樣,Weex 所有的標籤也不是真實控制元件,Weex的標籤只不過是JS 程式碼中所生成存的 dom,最後都是由 Native 端解析,再得到對應的Native控制元件渲染。如 Android 中 <text>
標籤對應 WXTextView 控制元件。
Weex 中檔案預設為 .vue ,而 vue 檔案是被無法直接執行的,所以 vue 會被編譯成 .js 格式的檔案,Weex SDK會負責載入渲染這個js檔案。Weex可以做到跨三端的原理在於:在開發過程中,程式碼模式、編譯過程、模板元件、資料繫結、生命週期等上層語法是一致的。
不同的是,在 JS Framework 層的最後,web 平臺和 Native 平臺,對 Virtual DOM 執行的解析方法是有區別的,在渲染真實 UI 的時候呼叫的介面也不同的。
Weex 表面上是一個客戶端技術,但實際上它串聯起了從本地開發、雲端部署到分發的整個鏈路。開發者首先可在本地像編寫 web 頁面一樣編寫一個 app 的介面,然後通過命令列工具將之編譯成一段 JavaScript 程式碼,生成一個 Weex 的 JS bundle;同時,開發者可以將生成的 JS bundle 部署至雲端,然後通過網路請求或預下發的方式載入至使用者的移動應用客戶端;在移動應用客戶端裡,Weex SDK 會準備好一個 JavaScript 執行環境,並且在使用者開啟一個 Weex 頁面時在這個執行環境中執行相應的 JS bundle,並將執行過程中產生的各種命令傳送到 native 端進行介面渲染、資料儲存、網路通訊、呼叫裝置功能及使用者互動響應等功能;同時,如果使用者希望使用瀏覽器訪問這個介面,那麼他可以在瀏覽器裡開啟一個相同的 web 頁面,這個頁面和移動應用使用相同的頁面原始碼,但被編譯成適合Web展示的JS Bundle,通過瀏覽器裡的 JavaScript 引擎及 Weex SDK 執行起來的。
其中, Native 載入bundle 檔案大致經歷了以下階段:
- weex 接收到 js 檔案以後,JS Framework 根據檔案為 Vue 模式,會呼叫weex-vue-framework 中提供的createInstance方法建立例項。
- createInstance 中會執行 Js Entry 程式碼裡 new Vue() 建立一個元件,通過其 render 函式建立出 Virtual DOM 節點。
- 由JS V8 引擎上解析 Virtual DOM ,得到 Json 資料傳送至 Dom 線,這裡輸出 Json 也是方便跨端的資料傳輸。
- Dom 執行緒解析 Json 資料,得到對應的 WxDomObject,然後建立對應的WxComponent 提交 Render 。
- Render在原生端最終處理處理渲染任務,並回撥裡JS方法。
相比React Native,Weex主要是在JS V8的引擎上多了 JS Framework 承當了重要的職責,使得上層具備統一性,可以支援跨三個平臺。總的來說它主要負責是:管理Weex的生命週期,解析JS Bundle,轉為Virtual DOM,再通過所在平臺不同的API方法,構建頁面;進行雙向的資料互動和響應。
打包與釋出
在打包方案上,Weex和React Native都通過 Webpack 來打包bundle 檔案的。不過,React Native打包如果不做拆分,打出的包是很大的,因而會自己制定一些拆包的規則。而Weex 作為React Native之後出現的跨平臺實現方案,自然可以站在前人的肩膀上優化問題,比如:Bundle檔案過大問題。 Weex 選擇使用JS Framework 整合到 WeexSDK的方式,一定程度減少了JS Bundle的體積,使得 bundle 裡面只保留業務程式碼。
打包時,weex 是通過 webpack 打包出 bundle 檔案的。bundle 檔案的打包和 entry.js 檔案的配置數量有關,預設情況下之後一個 entry 檔案,自然也就只有一個bundle檔案。
在 weex 專案的 webpack.common.conf.js 中可以看到,其實打包也是區分了 webConfig 和 weexConfig 的不同打包方式。
Flutter
Flutter是Google用以幫助開發者在Ios和Android兩個平臺開發高質量原生應用的全新移動UI框架。與 React Native 和 Weex 框架使用的Javascript 技術不同,Flutter 使用的是全新的程式語言Drat,所以執行時並不需要 Javascript 引擎,但實際效果最終也通過原生渲染。
Flutter框架
Flutter框架主要分為 Framework 和 Engine兩層,我們基於Framework 開發App主要執行在 Engine 上。Engine 是 Flutter 的獨立虛擬機器,由它適配和提供跨平臺支援,目前猜測 Flutter 應用程式在 Android 上,是直接執行 Engine 上 所以在是不需要Dalvik虛擬機器。其架構圖如下圖所示:
得益於 Engine 層,Flutter 甚至不使用移動平臺的原生控制元件, 而是使用自己 Engine 來繪製 Widget (Flutter的顯示單元),而 Dart 程式碼都是通過 AOT 編譯為平臺的原生程式碼,所以 Flutter 可以 直接與平臺通訊,不需要JS引擎的橋接。
而Flutter唯一要求系統提供的是canvas,用以實現UI的繪製。不過,Flutter 上 Android 自帶了 Skia,Skia是一個 2D的繪圖引擎庫,跨平臺,所以可以被嵌入到 Flutter的 iOS SDK中,也使得 Flutter Android SDK要比 iOS SDK小很多。
對比
下面對上面介紹的幾大框架進行一個簡單的對比,以方便讀者進行對比學習。
對比型別 | React Native | Weex | Flutter |
---|---|---|---|
實現技術 | JavaScript | JavaScript | 原生編碼,無橋接 |
引擎 | JS V8 | JSCore | Flutter engine |
使用語言 | React | Vue | Dart |
bundle檔案大小 | 預設單一、較大 | 較小、多頁面可多檔案 | 不需要 |
上手難度 | 稍高大 | 容易 | 簡單 |
框架難度 | 較重 | 較輕 | 重 |
支援物件 | Android、IOS | Android、IOS、Web | Android、IOS |
包大小對比
上面Apk大小是通過 react-native init、weex create 和 flutter 建立出的工程後,直接不新增任何程式碼,打包出來的 release 簽名 apk 大小。從下圖可以看出,其中大比例都是在so庫。