作者:Longv2go
地址:http://t.cn/RGxdO76
React Native (Android)內(nèi)置了一個用于解析JavaScript(以下簡稱JS)腳本的框架,方便把Java類暴漏給JS調(diào)用,具體的使用方法參見,這篇文章就用來研究一下Java和JS的通信原理,JS是如何調(diào)用Java的。
總體結(jié)構(gòu)
當初始化階段,Java端會把所有要暴漏的Java類的信息封裝成Config傳給JS,然后根據(jù)Config生成對應(yīng)Java類的Javascript鏡像對象,以及要暴漏的方法,在JS中調(diào)用這個鏡像對象的方法就會被轉(zhuǎn)發(fā)到對應(yīng)的Java對象上,如下所示
JS的代碼總要被解析執(zhí)行,那么React是在哪里執(zhí)行JS的呢?React并沒有通過webview去執(zhí)行JS代碼,它是通過Jni調(diào)用c++代碼通過Javascriptcore來執(zhí)行JS的,首先來看看生成so依賴的的文件,代碼在react-native/ReactAndroid/src/main/jni目錄下。(用NDK編譯在Android上運行的c/c++代碼,關(guān)于NDK請自行g(shù)oogle)

例如以下代碼,截取自O(shè)nLoad.cpp的JNI_OnLoad方法(這個方法會在Java載入so文件的時候由Jni首先調(diào)用)
registerNatives("com/facebook/react/bridge/JSCJavaScriptExecutor", {
makeNativeMethod("initialize", executors::createJSCExecutor),
});
意思是把Java中的JSCJavaScriptExecutor類的initialize方法映射為executors::createJSCExecutor的C++方法,這樣當在Java中調(diào)用initialize就會在C++中執(zhí)行executors::createJSCExecutor。
Java端初始化
在第一個Activity創(chuàng)建的時候開始進行整個Brdige的Java端的初始化,流程圖如下
初始化主要做幾件事情
- 創(chuàng)建JSCJavaScriptExecutor,這個是個C++包裝類,會調(diào)用到C++的
executors::createJSCExecutor() - 創(chuàng)建NativeModuleRegistry管理所有的要暴漏給JS的Java類,暴漏給JS的java類的搜集是通過ReactActivity中的getPackages實現(xiàn)的,詳看上圖
- 創(chuàng)建ReactBridge對象,這個對象也是個C++橋梁對象,用來調(diào)用C++代碼,4. 創(chuàng)建過程會調(diào)用到
bridge::create()方法 - 創(chuàng)建config(包含了要暴漏的所有java類的信息,json格式),并通過bridge設(shè)置到JS環(huán)境中的
__fbBatchedBridgeConfig變量,這樣在JS端就可以通過這個變量來獲取所有的Java類信息了,然后根據(jù)config生產(chǎn)對應(yīng)的鏡像對象。
config格式如下:
{
"remoteModuleConfig": {
"MyToastAndroid": {
"moduleID": 14,
"methods": {
"show": {
"methodID": 0,
"type": "remote"
}
},
"constants": {
"LONG": 1,
"SHORT": 0
}
},...
}
}
Java端還會創(chuàng)建一個CatalystInstanceImpl對象,這個對象用來管理所有的NativeModules以及與C++通信的橋梁ReactBrdige,類圖結(jié)構(gòu)如下:
幾個重要的類
- NativeModuleRegistry, 維護一個mModuleInstances數(shù)組,這個數(shù)組的順序很重要,因為這和在JS端維護的鏡像對象的數(shù)組是一致的當JS調(diào)用Java的時候?qū)嶋H上傳遞的正是在這個數(shù)組中的索引
- NativeModuleReactCallBack, C++回調(diào)Java的對象,這個對象會在創(chuàng)建ReactBridge的時候傳遞給C++,當JS調(diào)用Java的方法的時候會調(diào)用這個類的方法
- ReactBridge,調(diào)用C++的橋梁
最后catalystInstance.runJSBundle()開啟JS端的初始化流程。
JS端的初始化
和React Native iOS的JS初始化是一樣的,因為Android和iOS的react是同享一份JS代碼的,在react命令生成的react native工程的node_modules目錄下面存放著所有JS的模塊。在編譯的時候會把所有的JS模塊合并成一個大的JS文件。初始化就是在JS環(huán)境中執(zhí)行這個文件。其中MessageQueue.js, BatchedBridge.js和NativeModules.js三個文件是關(guān)于JS bridge的。初始化流程如下圖
在遍歷RemoteModules的時候需要為每一個映射對象生成Java暴漏的方法,因為JS是不支持消息轉(zhuǎn)發(fā),如果調(diào)用了沒有實現(xiàn)的方法,那么就直接生成一個錯誤,所以要知道每一個暴漏的Module要暴漏的方法,在JS端預(yù)先生成對應(yīng)的實現(xiàn)。在Java端初始化的時候已經(jīng)在JS中注入了config信息,包括了要暴漏的類和方法名,足已生成鏡像對象了。MessageQueue.js中的_genMethod方法中為每一個映射對象生成相應(yīng)的方法實現(xiàn)。最后生成方法如下:
> 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);
}
當調(diào)用一個鏡像對象的方法,就會調(diào)用到_nativeCall方法,而參數(shù)就是閉包生成的時候捕獲的module和method等, 在Java端和JS端會保存一份關(guān)于暴漏的Java類對象信息的數(shù)組,這倆分數(shù)組的順序是相同的,而 _nativeCall中的參數(shù)就是要調(diào)用的Java類在數(shù)組中的索引,這樣在Java端就可以通過索引找到要調(diào)用的Java類了。在JS端這個數(shù)組是MessageQueue的modulesConfig,Java端是NativeModuleRegistry的mModuleInstances。
JS調(diào)用Java流程
JS會在調(diào)用native方法的時候調(diào)用_nativeCall
然后調(diào)用global.nativeFlushQueueImmediate(this._queue);
,其中nativeFlushQueueImmediate方法會調(diào)用到C++中,是JS調(diào)用C++的橋梁
nativeFlushQueueImmediate方法是在C++中的JSCExecutor.cpp中注冊的,我們先來看看JSCExecutor的創(chuàng)建過程,如下圖
在JSCExecutor的構(gòu)造方法中調(diào)用了installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
,這樣就在JS環(huán)境中注冊了nativeFlushQueueImmediate方法,當在JS中調(diào)用了nativeFlushQueueImmediate就會執(zhí)行JSCExecutor的nativeFlushQueueImmediate C++方法,然后調(diào)用executor->flushQueueImmediate(resStr);,如上圖所示,會回調(diào)到 OnLoad.cpp中的dispatchCallbacksToJava()方法,上圖中紅框中是采用了C++的閉包寫法,參考
dispatchCallbacksToJava ---> makeJavaCall() ---> env->CallVoidMethod()
最后調(diào)用到CallVoidMethod的jni方法,這樣就從C++調(diào)用到了Java代碼了,傳入的CallVoidMethod的callback參數(shù)就是在創(chuàng)建ReactBrdige的時候傳入的NativeModuleReactCallback的java對象對應(yīng)的jni對象,而gCallbackMethod就是call方法,這樣就調(diào)用到了Java類NativeModuleReactCallback的call方法。哇哦終于回到j(luò)ava了,Java在通過反射最后調(diào)用實際的java方法。
總結(jié)
本文只是列出了整個Bridge比較難于理解的部分以及流程,想要詳細了解具體原理還需要自己看代碼,如果遇到代碼中不明白的地方可以參考本文。關(guān)于React Native iOS的Objective-C和JS的通信原理請參考。