React Native Android 通信原理
React Native (Android)內置了一個用于解析JavaScript(以下簡稱JS)腳本的框架,方便把Java類暴漏給JS調用,具體的使用方法參見,這篇文章就用來研究一下Java和JS的通信原理,JS是如何調用Java的。
總體結構
當初始化階段,Java端會把所有要暴漏的Java類的信息封裝成Config傳給JS,然后根據Config生成對應Java類的Javascript鏡像對象,以及要暴漏的方法,在JS中調用這個鏡像對象的方法就會被轉發到對應的Java對象上,如下所示
JS的代碼總要被解析執行,那么React是在哪里執行JS的呢?React并沒有通過webview去執行JS代碼,它是通過Jni調用c++代碼通過Javascriptcore來執行JS的,首先來看看生成so依賴的的文件,代碼在react-native/ReactAndroid/src/main/jni目錄下。 (用NDK編譯在Android上運行的c/c++代碼,關于NDK請自行google) 其中OnLoad.cpp很關鍵,里面通過Jni映射了本地的方法到Java中,是Java和C++之間的橋梁。在Java中主要通過ReactBridge.java來調用C++,NativeModulesReactCallback類是C++調用Java的橋梁。
例如以下代碼,截取自OnLoad.cpp的JNI_OnLoad方法 (這個方法會在Java載入so文件的時候由Jni首先調用)
registerNatives("com/非死book/react/bridge/JSCJavaScriptExecutor", {
makeNativeMethod("initialize", executors::createJSCExecutor),
});
意思是把Java中的JSCJavaScriptExecutor類的initialize方法映射為executors::createJSCExecutor的C++方法,這樣當在Java中調用initialize就會在C++中執行executors::createJSCExecutor。
Java端初始化
在第一個Activity創建的時候開始進行整個Brdige的Java端的初始化,流程圖如下
初始化主要做幾件事情
- 創建JSCJavaScriptExecutor,這個是個C++包裝類,會調用到C++的executors::createJSCExecutor()
- 創建NativeModuleRegistry管理所有的要暴漏給JS的Java類,暴漏給JS的java類的搜集是通過ReactActivity中的getPackages實現的,詳看上圖
- 創建ReactBridge對象,這個對象也是個C++橋梁對象,用來調用C++代碼,創建過程會調用到bridge::create()方法
-
創建config(包含了要暴漏的所有java類的信息,json格式),并通過bridge設置到JS環境中的__fbBatchedBridgeConfig變量,這樣在JS端就可以通過這個變量來獲取所有的Java類信息了,然后根據config生產對應的鏡像對象。
config格式如下:
{ "remoteModuleConfig": { "MyToastAndroid": { "moduleID": 14, "methods": { "show": { "methodID": 0, "type": "remote" } }, "constants": { "LONG": 1, "SHORT": 0 } },... } }
Java端還會創建一個CatalystInstanceImpl對象,這個對象用來管理所有的NativeModules以及與C++通信的橋梁ReactBrdige,類圖結構如下:
幾個重要的類
- NativeModuleRegistry, 維護一個mModuleInstances數組,這個數組的順序很重要,因為這和在JS端維護的鏡像對象的數組是一致的當JS調用Java的時候實際上傳遞的正是在這個數組中的索引
- NativeModuleReactCallBack, C++回調Java的對象,這個對象會在創建ReactBridge的時候傳遞給C++,當JS調用Java的方法的時候會調用這個類的方法
- ReactBridge,調用C++的橋梁
最后catalystInstance.runJSBundle()開啟JS端的初始化流程
JS端的初始化
和React Native iOS的JS初始化是一樣的,因為Android和iOS的react是同享一份JS代碼的,在react命令生成的react native工程的node_modules目錄下面存放著所有JS的模塊。在編譯的時候會把所有的JS模塊合并成一個大的JS文件。初始化就是在JS環境中執行這個文件。其中MessageQueue.js, BatchedBridge.js和NativeModules.js三個文件是關于JS bridge的。初始化流程如下圖
在遍歷RemoteModules的時候需要為每一個映射對象生成Java暴漏的方法,因為JS是不支持消息轉發,如果調用了沒有實現的方法,那么就直接生成一個錯誤,所以要知道每一個暴漏的Module要暴漏的方法,在JS端預先生成對應的實現。在Java端初始化的時候已經在JS中注入了config信息,包括了要暴漏的類和方法名,足已生成鏡像對象了。MessageQueue.js中的_genMethod方法中為每一個映射對象生成相應的方法實現。最后生成方法如下:
> NativeModules.ExportModule.hello
< function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var lastArg = args.length > 0 ? args[args.length - 1] : null;
var secondLastArg = args.length > 1 ? args[args.length - 2] : null;
var hasSuccCB = typeof lastArg === 'function';
var hasErrorCB = typeof secondLastArg === 'function';
hasErrorCB && invariant(hasSuccCB, 'Cannot have a non-function arg after a function arg.');
var numCBs = hasSuccCB + hasErrorCB;
var onSucc = hasSuccCB ? lastArg : null;
var onFail = hasErrorCB ? secondLastArg : null;
args = args.slice(0, args.length - numCBs);
return self.__nativeCall(module, method, args, onFail, onSucc);
}
當調用一個鏡像對象的方法,就會調用到_nativeCall方法,而參數就是閉包生成的時候捕獲的module和method等, 在Java端和JS端會保存一份關于暴漏的Java類對象信息的數組,這倆分數組的順序是相同的,而 _nativeCall中的參數就是要調用的Java類在數組中的索引,這樣在Java端就可以通過索引找到要調用的Java類了。在JS端這個數組是MessageQueue的modulesConfig,Java端是NativeModuleRegistry的mModuleInstances。
JS調用Java流程
JS會在調用native方法的時候調用 _nativeCall 然后調用 global.nativeFlushQueueImmediate(this._queue); ,其中nativeFlushQueueImmediate方法會調用到C++中,是JS調用C++的橋梁
nativeFlushQueueImmediate方法是在C++中的JSCExecutor.cpp中注冊的,我們先來看看JSCExecutor的創建過程,如下圖
在JSCExecutor的構造方法中調用了 installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); ,這樣就在JS環境中注冊了nativeFlushQueueImmediate方法,當在JS中調用了nativeFlushQueueImmediate就會執行JSCExecutor的nativeFlushQueueImmediate C++方法,然后調用 executor->flushQueueImmediate(resStr); ,如上圖所示,會回調到 OnLoad.cpp中的dispatchCallbacksToJava()方法,上圖中紅框中是采用了C++的閉包寫法, 參考
dispatchCallbacksToJava ---> makeJavaCall() ---> env->CallVoidMethod()
最后調用到CallVoidMethod的jni方法,這樣就從C++調用到了Java代碼了,傳入的CallVoidMethod的callback參數就是在創建ReactBrdige的時候傳入的NativeModuleReactCallback的java對象對應的jni對象,而gCallbackMethod就是call方法,這樣就調用到了Java類NativeModuleReactCallback的call方法。哇哦~終于回到java了~,Java在通過反射最后調用實際的java方法。
總結
本文只是列出了整個Bridge比較難于理解的部分以及流程,想要詳細了解具體原理還需要自己看代碼,如果遇到代碼中不明白的地方可以參考本文。關于React Native iOS的Objective-C和JS的通信原理請參考. 轉載請標明出處
來自:https://longv2go.github.io/2016/02/02/react-android-通信原理.html