天天看點

jsbridge實作及原理_【React Native for Android】jsBridge實作原理

基于源碼版本:0.28

簡單整理了一下幾個元件之間的關系,

1.JSBridge初始化過程

React Native for Android(RN4A)的核心流程在QZone的架構啟動核心路徑剖析一文中講述得很詳細,本文不再贅述,主要解析RN4A裡面的Native&JS通信機制。

注:Java在RN4A中是Native子產品,涉及JNI的部分在java中術語為native,注意大小寫的區分不要混淆。

在ReactInstanceManager初始化時會建立ReactContext,其中主要的一部分工作就是注冊Native&js子產品,我們看看它都做了什麼:

1.1子產品注冊

在createReactContext()中會先注冊CoreModulesPackage的Native&JS子產品與所有的ViewManager,之後注冊在ReactNativeHost中(0.28之前的版本在ReactActivity中)聲明的所有其他ReactPackage的Native&JS子產品,其中:

o    Native子產品注冊

将所有的NativeModule添加進NativeModuleRegistry.Builder後,它會依次build每一個NativeModule,主要做的事情就是生成moduleID、解析帶@ReactMethod注解的方法;

o    JavaScript子產品注冊

o    在js層,js子產品在寫的時候都需要加上BatchedBridge.registerCallableModule('module', Module);注冊到BatchedBridge.js中以供後續查找;

o    在java層,js子產品将想要暴露出來的方法聲明為一個接口類,它被build的時候利用Java動态代理生成執行個體, 具體的方法invoke由CatalystInstance.callFunction代理執行。

注冊完後,會初始化CatalystInstance, 子產品注冊、build生成後的NativeModuleRegistry與JavaScriptModuleRegistry都由CatalystInstance持有.

1.2初始化ReactBridge

CatalystInstance初始化時會初始化ReactBridge,ReactBridge是在Java層與js溝通的橋梁(廢話..),它是一個native類,大部分實作位于Bridge.h/.cpp,在初始化時調用的native initialize()方法對應OnLoad.cpp中的create函數,它的需要三個參數:

o    JavaScriptExecutor

RN4A使用WebKit的JavaScriptCore(JSCore)來解析js, JavaScriptExecutor的實作類JSCJavaScriptExecutor是一個native類,它封裝了JSCore的邏輯(對應JSCExecutor.h/.cpp)。

o    ReactCallback

為JavaScript提供Java module執行入口。

o    MessageQueueThread

包裝着Java子產品執行的線程Handler。 注: RN4A裡面有三個主要執行線程:UI線程, JS線程, Java線程.

我們看一下它的初始化過程:1

2

3

4

5

6

7

8

9

10static void create(JNIEnv* env, jobject obj, jobject   executor, jobject callback,

jobject   callbackQueueThread) {

auto weakCallback = createNew(callback);

auto weakCallbackQueueThread =   createNew(callbackQueueThread);

auto bridgeCallback =   folly::make_unique(weakCallback, weakCallbackQueueThread);

auto nativeExecutorFactory =   extractRefPtr(env, executor);

auto executorTokenFactory =   folly::make_unique();

auto bridge = createNew(nativeExecutorFactory.get(), std::move(executorTokenFactory), std::move(bridgeCallback));

setCountableForJava(env, obj, std::move(bridge));

}

實際上做的工作也不多,就是一堆封裝,最後生成了一個繼承Countable的Bridge執行個體,繼承Countable的對象的記憶體是由native配置設定的,也由native回收。

CatalystInstance初始化完ReactBridge後, 建立NativeModuleRegistry的JSON至并放在javascript的全局變量global.__fbBatchedBridgeConfig中:1

2

3bridge.setGlobalVariable(

"__fbBatchedBridgeConfig",

buildModulesConfigJSONProperty(mJavaRegistry));

在buildModulesConfigJSONProperty函數中我們可以看出Native子產品打包出的json資料結構大概為:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18remoteModuleConfig:   {

moduleName : {

moduleId: 0,

supportsWebWorkers: false,

methods: {

methodName : {

methodId: 0,

methodType: remote

}

// other methods ...

},

constants: {

constant1: 123,

constant2: 234

}

},

// other modules ...

}

這裡的bridge.setGlobalVariable最後到JSCExecutor.setGlobalVariable中,通過JavaScriptCore::JSObjectSetProperty實作。

JS加載Java子產品

在Java子產品被封裝好注入到gloabl.__fbBatchedBridgeConfig後,js需要進行一些處理,這裡有三個子產品需要注意:NativeModule.js, BatchedBridge.js, MessageQueue.js,我們來看看RN4A是怎麼一步步載入Native子產品的:

1.    js子產品中我們都會require('react-native')這樣(或者ES6的import xxx from ('react-native')),此時加載的react-native.js子產品中,會加載NativeModule.js子產品,NativeModule.js又會去加載BatchedBridge.js,在這裡解析之前傳入的global.__fbBatchedBridgeConfig;

2.    BatchedBridge.js裡面封裝了MessageQueue的一個執行個體,它将global.__fbBatchedBridgeConfig傳給了MessageQueue的構造函數,我們看看構造函數裡對它幹了什麼:1

2

3

4

5

6

7

8

9lazyProperty(this, 'RemoteModules', () => {

const {remoteModuleConfig} = configProvider();

const modulesConfig = this._genModulesConfig(remoteModuleConfig);

const modules = this._genModules(modulesConfig);

//生成調試用的Native module/method查找表

return modules;

});

這段代碼還算淺顯易懂,它定義了一個RemoteModules屬性,其中将子產品、方法、常量什麼的解析為js可用的變量、函數。lazyProperty函數實際上就是Object.defineProperty函數裡面對property指定get(),不直接初始化。

再加上之前所說的,需要暴露給java的JS子產品都會調用BatchedBridge.registerCallableModule來注冊自己,最後也會跑到MessageQueue裡面,這樣一來,所有的Native&JS子產品入口都集中在了MessageQueue裡面。BatchedBridge最後将自己定義為了一個全局變量,友善JSCore直接找到它:1Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });

1.    加載好BatchedBridge.js後,回到NativeModule.js, 它BatchedBridge.RemoteModules又做了一點處理:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26const NativeModules = {};

Object.keys(RemoteModules).forEach((moduleName)   => {

Object.defineProperty(NativeModules, moduleName, {

configurable: true,

enumerable: true,

get: () => {

let module = RemoteModules[moduleName];

//IOS用,如果注入了nativeRequireModuleConfig方法,則由它生成moduleConfig

if (module && typeof module.moduleID === 'number' &&   global.nativeRequireModuleConfig) {

const json =   global.nativeRequireModuleConfig(moduleName);

const config = json && JSON.parse(json);

module = config &&   BatchedBridge.processModuleConfig(config, module.moduleID);

RemoteModules[moduleName] = module;

}

//将每個生成的module結構定義為自己的變量

Object.defineProperty(NativeModules,   moduleName, {

configurable: true,

enumerable: true,

value: module,

});

return module;

},

});

});

于是我們就會看到RN4A官方文檔上所說的,如果你定義了一個Native子產品,需要額外加一個檔案,聲明如下資訊:1

2import { NativeModules } from 'react-native';

module.exports =   NativeModules.YourNativeModule;

這下就明白了,因為所有Native的子產品都會在加載的時候注冊到NativeModules裡面。

Java執行JS

上面說到所有的JavaScript子產品的方法都通過動态代理交給CatalystInstance.callFunction來執行,那麼具體是怎麼被執行的呢,我們來看看在JSCExecutor.cpp這一層,它會生成JS調用gloabl.__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null, [module, method, args])交由JavascriptCore來執行。結合上文分析,這個gloabl.__fbBatchedBridge就是MessageQueue。

MessageQueue.callFunctionReturnFlushedQueue裡面直接調用了__callFunction()函數,我們看看它是怎麼找JS子產品執行的:1

2

3

4

5

6

7__callFunction(module: string, method: string, args: any) {

//标記時間, 開始systrace

const moduleMethods = this._callableModules[module];

//檢查module合法性

moduleMethods[method].apply(moduleMethods,   args);

//結束systrace

}

this._callableModules裡面就存放了所有由BatchedBridge.registerCallableModule注冊上來的java子產品,然後再找對應方法執行即可。

關于js如何執行java的,請看下文分解吧。大頭鬼的ReactNativeAndroid源碼分析-Js如何調用Native的代碼寫這塊很不錯,大家也可以作為參考。

由于RN隻是一個View層的架構,還需要配合Flux或者Redux才能進行項目開發,并且JSX這樣的文法與往常的Web、Native開發文法習慣都比較不同,它的學習曲線非常高。我寫了一個配合Flow&ES6的todo-example,可以供初學者學習:)