基于源码版本:0.28
简单整理了一下几个组件之间的关系,放在前面:
React Native for Android(RN4A)的核心流程在QZone的 框架启动核心路径剖析 一文中讲述得很详细,本文不再赘述,主要解析RN4A里面的Native&JS通信机制。
注:Java在RN4A中是Native模块,涉及JNI的部分在java中术语为native,注意大小写的区分不要混淆。
在 ReactInstanceManager
初始化时会创建 ReactContext
,其中主要的一部分工作就是注册Native&js模块,我们看看它都做了什么:
在 createReactContext()
中会先注册 CoreModulesPackage
的Native&JS模块与所有的 ViewManager
,之后注册在 ReactNativeHost
中(0.28之前的版本在 ReactActivity
中)声明的所有其他 ReactPackage
的Native&JS模块,其中:
NativeModule
添加进 NativeModuleRegistry.Builder
后,它会依次build每一个 NativeModule
,主要做的事情就是生成moduleID、解析带 @ReactMethod
注解的方法; BatchedBridge.registerCallableModule('module', Module);
注册到 BatchedBridge.js
中以供后续查找; CatalystInstance.callFunction
代理执行。 注册完后,会初始化 CatalystInstance
, 模块注册、build生成后的 NativeModuleRegistry
与 JavaScriptModuleRegistry
都由 CatalystInstance
持有.
CatalystInstance
初始化时会初始化 ReactBridge
, ReactBridge
是在Java层与js沟通的桥梁(废话..),它是一个native类,大部分实现位于 Bridge.h/.cpp
,在初始化时调用的native initialize()
方法对应 OnLoad.cpp
中的 create
函数,它的需要三个参数:
JavaScriptExecutor
JavaScriptExecutor
的实现类 JSCJavaScriptExecutor
是一个native类,它封装了JSCore的逻辑(对应 JSCExecutor.h/.cpp
)。 ReactCallback
MessageQueueThread
我们看一下它的初始化过程:
staticvoidcreate(JNIEnv* env, jobject obj, jobject executor, jobject callback, jobject callbackQueueThread) { autoweakCallback = createNew<WeakReference>(callback); autoweakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread); autobridgeCallback = folly::make_unique<PlatformBridgeCallback>(weakCallback, weakCallbackQueueThread); autonativeExecutorFactory = extractRefPtr<CountableJSExecutorFactory>(env, executor); autoexecutorTokenFactory = folly::make_unique<JExecutorTokenFactory>(); autobridge = createNew<CountableBridge>(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
中:
bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry));
在 buildModulesConfigJSONProperty
函数中我们可以看出Native模块打包出的json数据结构大概为:
remoteModuleConfig: { 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 实现。
在Java模块被封装好注入到 gloabl.__fbBatchedBridgeConfig
后,js需要进行一些处理,这里有三个模块需要注意: NativeModule.js
, BatchedBridge.js
, MessageQueue.js
,我们来看看RN4A是怎么一步步载入Native模块的:
js模块中我们都会 require('react-native')
这样(或者ES6的 import xxx from ('react-native')
),此时加载的 react-native.js
模块中,会加载 NativeModule.js
模块, NativeModule.js
又会去加载 BatchedBridge.js
,在这里解析之前传入的 global.__fbBatchedBridgeConfig
;
BatchedBridge.js
里面封装了 MessageQueue
的一个实例,它将 global.__fbBatchedBridgeConfig
传给了 MessageQueue
的构造函数,我们看看构造函数里对它干了什么:
lazyProperty(this,'RemoteModules', () => { const{remoteModuleConfig} = configProvider(); constmodulesConfig =this._genModulesConfig(remoteModuleConfig); constmodules =this._genModules(modulesConfig); //生成调试用的Native module/method查找表 returnmodules; });
这段代码还算浅显易懂,它定义了一个 RemoteModules
属性,其中将模块、方法、常量什么的解析为js可用的变量、函数。 lazyProperty
函数实际上就是 Object.defineProperty
函数里面对property指定 get()
,不直接初始化。
再加上之前所说的,需要暴露给java的JS模块都会调用 BatchedBridge.registerCallableModule
来注册自己,最后也会跑到 MessageQueue
里面,这样一来,所有的Native&JS模块入口都集中在了 MessageQueue
里面。 BatchedBridge
最后将自己定义为了一个全局变量,方便JSCore直接找到它:
Object.defineProperty(global,'__fbBatchedBridge', { value: BatchedBridge });
BatchedBridge.js
后,回到 NativeModule.js
, 它 BatchedBridge.RemoteModules
又做了一点处理: constNativeModules = {}; Object.keys(RemoteModules).forEach((moduleName) => { Object.defineProperty(NativeModules, moduleName, { configurable: true, enumerable: true, get: () => { letmodule= RemoteModules[moduleName]; //IOS用,如果注入了nativeRequireModuleConfig方法,则由它生成moduleConfig if(module&&typeofmodule.moduleID ==='number'&& global.nativeRequireModuleConfig) { constjson = global.nativeRequireModuleConfig(moduleName); constconfig = 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, }); returnmodule; }, }); });
于是我们就会看到RN4A官方文档上所说的,如果你定义了一个Native模块,需要额外加一个文件,声明如下信息:
import{ NativeModules }from'react-native'; module.exports = NativeModules.YourNativeModule;
这下就明白了,因为所有Native的模块都会在加载的时候注册到 NativeModules
里面。
上面说到所有的JavaScript模块的方法都通过动态代理交给 CatalystInstance.callFunction
来执行,那么具体是怎么被执行的呢,我们来看看
在 JSCExecutor.cpp
这一层,它会生成JS调用 gloabl.__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null, [module, method, args])
交由JavascriptCore来执行。结合上文分析,这个 gloabl.__fbBatchedBridge
就是 MessageQueue
。
MessageQueue.callFunctionReturnFlushedQueue
里面直接调用了 __callFunction()
函数,我们看看它是怎么找JS模块执行的:
__callFunction(module: string, method: string, args: any) { //标记时间, 开始systrace constmoduleMethods =this._callableModules[module]; //检查module合法性 moduleMethods[method].apply(moduleMethods, args); //结束systrace }
this._callableModules
里面就存放了所有由 BatchedBridge.registerCallableModule
注册上来的java模块,然后再找对应方法执行即可。
关于js如何执行java的,请看下文分解吧