移動跨平臺開發深度解析

xiangzhihong發表於2018-08-10

注:本文為轉載文章,部分內容參考移動端跨平臺開發的深度解析,並做了精簡和加工。

概述

移動跨平臺開發一直是移動開發者和前端開發者追求的的話題,從早期的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庫。

移動跨平臺開發深度解析

附:
React Native中文網
Weex官方文件
Flutter中文社群

相關文章