React Native(以下简称RN)的目标是用基于react的JavaScript写代码,在iOS/Android平台上原生渲染,正如他们的口号"Learn Once,Write anywhere!",只要学会了大前端,iOS/Android/Web通吃,这样就很神奇了,react.js还是那个react.js,模块化、虚拟DOM、JSX语法概念一样没少,甚至可以基于流行的flux单向数据流来架构我们的应用,而在客户端并不是一个web页面,而是纯原生渲染,性能比纯web页提升很多,而且还顺带具有像web页一样的动态更新能力。
随着版本的迭代更新,RN功能和相关特性也越来越多,代码复杂度也随之上升,这里先不论RN的争议和发展趋势,而来学习下其优秀的架构设计和代码实现。我们就用一个最简单的项目来进行剖析,运行命令react-native init RNDemo
,这里我的RN版本号为:0.47.0,RN最核心的当属js与native的通信机制,理解了这套机制则比较容易理解RN整个架构。
Native模块
RCTBridgeModule
native导出给js的类称之为模块类,如RCTUIManager
RCTTiming
等,每个模块类都实现了RCTBridgeModule
协议
@protocol RCTBridgeModule <NSObject>
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName;
@optional
@property (nonatomic, weak, readonly) RCTBridge *bridge;
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
#define RCT_EXPORT_METHOD(method) \
RCT_REMAP_METHOD(, method)
#define RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(method) \
RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(, method)
#define RCT_REMAP_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
- (void)method;
#define RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, YES) \
- (id)method;
#define RCT_EXTERN_MODULE(objc_name, objc_supername) \
RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
objc_name : objc_supername \
@end \
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE(js_name)
#define RCT_EXTERN_METHOD(method) \
_RCT_EXTERN_REMAP_METHOD(, method, NO)
#define RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(method) \
_RCT_EXTERN_REMAP_METHOD(, method, YES)
#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
+ (NSArray *)RCT_CONCAT(__rct_export__, \
RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
return @[@#js_name, @#method, @is_blocking_synchronous_method]; \
}
- (NSArray<id<RCTBridgeMethod>> *)methodsToExport;
- (NSDictionary<NSString *, id> *)constantsToExport;
- (void)batchDidComplete;
- (void)partialBatchDidFlush;
@end
该协议定义了模块导出方法、导出常量、模块运行队列等,还定义了很多宏。
RCT_EXPORT_MODULE
模块类被加载进runtime的时候,执行load
方法,将该模块类的Class
信息添加到一个全局数组RCTModuleClasses
里去,RCTGetModuleClasses()
方法可获取该数组。
RCT_EXPORT_METHOD(method)
RN在e9095b2
版本移除了bang神博客所说的从data数据段获取导出方法的黑魔法,而是给每个导出方法添加一个对应的方法。
比如method为doSomething
,则宏展开后为
+ (NSArray *)__rct_export__(行号和系统计数){
return @[@"", @"doSomething", @(NO)];
}
- (void)doSomething;
RCTModuleData
方法- (NSArray<id<RCTBridgeMethod>> *)methods
,通过遍历模块运行时方法列表,找到有__rct_export__
前缀的方法,根据方法返回的数组实例化RCTModuleMethod
,从而收集到所有RCT_EXPORT_METHOD
对应的导出方法。
模块配置
所有的模块配置存放在ModuleRegistry
C++类中,JSCExecutor
的getNativeModule
方法可获得指定模块的配置,最终是从ModuleRegistry
类方法getConfig
拿到。JSCNativeModules
管理Native的导出模块,JSCNativeModules
构造函数传入JsToNativeBridge
的getModuleRegistry
方法返回的ModuleRegistry
指针,JsToNativeBridge
管理js调用Native所需配置、方法等,是js调用native的native响应方。
以RCTAppState
模块为例,拿到的模块信息如下:
struct ModuleConfig {
size_t index;
folly::dynamic config;
};
{
21;
[AppState,{initialAppState:unknown},[getCurrentAppState,addListener,removeListeners]];
}
index是模块index,config动态数组依次存放moudlename、export constants、export methodNames array 、promiseMethodId array 、syncMethodId array。
Native模块生成
Native模块初始化
在应用启动delegate里,创建了一个RCTRootView
实例,这个实例初始化了RCTBridge
实例,在其- (void)setUp
方法里:
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];
self.batchedBridge
是RCTCxxBridge
的实例,用于批量桥接,之前版本的RCTBatchedBridge
已不再实现,在- (void)start
方法主要步骤是:
- 发送js即将加载通知
-
创建常驻线程
_jsThread
,native和js互相调用默认会在该线程执行,也可以自己指定模块的运行队列_jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(runJSRunLoop) object:nil]; _jsThread.name = RCTJSThreadName; _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive; [_jsThread start];
-
初始化所有native modules
- (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup { ... NSArray<id<RCTBridgeModule>> *extraModules = nil; if (self.delegate) { if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { extraModules = [self.delegate extraModulesForBridge:_parentBridge]; } } else if (self.moduleProvider) { extraModules = self.moduleProvider(); } ... NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new]; NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new]; NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new]; ... // Set up moduleData for pre-initialized module instances for (id<RCTBridgeModule> module in extraModules) { Class moduleClass = [module class]; NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); if (RCT_DEBUG) { ... } // Instantiate moduleData container RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self]; moduleDataByName[moduleName] = moduleData; [moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } ... // Set up moduleData for automatically-exported modules for (Class moduleClass in RCTGetModuleClasses()) { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); if ([moduleName isEqual:@"RCTJSCExecutor"]) { continue; } // Check for module name collisions RCTModuleData *moduleData = moduleDataByName[moduleName]; if (moduleData) { if (moduleData.hasInstance) { // Existing module was preregistered, so it takes precedence continue; } else if ([moduleClass new] == nil) { // The new module returned nil from init, so use the old module continue; } else if ([moduleData.moduleClass new] != nil) { // Both modules were non-nil, so it's unclear which should take precedence RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the " "name '%@', but name was already registered by class %@", moduleClass, moduleName, moduleData.moduleClass); } } // Instantiate moduleData // TODO #13258411: can we defer this until config generation? moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; moduleDataByName[moduleName] = moduleData; [moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } ... // Store modules _moduleDataByID = [moduleDataByID copy]; _moduleDataByName = [moduleDataByName copy]; _moduleClassesByID = [moduleClassesByID copy]; ... // Dispatch module init onto main thead for those modules that require it for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) { (void)[moduleData instance]; } } ... // From this point on, RCTDidInitializeModuleNotification notifications will // be sent the first time a module is accessed. _moduleSetupComplete = YES; [self _prepareModulesWithDispatchGroup:dispatchGroup]; ... }
主要步骤:
-
得到bridgeModule数组
extraModules
从delegate实现方或传入的
moduleProvider
属性获取extraModules
,这里两者都为空 实例化
moduleClassesByID
Class
数组,moduleDataByID
RCTModuleData*
模块数据数组,moduleDataByName
名字模块数据字典,临时保存,作用见名思义遍历
extraModules
,填充第二步数组和字典-
从
RCTGetModuleClasses()
得到声明了RCT_EXPORT_MODULE
的所有模块Class
,遍历数组,先检查命名冲突,再以moduleClass
为参数实例化RCTModuleData
,然后填充第二步数组和字典RCTModuleData
类管理导出给js的模块数据,包括Class
信息,导出方法,导出常量等 遍历
_moduleDataByID
,调用RCTModuleData
对应实例的instance
方法,初始化RCTModuleData
类执行
_prepareModulesWithDispatchGroup
方法,初始化除白名单外的模块导出常量
-
-
实例化
Instance
类,该类在下文有介绍_reactInstance.reset(new Instance);
-
实例化抽象工厂类
JSExecutorFactory
__weak RCTCxxBridge *weakSelf = self; std::shared_ptr<JSExecutorFactory> executorFactory; if (!self.executorClass) { BOOL useCustomJSC = [self.delegate respondsToSelector:@selector(shouldBridgeUseCustomJSC:)] && [self.delegate shouldBridgeUseCustomJSC:self]; // The arg is a cache dir. It's not used with standard JSC. executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object ("UseCustomJSC", (bool)useCustomJSC) #if RCT_PROFILE ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch) #endif )); } else { id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass]; executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) { if (error) { [weakSelf handleError:error]; } })); }
本地JavaScriptCore运行会实例化
JSCExecutorFactory
, 浏览器远程调试模式会实例RCTObjcExecutorFactory
,这里我们就以JSCExecutorFactory
为例分析,远程调试会在另一篇做分析。 -
在
_jsThread
线程上初始化桥接- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory { if (!self.valid) { return; } RCTAssertJSThread(); __weak RCTCxxBridge *weakSelf = self; _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) { if (error) { [weakSelf handleError:error]; } }); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil); // This can only be false if the bridge was invalidated before startup completed if (_reactInstance) { // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance _reactInstance->initializeBridge( std::unique_ptr<RCTInstanceCallback>(new RCTInstanceCallback(self)), executorFactory, _jsMessageThread, [self _buildModuleRegistry]); #if RCT_PROFILE ... #endif } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); }
主要步骤:
- 实例化
RCTMessageThread
类,RCTMessageThread
类封装了在_jsThread常驻线程上的同步和异步执行任务的方法。 -
Instance
实例_reactInstance
执行了初始化方法void initializeBridge(..)
, 注入了所需依赖,Instance
是iOS/Android与javacriptCore交互的入口类。初始化了模块注册表ModuleRegistry
实例,ModuleRegistry
是C++类,ios/android均需填充模块信息数组
- 实例化
-
加载jsbundle源文件
RCTJavaScriptLoader
封装了加载jsbundle文件的方法,主要步骤是需要下载则通过RCTMultipartDataTask
下载js文件,最后返回NSData
数据 -
执行解析jsbundle
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ RCTCxxBridge *strongSelf = weakSelf; if (sourceCode && strongSelf.loading) { [strongSelf executeSourceCode:sourceCode sync:NO]; } });
在上述组任务结束时,收到通知,在最高等级全局队列执行加载完的jsbundle
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync { // This will get called from whatever thread was actually executing JS. dispatch_block_t completion = ^{ // Flush pending calls immediately so we preserve ordering [self _flushPendingCalls]; // Perform the state update and notification on the main thread, so we can't run into // timing issues with RCTRootView dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self->_parentBridge userInfo:@{@"bridge": self}]; // Starting the display link is not critical to startup, so do it last [self ensureOnJavaScriptThread:^{ // Register the display link to start sending js calls after everything is setup [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; }]; }); }; if (sync) { [self executeApplicationScriptSync:sourceCode url:self.bundleURL]; completion(); } else { [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion]; } #if RCT_DEV ... #endif }
主要步骤:
清空调用队列
_pendingCalls
,_pendingCount
置0发送js加载完毕通知,
_jsThread
线程,设置RCTDisplayLink
监测js线程帧率,原理在另一篇作分析-
执行解析jsbundle,这里是异步执行
执行解析中间过程为了解耦和扩展性,引入了
NativeToJsBridge
等类,层次很多,可能看到这就有点晕了,来看下这块的执行过程。
jsBundle执行
首先,上文提到的JSCExecutorFactory
,运用了标准的工厂模式:
JSCExecutor
是本地JavaScriptCore具体执行的产品类,是跨平台的C++类,是一个非常重要的类。
JSCExecutor
构造时向JSContext注入了全局函数
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
...
installGlobalProxy(m_context, "nativeModuleProxy",
exceptionWrapMethod<&JSCExecutor::getNativeModule>());
...
installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");
installNativeHook
方法同时注册了回调函数exceptionWrapMethod<method>()
,当JS端调用对应方法,exceptionWrapMethod
全局函数被调用。
MessageQueueThread
是RCTMessageThread
的抽象基类,封装了在消息队列里同步和异步执行的方法,MessageQueueThread
是需要各平台各自实现的。
class MessageQueueThread {
public:
virtual ~MessageQueueThread() {}
virtual void runOnQueue(std::function<void()>&&) = 0;
// runOnQueueSync and quitSynchronous are dangerous. They should only be
// used for initialization and cleanup.
virtual void runOnQueueSync(std::function<void()>&&) = 0;
// Once quitSynchronous() returns, no further work should run on the queue.
virtual void quitSynchronous() = 0;
};
Instance
类依赖了很多类,它是一个C++类,是iOS/Android与javacriptCore交互的入口类,封装了native与js的交互,包括解析js字符流,调用js方法,设置jscontext全局变量,发出回调等。
class RN_EXPORT Instance {
public:
~Instance();
void initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry);
void setSourceURL(std::string sourceURL);
void loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously);
void loadUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously);
bool supportsProfiling();
void startProfiler(const std::string& title);
void stopProfiler(const std::string& title, const std::string& filename);
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
void callJSFunction(std::string&& module, std::string&& method, folly::dynamic&& params);
void callJSCallback(uint64_t callbackId, folly::dynamic&& params);
// This method is experimental, and may be modified or removed.
template <typename T>
Value callFunctionSync(const std::string& module, const std::string& method, T&& args) {
CHECK(nativeToJsBridge_);
return nativeToJsBridge_->callFunctionSync(module, method, std::forward<T>(args));
}
#ifdef WITH_JSC_MEMORY_PRESSURE
void handleMemoryPressure(int pressureLevel);
#endif
private:
void callNativeModules(folly::dynamic&& calls, bool isEndOfBatch);
void loadApplication(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
void loadApplicationSync(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
std::shared_ptr<InstanceCallback> callback_;
std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::mutex m_syncMutex;
std::condition_variable m_syncCV;
bool m_syncReady = false;
};
Instance
类依赖的InstanceCallback
JSExecutorFactory
MessageQueueThread
ModuleRegistry
等类,都需要平台各自实现,Instance
是作为一个跨平台的接口封装类,如iOS中,callback
成员变量是指向InstanceCallback
的子类RCTInstanceCallback
,jsef
成员则动态指向JSExecutorFactory
的具体工厂类,JSExecutorFactory
则可以选择对应的具体产品类。jsQueue
指向RCTMessageThread
类,moduleRegistry
需要各平台自行填充。Instance
类函数实现基本都是通过NativeToJsBridge
具体实现的,在Instance
执行initializeBridge
时,在MessageQueueThread
同步初始化了NativeToJsBridge
的实例nativeToJsBridge_,那么我们来看看NativeToJsBridge
类。
在RCTCxxBridge
类的enqueueApplicationScript:url:onComplete:
方法,根据jsbundle类型去执行对应的方法。jsbundle目前有三种类型:String
RAMBundle
BCBundle
,String
表示普通jsbundle,用bundle
命令整合出来的。RAMBundle
是用unbundle
命令打出来的bundle,它除了生成整合的js文件index.ios.bundle
外,还会生成各个单独的未整合js文件,全部放在js-modules
目录下, bundle头四个字节固定为0xFB0BD1E5
。BCBundle
是js字节码bundle类型,并未用到,就以普通jsbundle为例
self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
sourceUrlStr.UTF8String, false);
核心是通过Instance
实例去执行解析, Instance
的loadScriptFromString
方法调用到NativeToJsBridge
的loadApplication
和loadApplicationSync
的方法,NativeToJsBridge
实例在Instance
类构造的时候在_jsThread
线程初始化。
void NativeToJsBridge::loadApplication(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
[unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
auto unbundle = unbundleWrap.move();
if (unbundle) {
executor->setJSModulesUnbundle(std::move(unbundle));
}
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
普通jsbundle调用到JSExecutor
子类loadApplicationScript
方法,以子类JSCExecutor
为例:
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
SystraceSection s("JSCExecutor::loadApplicationScript",
"sourceURL", sourceURL);
std::string scriptName = simpleBasename(sourceURL);
ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
String jsSourceURL(m_context, sourceURL.c_str());
// TODO t15069155: reduce the number of overrides here
#ifdef WITH_FBJSCEXTENSIONS
...
#elif defined(__APPLE__)
BundleHeader header;
memcpy(&header, script->c_str(), std::min(script->size(), sizeof(BundleHeader)));
auto scriptTag = parseTypeFromHeader(header);
if (scriptTag == ScriptTag::BCBundle) {
using file_ptr = std::unique_ptr<FILE, decltype(&fclose)>;
file_ptr source(fopen(sourceURL.c_str(), "r"), fclose);
int sourceFD = fileno(source.get());
JSValueRef jsError;
JSValueRef result = JSC_JSEvaluateBytecodeBundle(m_context, NULL, sourceFD, jsSourceURL, &jsError);
if (result == nullptr) {
throw JSException(m_context, jsError, jsSourceURL);
}
} else
#endif
{
String jsScript;
{
SystraceSection s_("JSCExecutor::loadApplicationScript-createExpectingAscii");
ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_START);
jsScript = adoptString(std::move(script));
ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP);
}
#ifdef WITH_FBSYSTRACE
fbsystrace_end_section(TRACE_TAG_REACT_CXX_BRIDGE);
#endif
SystraceSection s_("JSCExecutor::loadApplicationScript-evaluateScript");
evaluateScript(m_context, jsScript, jsSourceURL);
}
flush();
ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
ReactMarker::logMarker(ReactMarker::RUN_JS_BUNDLE_STOP);
}
该方法主要步骤:
-
根据JSBundle header得到JSBundle类型来执行对应的js上下文。
evaluateScript
是由jshelps
组件封装的,最终调用到JavaScriptCore的方法JSEvaluateScript
。jschelps
主要封装了JavaScriptCore的相关函数,以及JSStringRef
的C++对象封装String
类,JSObjectRef
的C++对象封装Object
类,JSValueRef
的C++对象封装Value
类等。 -
调用
flush()
native调用js
flushedQueue
方法,返回js端待调用方法队列,然后native执行,清空该队列
至此,Native模块生成和相关准备工作完成,模块配置存放在ModuleRegistry
类中。
JS模块
本地jscore运行时,当js需要调用到native模块的时候,通过nativeModuleProxy
执行native所注入方法,返回对应的模块信息,而当远程调试模式时,native向global.__fbBatchedBridgeConfig
注入了所有模块列表信息,同样是由native端生成的,如下:
type ModuleConfig = [
string, /* name */
?Object, /* constants */
Array<string>, /* functions */
Array<number>, /* promise method IDs */
Array<number>, /* sync method IDs */
];
在JS中同样也存在供native调用的模块,node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js
中,模块保存在_lazyCallableModules
中。
JS模块生成
registerCallableModule(name: string, module: Object) {
this._lazyCallableModules[name] = () => module;
}
registerLazyCallableModule(name: string, factory: void => Object) {
let module: Object;
let getValue: ?(void => Object) = factory;
this._lazyCallableModules[name] = () => {
if (getValue) {
module = getValue();
getValue = null;
}
return module;
};
}
通过外部注册,填充_lazyCallableModules
数组,
Native 调用 JS
在RN里,封装了底层细节,外部暴露出的是通过RCTCxxBridge
方法enqueueJSCall:method:args:completion
调用,如native向js发送时间消息的方法sendEventWithName:body
实现就是调用该方法。该方法实现如下:
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
if (!self.valid) {
return;
}
/**
* AnyThread
*/
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil);
RCTProfileBeginFlowEvent();
[self _runAfterLoad:^{
RCTProfileEndFlowEvent();
if (self->_reactInstance) {
self->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
[RCTConvert folly_dynamic:args ?: @[]]);
// ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
// the block is invoked after callJSFunction
if (completion) {
if (self->_jsMessageThread) {
self->_jsMessageThread->runOnQueue(completion);
} else {
RCTLogWarn(@"Can't invoke completion without messageThread");
}
}
}
}];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
主要通过调用Instance
类的callsJSFunction
方法,最终调用到JSCExecutor::callFunction
方法
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
SystraceSection s("JSCExecutor::callFunction");
// This weird pattern is because Value is not default constructible.
// The lambda is inlined, so there's no overhead.
auto result = [&] {
try {
if (!m_callFunctionReturnResultAndFlushedQueueJS) {
bindBridge();
}
return m_callFunctionReturnFlushedQueueJS->callAsFunction({
Value(m_context, String::createExpectingAscii(m_context, moduleId)),
Value(m_context, String::createExpectingAscii(m_context, methodId)),
Value::fromDynamic(m_context, std::move(arguments))
});
} catch (...) {
std::throw_with_nested(
std::runtime_error("Error calling " + moduleId + "." + methodId));
}
}();
callNativeModules(std::move(result));
}
callFunction
方法先执行js端方法callFunctionReturnFlushedQueue
(在MessageQueue.js文件中),返回js端消息队列,然后native解析队列,即调用callNativeModules
,这个过程在下文JS调用Native
有分析。
总体来说还是使用JSCHelpers
中封装的C++方法evaluateScript(JSContextRef, JSStringRef, JSStringRef)
,在常驻线程来执行js语句,返回结果native解析。
JS调用Native
在node_modules/react-native/Libraries/BatchedBridge/NativeModules.js
文件中:
let NativeModules : {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else {
...
}
module.exports = NativeModules;
本地JavascriptCore执行时,nativeModuleProxy
全局函数在JSCExecutor
构造时,通过installGlobalProxy
方法注入了,这里的else
分支是浏览器远程调试走的。当取nativeModuleProxy
属性,如执行const RCTAppState = NativeModules.AppState;
,JSObjectGetPropertyCallback
回调在C++端被触发,调用到JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName)
方法,该方法通过JSCNativeModules
的getModule
方法拿到native对应配置,如第一节模块配置中拿到对应的配置表。
js端也有 有BatchedBridge
概念,node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js
中,const BatchedBridge = new MessageQueue();
,BatchedBridge
对象实际上是MessageQueue
的实例,转到当前目录下的MessageQueue.js
文件。
js需要调用native方法的时候,调用enqueueNativeCall
函数,比如js端执行方法:
UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload)
这段代码是在ReactNativeStack-dev.js
中,用于js端通告native创建视图,UIManager
实际是NativeModules
对象,本地JavacriptCore运行时,NativeModules
对象方法在native的JSCExecutor::getNativeModule
方法中通过调用js方法global.__fbGenNativeModule
建立,global.__fbGenNativeModule
即指向genModule
方法对象,genModule
方法中调用genMethod
,genMethod
中持有闭包,将native方法调用通过BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
方法加入队列处理,故上述方法调用最终通过enqueueNativeCall
调用。
enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
if (onFail || onSucc) {
if (__DEV__) {
...
}
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
// to indicate fail (0) or success (1)
onFail && params.push(this._callID << 1);
onSucc && params.push((this._callID << 1) | 1);
this._successCallbacks[this._callID] = onSucc;
this._failureCallbacks[this._callID] = onFail;
}
if (__DEV__) {
...
}
this._callID++;
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
if (__DEV__) {
...
}
this._queue[PARAMS].push(params);
const now = new Date().getTime();
if (global.nativeFlushQueueImmediate &&
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
this._inCall === 0)) {
var queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
if (__DEV__ && this.__spy && isFinite(moduleID)) {
...
} else if (this.__spy) {
this.__spy({type: TO_NATIVE, module: moduleID + '', method: methodID, args: params});
}
}
enqueueNativeCall
向_queue
依次插入moduleID
methodID
params
,flushedQueue
方法会把当前的_callID
插入到_queue
最后,紧接着判断相邻两次flushQueue
时间超过MIN_TIME_BETWEEN_FLUSHES_MS
即5ms,或者当前没有正在处理的方法,则执行全局nativeFlushQueueImmediate
函数。nativeFlushQueueImmediate
函数传入_queue
参数,它在native端之前通过installNativeHook
注入了,js端调用后native端收到函数回调,最终对应执行JSCExecutor
类的nativeFlushQueueImmediate
方法,该方法最终调用到JsToNativeBridge
的callNativeModules
方法,callNativeModules
解析出js透传的参数_queue
,然后动态调用方法。
for (auto& call : parseMethodCalls(std::move(calls))) {
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
m_registry是ModuleRegistry
的实例
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
folly::to<std::string>("moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
invoke
方法即以反射去动态执行方法,具体执行方法各平台各自实现,iOS上实际执行的类是RCTNativeModule
,它的invoke
方法在参数对应的module
指定线程队列执行invokeInner
方法,然后经过转换参数等操作,最终调用到RCTModuleMethod
类的invokeWithBridge:module:arguments
方法,通过NSInvocation
的invokeWithTarget
方法实现动态调用,并返回调用结果,中间经过了处理method name, methodSignature等过程,此处代码可浏览RCTModuleMethod
的类实现。另外在processMethodSignature
方法中,将cbID
和返回结果暂存,调用成功通过JSCExecutor
的m_invokeCallbackAndReturnFlushedQueueJS
属性 ,调用到js里MessageQueue
类的invokeCallbackAndReturnFlushedQueue
方法,js端拿到返回值,js调用native的闭环形成。
那么还有一个问题,js只是把消息加入了队列,js什么时候去让native去取js的消息队列处理?
-
js端超时机制
需要注意的是,远程调试模式并没有超时机制,
global.nativeFlushQueueImmediate
始终是 undefined的。每次消息入队的时候,会检查距离上次队列清空完成是否超过5ms,超过则调用
nativeFlushQueueImmediate
清空队列,native注册回调被调用,否则立即入队,由于js是单线程的,5ms内也不会积压很多消息,所以不用担心处理效率问题。 -
native主动调用
native调用js方法,native调用
enqueueJSCall:method:args:completion
方法会取到js消息队列,其实包含folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional<Object> m_callFunctionReturnFlushedQueueJS; folly::Optional<Object> m_flushedQueueJS; folly::Optional<Object> m_callFunctionReturnResultAndFlushedQueueJS;
处理方法都会返回js消息队列,即native每次调用js,都会主动去取js队列,比如事件消息、timer等。
综上所述,js调用native实际上是有两种机制的:
- native向jscontext的 global注入全局对象,同时注册相应的回调,如
nativeFlushQueueImmediate
,js函数被调用,对应native回调被响应 - js组成消息队列,native调用
flushedQueue
主动去取
第一种是JSPatch
所采用的,不过它注册的回调是一个block, 第二种机制是最复杂的,对于模块,需要两端维护一份配置表,但是最高效的,js方需要执行native方法,仅需传递moduleId
methodId
arguments
必要参数给native,而方法真正执行是在native方异步执行的,返回结果异步返回给js方,如果换成方式1,native方法在jscontext同步执行,明显影响效率,而且 当短时间内有很多条消息,JS并不会去频繁调用native,会在5ms内去累积消息,然后发送给native。