大家吼,(◐‿◑)作為失蹤人口迴歸,這次第二期,就讓我們來懟React Native的通訊,快速實現單獨的React Native模組到APP裡,愉悅吧騷年。至於為什麼要有這期?當然是為了愉悅的飆車啦ε-(´∀`; )。
下方包含原始碼劇透,劇情略長,請緊張耐心的往下看。( ̄^ ̄)ゞ
文中標註有“【數字】”的是乾貨喲。
開始之前
本文前上部分主要拆解一些基礎的原理,由淺到深;後半部分講解整合模組實現,你也可以直接閱讀後半部分,快速實現模組整合。文中著重在Android端幫助大家理解React Native。
下方先提前介紹一些關鍵類。
- ReactActivity:預設所有的Activity都繼承它。
- ReactNativeHost :幫你”hold”住ReactInstanceManager。
- ReactActivityDelegate:ReactActivity的邏輯代理實現。
- ReactRootView :React NativeUI的所在。
- ReactInstanceManager:React Native的扛把子,抽象類。
- XReactInstanceManagerImpl :ReactInstanceManager的實現類。
- ReactContext: 管理React Native的狀態等。
- NativeModule:繼承它的module可以在js端使用,其中就包括有DeviceEventManagerModule,與JS實現事件模式互動的module。
- Callback/Promise: 回撥介面,與js端互動。
一、上半部分
1、MainApplication
預設react-native init
建立的專案裡,會有一個MainActivity
和一個MainApplication
。MainApplication
繼承了ReactApplication
介面,介面只有一個方法:getReactNativeHost
。
1.1、ReactNativeHost
這個介面實現在Application,通過getApplication,你可以隨時拿到ReactNativeHost
,它會幫你建立一個單例:ReactInstanceManager
作為管理器。ReactNativeHost
還可以配置一系列的行為,其中最關鍵的,便是getPackages
介面。
getPackages
介面返回了一系列的ReactPackage類,ReactPackage可以看作是,向ReactNative註冊了原生模組,這樣在JS中你也可以使用原生模組的功能,按鍵第三方庫時,react-native link
命令,其中一個行為,就是在getPackages中幫你插入,庫需要引用到的模組。
如上圖,是MainReactPackage
內部實現,MainReactPackage
是官方的類,其中關聯了很多NativeModule
,Module中你可以通過@ReactMethod
註解,指定一個方法為JS可以呼叫的方法,如下圖的DetailModule
,便是繼承了NativeModule
的JAVA端實現類,在js中引入。
總結一下,劃重點Σ( ̄。 ̄ノ)ノ:
- MainApplication繼承了ReactApplication,返回了ReactNativeHost。
- ReactNativeHost裡建立了ReactInstanceManager,並且實現了getPackages,返回了ReactPackage列表。
- ReactInstanceManager在建立Builder時,把ReactPackage列表加入到管理器。
- ReactPackage列表裡面都關聯了NativeModule的實現類。
- NativeModule的實現類可以通過註解,類似@ReactMethod讓原生方法可以被React呼叫。
粗略流程:
MainApplication -> ReactApplication -> ReactNativeHost -> ReactInstanceManager -> ReactPackage -> NativeModule -> CatalystInstance(這位就是負責傳送的同志)
【1】所以只要實現了ReactPackage和NativeModule,將它註冊到ReactNativeHost
或者ReactInstanceManager
,就可以在React Native中繼承你原生的模組了。
2、ReactActivity
MainActivity大家肯定不陌生,預設react-native init
建立的專案裡,MainActivity十分簡單,只有一個getMainComponentName
,它就是告訴Avtivity,預設需要載入的js元件名(Component)是什麼,而其餘的事情,都是繼承的ReactActivity
幫你實現。
首先我們直接來分析下順序:
ReactActivity
預設建立了一個ReactActivityDelegate
。ReactActivityDelegate
建立了一個單例的ReactInstanceManager
(通過上面的ReactNativeHost)。ReactInstanceManager
(抽象類)內部建立了ReactRootView
。ReactInstanceManager
的實現類為XReactInstanceManagerImpl
。XReactInstanceManagerImpl
在createReactContext 建立了ReactApplicationContext
。ReactApplicationContext
實現了生命狀態事件的分發,通知js端Activity的狀態。
結合上面 MainApplication部分:
ReactInstanceManager
裡面註冊了ReactPackage
。ReactPackage
關聯了NativeModule
的實現類。NativeModule
可以通過增加註解的方法被JS端呼叫。
所以流程可以粗略認為是
1、MainApplication -> ReactApplication -> ReactNativeHost。
2、ReactActivity -> ReactActivityDelegate -> ReactNativeHost ->
ReactInstanceManager -> ReactContex -> ReactPackage -> NativeModule
例如,ReactActivity的OnResume事件流程:
1、ReactActivityDelegate.onResume();。
2、getReactNativeHost().getReactInstanceManager().onHostResume();。
3、ReactContext.onHostResume();。
4、AppStateModule.onHostResume();。
5、RCTDeviceEventEmitter 通過 emit(“appStateDidChange”, createAppStateEventMap());通知js。
【2】這裡我們需要注意,只要繼承了ReactActivity,無論你實現了多少個Activity,它們的內部ReactInstanceManager都只有一個,訊息會出現共享的情況。比如A頁面onResume是,B頁面就會onPause,如果你在JS端監聽頁面的狀態,會同時收到兩個訊息通知。
再深入的我們就先不追究,後面有深入通訊相關的文章推薦,其中涉及到CatalystInstance
、ReactBridge
、BridgeCallback
等等,通過jni轉為字串,再拼接為命令和程式碼執行等原理,有興趣的可以移步吸幾口。
可以看出,ReactInstanceManager是其中的關鍵,無論哪裡都有它的身影,ReactNativeHost的Package列表是給它,建立ReactContex也是它,其實載入JS的也是它,所以後半部分實現模組,其中很關鍵的就是它了。
二、下半部分
實現一個React Native應用,有兩種方法:
1、一種直接繼承ReactActivity
,指定js中需要載入的元件名字。
2、在佈局中加入ReactRootView
,通過ReactInstanceManager
載入管理js。
關於第一種,我們不深入展開,因為它的實現通過上面已經大致講完,參考init下來的react工程,可以很簡單的實現,他們共享Applicaton中的ReactNativeHost,和Host建立的ReactInstanceManager。
那麼我們為什麼要講第二種呢?這裡首先講解一個知識點:
【3】React Native在打包的時候,是把js程式碼打包成js bundle,js bundle就是壓縮後的js程式碼,它放在android的assert檔案下,啟動React Native應用時預設載入它。
既然如何,那麼我們是否可以修改js bundle的載入路徑?當然可以啊,不然說個卵(╯‵□′)╯︵┻━┻。通過網路下載不同的js bundle,載入實現不同的React Native App,哇塞,這不就是簡單的微信小程式麼。
ReactNativeHost也可以配置js bundle的檔案路徑,那麼繼承ReactActivity不是可以更簡單的實現嗎?不,因為繼承ReactActivity,他們內部共享了一個ReactInstanceManager,作為單獨的React Native程式模組,想想訊息、路由、store等等會互相干擾汙染·····
1、建立一個React Native 應用。
1.1、如下圖,首先你需要在佈局中建立一個ReactRootView。
1.2、建立一個ReactInstanceManager,配置你需要支援的自定義選項,最後通過build(),實現一個XReactInstanceManagerImpl,將它這是給ReactRootView。
如上圖,可以看到:
- setJSBundleFile,你可以指定載入bundle檔案的路徑
- addPackge,增加你的React Native小程式支援的原生模組,其中MainReactPackage是必須的。
- setJSMainModuleName指定了主js模組的名字。
是不是很簡單,這樣你就可以通過原生的http,去下載和更新js bundle,然後載入顯示,從而實現類似微信小程式的需求。
當然,如上圖,不要忘記給你的Activity繼承DefaultHardwareBackBtnHandler介面,還有將activity的生命狀態通知到js端。
1.3 DefaultHardwareBackBtnHandler
這裡要大篇幅講解下,DefaultHardwareBackBtnHandler介面,通過它我們可以整體瞭解,React Native從android端,到JS端對back按鍵事件的處理。
-
首先Activity需要繼承DefaultHardwareBackBtnHandler介面。DefaultHardwareBackBtnHandler只有一個invokeDefaultOnBackPressed方法。
-
ReactInstanceManager在
onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl);
中需要傳入activity和handler介面。 -
ReactInstanceManager.onBackPressed()會通過DeviceEventManagerModule,向js端傳送了
"hardwareBackPress"
訊息。 -
JS中,在BackAndrod類中,預設通過全域性靜態方法,註冊了
"hardwareBackPress"
的監聽。如下圖所示,監聽中判斷全域性Set表中的callBack,倒序迴圈判斷,是否有callback,callback是否返回true,如果都沒有,就呼叫exitApp。
- BackAndroid.App()通過下圖中的原生module,最終經過幾次變換,會呼叫到上面Activity的DefaultHardwareBackBtnHandler介面,通過invokeDefaultOnBackPressed()響應。
- 最後在invokeDefaultOnBackPressed中通過 super.onBackPressed();結束Activity的一生。
【4】綜合理解,React Native對於android back按鍵,是在onBackPressed中,把所有的back事件都發到js端,如果js端沒監聽,或者監聽都返回了false,那麼就會回到繼承了DefaultHardwareBackBtnHandler介面,實現了invokeDefaultOnBackPressed的Activity處理。
2、建立你的Moudle實現自定義互動
(˶‾᷄ ⁻̫ ‾᷅˵)下方乾貨滿滿,請耐心吸食
首先我們建立一個DetailMoudle繼承ReactContextBaseJavaModule,如下圖。
-
通過
getName
指定了js端使用的名字。 -
通過
@ReactMethod
註解指定了哪些方法可以被js端呼叫,js端可以傳遞指定型別的引數,這裡注意【5】@ReactMethod的返回型別一定是void。 -
引數傳遞js端與android端對應如下圖。
-
Callback/Promise 都是回撥介面,promise有更多元化的回撥選擇。但是注意:【6】無論是Callback 還是 Promise ,在執行invoke/(reject、resolve)之後,都會在js的訊息佇列中被銷燬,不能再呼叫一次,也就是說所有的callback只能執行一次。
-
你還可以通過訊息機制實現android和js端的互動,如下圖。
- 如下圖,通過繼承ActivityEventListener,用ReactApplicationContext新增監聽,可以方便的在module中監聽activity返回。網上說的用訊息阻塞佇列的做法就算了吧。
- 通過如下方法,可以在android的其他位置拿到module物件。
- 建立一個DetailPackage 繼承 ReactPackage,將建立好的DetailModule新增到
createNativeModules
方法中,如下圖。
- 最後將你的ReactPackage新增到你的ReactNativeHost或者ReactInstanceManager中。在js端通過下圖方式呼叫。
歐耶,終於碼完了,你是不是對於React Native 相關的通訊機制,還有互動實現有了新的瞭解呢?如果你覺得還不滿足,這裡推薦一個深度瞭解React Native通訊的系列。文中從android到js端,還有jni層面都做了詳細的跟蹤,有興趣的可跳轉觀摩,下方連結。
React-Native系列Android——Native與Javascript通訊原理
專案相關的原始碼:github.com/CarGuo/Lear…
個人github:github.com/CarGuo