React Native Android 通信原理

mikyxiang 8年前發布 | 10K 次閱讀 Java ReactNative 移動開發 React Native C/C++

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端的初始化,流程圖如下

初始化主要做幾件事情

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

幾個重要的類

  1. NativeModuleRegistry, 維護一個mModuleInstances數組,這個數組的順序很重要,因為這和在JS端維護的鏡像對象的數組是一致的當JS調用Java的時候實際上傳遞的正是在這個數組中的索引
  2. NativeModuleReactCallBack, C++回調Java的對象,這個對象會在創建ReactBridge的時候傳遞給C++,當JS調用Java的方法的時候會調用這個類的方法
  3. 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

 

Save

 本文由用戶 mikyxiang 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!