React Native Android 通信原理

作者: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)

reactnativejni
reactnativejni
其中OnLoad.cpp很關(guān)鍵,里面通過Jni映射了本地的方法到Java中,是Java和C++之間的橋梁。在Java中主要通過ReactBridge.java來調(diào)用C++,NativeModulesReactCallback類是C++調(diào)用Java的橋梁。

例如以下代碼,截取自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端的初始化,流程圖如下

初始化主要做幾件事情

  1. 創(chuàng)建JSCJavaScriptExecutor,這個是個C++包裝類,會調(diào)用到C++的executors::createJSCExecutor()
  2. 創(chuàng)建NativeModuleRegistry管理所有的要暴漏給JS的Java類,暴漏給JS的java類的搜集是通過ReactActivity中的getPackages實現(xiàn)的,詳看上圖
  3. 創(chuàng)建ReactBridge對象,這個對象也是個C++橋梁對象,用來調(diào)用C++代碼,4. 創(chuàng)建過程會調(diào)用到bridge::create()方法
  4. 創(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)如下:
React 類圖
React 類圖

幾個重要的類

  1. NativeModuleRegistry, 維護一個mModuleInstances數(shù)組,這個數(shù)組的順序很重要,因為這和在JS端維護的鏡像對象的數(shù)組是一致的當JS調(diào)用Java的時候?qū)嶋H上傳遞的正是在這個數(shù)組中的索引
  2. NativeModuleReactCallBack, C++回調(diào)Java的對象,這個對象會在創(chuàng)建ReactBridge的時候傳遞給C++,當JS調(diào)用Java的方法的時候會調(diào)用這個類的方法
  3. 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的。初始化流程如下圖
react-native 通信原理
react-native 通信原理

在遍歷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的通信原理請參考。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 作者:Longv2go地址:http://t.cn/RGxdO76 React Native (Android)內(nèi)...
    IT程序獅閱讀 2,063評論 0 4
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 想離你遠 怕看不見 更怕北風野蠻 想撫你的臉 怕你太暖 怕你在溫柔中消散 只能望著你 不敢太近,也不能太遠 才能與...
    長河冬陽閱讀 381評論 4 9
  • 母親節(jié)的發(fā)明者是安娜·賈維斯,直到臨死前她都反對著母親節(jié)的商業(yè)化。 列夫·托爾斯泰寫了《安娜·卡列尼娜》,有讀者很...
    黑夜思考者閱讀 366評論 1 4
  • 圖片來自網(wǎng)絡(luò) DAY 1 “真TM冷!” 方義拎著包下了出租車,冷風立即從他的衣服縫隙里鉆了進來,他打了個冷顫,...
    腦洞大叔閱讀 2,060評論 2 7

友情鏈接更多精彩內(nèi)容