http://blog.csdn.net/xiangzhihong8/article/details/52623852javascript
版權聲明:本文爲博主原創文章,未經博主容許不得轉載。html
Facebook 於2015年9月15日推出react native for Android 版本, 加上2014年末已經開源的iOS版本,至此RN (react-native)真正成爲跨平臺的客戶端框架。本篇主要是從分析代碼入手,探討一下RN在安卓平臺上是如何構建一套JS的運行框架。react
RN 這套框架讓 JS開發者能夠大部分使用JS代碼就能夠構建一個跨平臺APP。 Facebook官方說法是learn once, run everywhere, 即在Android 、 IOS、 Browser各個平臺,程序畫UI和寫邏輯的方式都大體相同。由於JS 能夠動態加載,從而理論上能夠作到write once, run everywhere, 固然要作額外的適配處理。如圖:android
RN須要一個JS的運行環境, 在IOS上直接使用內置的javascriptcore, 在Android 則使用webkit.org官方開源的jsc.so。 此外還集成了其餘開源組件,如fresco圖片組件,okhttp網絡組件等。c++
RN 會把應用的JS代碼(包括依賴的framework)編譯成一個js文件(通常命名爲index.android.bundle), , RN的總體框架目標就是爲了解釋運行這個js 腳本文件,若是是js 擴展的API, 則直接經過bridge調用native方法; 若是是UI界面, 則映射到virtual DOM這個虛擬的JS數據結構中,經過bridge 傳遞到native , 而後根據數據屬性設置各個對應的真實native的View。 bridge是一種JS 和 Java代碼通訊的機制, 用bridge函數傳入對方module 和 method便可獲得異步回調的結果。git
對 於JS開發者來講, 畫UI只須要畫到virtual DOM 中,不須要特別關心具體的平臺, 仍是原來的單線程開發,仍是原來HTML 組裝UI(JSX),仍是原來的樣式模型(部分兼容 )。RN的界面處理除了實現View 增刪改查的接口以外,還自定義一套樣式表達CSSLayout,這套CSSLayout也是跨平臺實現。 RN 擁有畫UI的跨平臺能力,主要是加入Virtual DOM編程模型,該方法一方面能夠照顧到JS開發者在html DOM的部分傳承, 讓JS 開發者能夠用相似DOM編程模型就能夠開發原生APP , 另外一方面則可讓Virtual DOM適配實現到各個平臺,實現跨平臺的能力,而且爲將來增長更多的想象空間, 好比react-cavas, react-openGL。而實際上react-native也是從react-js演變而來。github
對 於 Android 開發者來講, RN是一個普通的安卓程序加上一堆事件響應, 事件來源主要是JS的命令。主要有二個線程,UI main thread, JS thread。 UI thread建立一個APP的事件循環後,就掛在looper等待事件 , 事件驅動各自的對象執行命令。 JS thread 運行的腳本至關於底層數據採集器, 不斷上傳數據,轉化成UI 事件, 經過bridge轉發到UI thread, 從而改變真實的View。 後面再深一層發現, UI main thread 跟 JS thread更像是CS 模型,JS thread更像服務端, UI main thread是客戶端, UI main thread 不斷詢問JS thread而且請求數據,若是數據有變,則更新UI界面。web
對於JS開發者來講, 整個RN APP就只有一個JS文件, 而開發者須要編寫的就只有如上部分。主要是四個部分:算法
require 全部依賴到的組件, 至關於java中的import 或者 c++ 中的include。
var AwesomeProject = React.createClass 建立APP, 而且在render函數中返回UI界面結構(採用JSX ), 實際通過編譯, 都會變成JS 代碼, 好比 變成 React.createElement(View,{style:{flex:1}},
var styles = StyleSheet.create({, 建立CSS 樣式,實際上會直接當作參數直接反饋到上面的React.createElement
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject); 以上三個更像是參數,這個纔是JS 程序的入口。即把當前APP的對象註冊到AppRegistry組件中, AppRegistry組件是js module。
接着就等待Native事件驅動渲染JS端定義的APP組件。
對於Android 開發者, 普通安卓程序入口是Activity.onCreate()方法 , 主要有三個對象
ReactRootView, Android 標準的FrameLayout對象,另一個功能是提供react 世界的入口,函數startReactApplication實際調用attachMeasuredRootView觸發react世界的初始化。
MyReactPackage, 配置當前APP 須要加載的模塊,RN 的JS框架會在初始化階段就會把native的模塊按照配置加載到JS數據結構中(MessageQueue), 從而才能在JS 層便可直接判斷native是否支持某個模塊。支持三種類型模塊配置, native module(實際就是不須要操做View結構的API), view managers(實際是映射到virtual DOM中的View組件), JS module 。
ReactInstanceManager, 構建React世界的運行環境,發送事件到JS世界, 驅動整個React世界運轉。 經過builder能夠建立不一樣的React環境, 好比內置js 路徑, 開發環境dev的js名字,是否支持調試等。doInBackground會加載指定的JS文件, onPostExecute會調用runApplication接口運行JS APP。
ReactRootView 第一次onMeasured計算完成, 而後會利用ReactInstanceManager建立 ReactContext上下文環境。重要的是初始化bridge以及加載js文件, 利用JSBundleLoader方法加載index.android.bundle. 如圖
此刻進入JS 世界, 開發者的js 語句連同react js框架層被執行。該步驟最終語句是執行AppRegistry.registerComponent註冊一個APP組件,但尚未到開始渲染。
當運行環境準備完畢, 則調用bridge方法運行上步註冊的APP組件,觸發一連串JS 和 Native相互通訊,配合事件驅動, 從而完成native世界的渲染。如圖利用bridge方法運行上面註冊的JS APP組件的runApplication方法:
全部的APP在操做系統中, 最終都會使用一個事件循環來運行。
通常來講,JS 開發者只須要開發各個組件對象,監聽組件事件, 而後利用framework接口調用render方法渲染組件。
而 實際上,JS 也是單線程事件循環,不論是 API調用, virtural DOM同步, 仍是系統事件監聽, 都是異步事件,採用Observer(觀察者)模式監聽JAVA層事件, JAVA層會把JS 關心的事件經過bridge直接使用javascriptCore的接口執行固定的腳本, 好比"requrire (test_module).test_methode(test_args)"。此時,UI main thread至關於work thread, 把系統事件或者用戶事件往JS層拋,同時,JS 層也不斷調用模塊API或者UI組件 , 驅動JAVA層完成實際的View渲染。JS開發者只須要監聽JS層framework定義的事件便可。如圖即JS thread 的消息隊列循環:
分 析代碼可知,消息線程建立於ReactContext環境初始化時, MessageQueueThread.java當中, 該消息隊列主要接收系統事件(如 Vsync、timer、doFrame、backkey)、UI事件(如鍵盤彈起、滾動等)以及 callback事件(JS 的回調函數)。
如圖即ReactRootView往JS 傳遞鍵盤彈出的事件:
而 對於Android 開發者, Android 已經爲APP建立一個默認的 Main Looper, 不論是Android System 仍是JS 事件都是發送到Main thread經過UI渲染出來。如圖便是MessageQueueThread.java直接使用主線程Looper。
跟普通APP不一樣是,此時JS thread至關於work thread, JS會把對應的事件或者數據經過bridge發送到UI thread。 如圖便是native Java層收到的JS事件的處理函數:
RN 框架最主要的就是實現了一套JAVA和 JS通訊的方案,該方案能夠作到比較簡便的互調對方的接口。通常的JS運行環境是直接擴展JS接口,而後JS經過擴展接口發送信息到主線程。但RN的通訊 的實現機制是單向調用,Native線程按期向JS線程拉取數據, 而後轉成JS的調用預期,最後轉交給Native對應的調用模塊。這樣最終一樣也能夠達到Java和 JS 定義的Module互相調用的目的。
JS 調用java 使用經過擴展模塊require('NativeModules')獲取native模塊,而後直接調用native公開的方法,好比 require('NativeModules').UIManager.manageChildren()。 JS 調用require('NativeModules')其實是獲取MessageQueue裏面的一個native模塊列表的屬性, 如:
使用_genModules 加載全部native module到 RemoteModules數組。RemoteModules每項都是一個映射到native module的JS對象。
調用RemoteModules 的方法, 實際是把moduleID、methodId、args放入三個queue保存。
至此, JS端調用完畢, queue中數據要等待Native層經過bridge來取。
native層會在必定條件下觸發事件, 經過bridge調用callFunctionReturnFlushedQueue
和 invokeCallbackAndReturnFlushedQueue ,獲得的返回值就是這三個queue。
bridge會把這三個queue交給parseMethodCalls解析, 而後經過JNI回調函數轉發到Java層
m_callback 函數是在bridge初始化的時候設置到c++層, 如:
而後在回調函數中,陸續調用ReactCallback對象的call方法,weakCallback就是java層初始化bridge時傳入的NativeModulesReactCallback對象,也就是ReactCallback的子類。
到此,轉入Java層. 從native module配置表中,取到對應module和method,並執行。
之 前ReactInstanceManager 中運行JS APP組件,JAVA 是調用catalystInstance.getJSModule 方法獲取JS 對象,而後直接訪問對象方法runApplication。實際上getJSModule 返回的是js對象在java層的映射對象。
java層能夠調用的JS模塊主要在CoreModulesPackage.createJSModules方法配置,有:
若是調用JSModules對象的方法,則會動態代理跳轉到(mBridge).callFunction(moduleId, methodId, arguments);
接着調用ReactBridge中聲明的JNI 函數,
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
經過JS 的require和 apply函數拼接一段JS 代碼, 而後用javascriptCore的腳本運行接口執行,並獲得返回值。
這 樣就在JS引擎中運行了一段JS代碼並獲得返回值,實現了JAVA層到JS層的調用。每次有JAVA對JS的訪問, 則在返回值中從JS層的messageQueue.js中抓取以前累積的一堆JS calls。由於JAVA層要把時間同步、 系統幀繪製等事件傳遞給JS, 所以queue中的JS calls都會在很短的時間內被抓取。
一、 模塊擴展(native module)
官方文檔操做:
https://facebook.github.io/react-native/docs/native-modules-android.html#content
二、 組件擴展(UI component)
官方文檔操做:
https://facebook.github.io/react-native/docs/native-components-android.html#content
由於react模塊加載主要在ReactPackage類配置,所以擴展能夠經過反射、外部依賴注入等機制,能夠作到跟H5容器同樣實現動態插拔的插件式擴展。好比API擴展, 經過外部傳入擴展模塊的類名便可反射構造函數建立新的API:
@Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList(); modules.addAll(Arrays.<NativeModule>asList( new AsyncStorageModule(reactContext), new FrescoModule(reactContext), new NetworkingModule(reactContext), new WebSocketModule(reactContext), new ToastModule(reactContext))); if (mModuleList != null && mModuleList.size() > 0) { for (int i = 0; i < mModuleList.size(); i++) { try { Log.i("MyReactPackage", "add Module:" + mModuleList.get(i)); Class c = Class.forName(mModuleList.get(i)); Class[] parameterTypes = {ReactApplicationContext.class}; java.lang.reflect.Constructor constructor = c.getConstructor(parameterTypes); Object[] parameters = {reactContext}; NativeModule module = (NativeModule) constructor.newInstance(parameters); modules.add(module); }catch (Exception e) { Log.i("MyReactPackage", "add Module Exeception:" + e); e.printStackTrace(); } } } return modules; }
離線包支持。 目前RN官方支持內置APK打包以及dev server在線更新。而實際上,通常的容器都會實現一套離線包發佈平臺。大體的實現方案是自定義一個JSBundleLoader,對接到應用管理髮布平臺。
分 離react 框架代碼和應用業務代碼。目前官方的生產工具是把框架代碼和業務代碼弄成一個bundle。 但框架代碼很大,須要共用, 所以要分離出框架代碼單獨前置加載。 應用業務代碼變成很小一段JS代碼單獨發佈。若是每次都加載框架代碼, 啓動業務代碼會比較慢,一個helloworld都須要4秒左右。初步實踐方案是把ReactInstanceManager設置成全局變量共享,在 Native APP 啓動初始化或者第一次進入RN APP時初始化ReactInstanceManager。這個可能會致使多個RN APP全局變量衝突。
在線更新
離線包更新主要依賴應用管理髮布平臺,大體能夠作到跟H5離線包一致。
經過source屬性設置圖片資源路徑, 映射到native層:
所以不論是離線包內資源仍是系統資源,只要能轉換成Android 統一資源定位URI對象,便可獲取到圖片。
若是是靜態資源,則直接URI統必定位。若是是動態資源, 好比要經過網關獲取到base64格式的圖片,則須要native擴展特別接口。
一、 可能瓶頸
* 由於bridge, JS和 JAVA是異步互通,若是實現複雜多API的邏輯,可能會致使部分效率損耗在多線程通訊。JS 異步的編程方式多多少少帶來一些不便。 * 由於bridge, 可能某些場景作不到及時響應。好比幀動畫的實時控制。 * Android版本剛推出不完善,而且目前RN版本還在不停的更新中, 可能存在暗坑。 * 加入JS引擎, 內存的控制比較麻煩,會比普通native增長很多。
二、 待研究