基于源碼版本: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,可以供初學者學習:)