本文通过分析源码,逐步解析ReactNative中JStoNative的通信机制。
本文同时发表于我的个人博客
Overview
本文详细分析了JStoNative的调用过程,包括:ReactNative的初始化、nativemodule注册、JS获取nativemodule信息、JS调用nativemodule。
本文分析的源码基于ReactNative0.47,为了叙述方便后文将ReactNative简称为RN。另外,为了精减篇幅聚焦重点,文中所列源码均做过简化,删除非关键代码。由于本文具体分析的是iOS侧的实现,故后文所有内容都是针对iOS平台(其中有一大部分是共用的)。
准备工作
阅读源码大概有两类目的,一是想通过源码了解某个实现细节;二是想了解其实现的整体结构、原理。对于第一种情形可以直接深入源码,快速抓住关注点;而后者如果也一股脑扎入源码,很容易陷入各种实现细节而迷失方向(尤其是大型源码)。此时,应先了解代码整体结构,抓住关键路径,必要时辅以类图。
本文讨论的RN通信机制是整个RN的核心,贯穿始末,涉及Objective-C
/Java
、C
、JavaScript
间的交互。因此,在深入分析前有必要先了解一下RN整体结构以及关键类的类图。
RN最大的优势在于跨平台、热更新。因此,RN库本身也需要在多个平台(iOS、Android)上运行,从上图可知,在RN中C
与JS
部分的实现为多平台共用,在此基础上再分化为各平台实现。其中,iOS平台特有的实现(Objective-C
、C
)主要集中在React下,以C
语言实现的共有部分在ReactCommon下:
JS侧与本文关系紧密的内容主要集中在:
下的BatchedBridge.js
、MessageQueue.js
以及NativeModules.js
中。
我们先大概了解几个关键类及其间的关系,对整体结构有个大致印象:
如上图:
RCTBridge
与RCTCxxBridge
属于iOS平台特有,前者是RN对业务层接口(图中其他类都属于内部类,业务层无感知),具体工作在其子类RCTCxxBridge
中完成;- 整个RN的核心在跨平台的C层,其中很多类的功能从其名称即可略知一二,后文也会有详细的描述;
- 在RN中肯定少不了JS的支持,从上图可知,JS与Native的通信发生在
JavaScript
与C
间,这也是本文分析的重点。
说到通信,无外乎JStoNative、NativetoJS,本篇我们重点分析JStoNative。
JavaScript—>Native
通过Apple推出的JavaScriptCore
,要实现JStoNative的通信并非难事,主要有两条途径:
block
——在RN这样复杂的应用场景中block
显得有些力不从心;JSExport
协议——通过实现该协议可以向JS曝露Native接口(但不具备跨平台能力)。
因此,RN并没有使用JavaScriptCore
提供的这两种方式,而是自己实现了一套通信机制。我们先从一个简单的例子入手:CalendarManager
封装了iOS平台的日历控件,供JS调用。
//CalendarManager.h#import<React/RCTBridgeModule.h>@interfaceCalendarManager:NSObject<RCTBridgeModule>@end
//CalendarManager.m@implementationCalendarManager//ToexportamodulenamedCalendarManagerRCT_EXPORT_MODULE();RCT_EXPORT_METHOD(addEvent:(NSString*)namelocation:(NSString*)location){RCTLogInfo(@"Pretendingtocreateanevent%@at%@",name,location);}@end
我们知道,要将Nativemodule(类、接口)曝露给JS,module需要实现RCTBridgeModule
协议,并且在实现中要插入RCT_EXPORT_MODULE
宏。具体曝露的方法也需要通过RCT_EXPORT_METHOD
宏定义。
//JSimport{NativeModules}from'react-native';varCalendarManager=NativeModules.CalendarManager;CalendarManager.addEvent('BirthdayParty','4PrivetDrive,Surrey');
此时,在JS中就可以通过NativeModules.CalendarManager.addEvent(...)
方式调用Native接口了。下面将对这一过程逐一分析,先来了解上面提到的两个关键的宏:
RCT_EXPORT_MODULE
#defineRCT_EXPORT_MODULE(js_name)\RCT_EXTERNvoidRCTRegisterModule(Class);\(NSString*)moduleName{return@#js_name;}\(void)load{RCTRegisterModule(self);}
可以看到,添加RCT_EXPORT_MODULE
宏,相当定义了load
、moduleName
方法。正是在load
方法中调用了RCTRegisterModule
方法注册Module(会影响App启动速度^_^)。
voidRCTRegisterModule(ClassmoduleClass){staticdispatch_once_tonceToken;dispatch_once(&onceToken,^{RCTModuleClasses=[NSMutableArraynew];});//Registermodule[RCTModuleClassesaddObject:moduleClass];}
RCTRegisterModule
只是简单收集所有需要曝露给JS的类。
RCT_EXPORT_METHOD
曝露给JS的接口需要通过RCT_EXPORT_METHOD
宏来定义,上文中:
RCT_EXPORT_METHOD(addEvent:(NSString*)namelocation:(NSString*)location){RCTLogInfo(@"Pretendingtocreateanevent%@at%@",name,location);}
最终展开后的样子(由于篇幅关系具体的展开过程不作描述):
(NSArray*)__rct_export__531{return@[@"",@"addEvent:(NSString*)namelocation:(NSString*)location",@NO];}-(void)addEvent:(NSString*)namelocation:(NSString*)location{RCTLogInfo(@"Pretendingtocreateanevent%@at%@",name,location);}
可以看到通过宏添加了一个新方法,其命名规则:__rct_export__
__LINE__
__COUNTER__
。同时注意到,所有曝露给JS的方法返回值都是void
,要返回结果时,需通过callback方式实现。
优雅、巧妙地使用宏充满技巧。
上面介绍的RCT_EXPORT_MODULE
以及RCT_EXPORT_METHOD
宏属于编译阶段的处理,下面我们从执行角度一步一步进行分析。
上图是RN初始化过程的时序图,我们将沿着图中的步骤逐步分析。
注册NativeModule
通过上述坐标,最终定位到RCTCxxBridge._initModulesWithDispatchGroup
:
-(void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup{NSMutableArray<Class>*moduleClassesByID=[NSMutableArraynew];NSMutableArray<RCTModuleData*>*moduleDataByID=[NSMutableArraynew];NSMutableDictionary<NSString*,RCTModuleData*>*moduleDataByName=[NSMutableDictionarynew];//SetupmoduleDataforautomatically-exportedmodulesfor(ClassmoduleClassinRCTGetModuleClasses()){NSString*moduleName=RCTBridgeModuleNameForClass(moduleClass);moduleData=[[RCTModuleDataalloc]initWithModuleClass:moduleClassbridge:self];moduleDataByName[moduleName]=moduleData;[moduleClassesByIDaddObject:moduleClass];[moduleDataByIDaddObject:moduleData];}//Storemodules_moduleDataByID=[moduleDataByIDcopy];_moduleDataByName=[moduleDataByNamecopy];_moduleClassesByID=[moduleClassesByIDcopy];}
- 上述代码第
8
行RCTGetModuleClasses()
即是获取通过RCTRegisterModule
注册的module类(即所有曝露给JS的类); - 通过
RCTRegisterModule
注册的module默认使用init
方法进行初始化,若某个module的初始化需要参数,可通过RCTBridgeDelegate
->extraModulesForBridge
或moduleProvider
提供已初始化的module实例。
至此,所有需要曝露给JS的module都已注册完成,并以RCTModuleData
格式存储在RCTCxxBridge
中。
大部分module都是懒加载,只有那些需要在主线程完成初始化以及有常量需要导出的module才会在注册时实例化。
Instance
坐标定位到RCTCxxBridge._initializeBridge
方法中:
-(void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory{if(_reactInstance){_reactInstance->initializeBridge(std::unique_ptr<RCTInstanceCallback>(newRCTInstanceCallback(self)),executorFactory,_jsMessageThread,[self_buildModuleRegistry]);}}
_initializeBridge
方法做的最重要的事情就是初始化Instance
实例_reactInstance
,此过程将所有曝露给JS的module由RCTModuleData
格式转化为ModuleRegistry
格式传入Instance
。
-(std::shared_ptr<ModuleRegistry>)_buildModuleRegistry{autoregistry=std::make_shared<ModuleRegistry>(createNativeModules(_moduleDataByID,self,_reactInstance));returnregistry;}
此时,有必要介绍与NativeModule有关的几个类:
JSCNativeModules
——C
类,在JSCExecutor
中使用;ModuleRegistry
——C
类,是NativeModule
的集合;NativeModule
——C
抽象类,定义了与NativeModule有关的接口;RCTNativeModule
——实现了NativeModule
中定义的接口;RCTModuleData
——OC
类,是存储曝露的moudle的数据结构;RCTBridgeMethod
——OC
类,是存储曝露给JS接口(方法)的数据结构。
Instance
是一个中转类,没有做太多的事情,我们继续向前。
NativeToJsBridge
此时,来到Instance::initializeBridge
->NativeToJsBridge::NativeToJsBridge
,很明显NativeToJsBridge
是NativetoJS的桥接。所有从Native到JS的调用都是从NativeToJsBridge
中的接口发出去的。在其构造函数中会初始化两个成员变量:
m_executor
—JSExecutor
类型的指针,从上文可知JSExecutor
是个C
抽象类,m_executor
实际指向JSCExecutor
的实例,作为JS的引擎,无论是NativetoJS还是JStoNative最终都需要该类来处理,后面我们会逐一分析;m_delegate
—JsToNativeBridge
类型的指针,顾名思义,JStoNative的桥接,该成员变量仅用于初始化JSCExecutor
实例。
JSCExecutor
坐标定位到NativeToJsBridge::NativeToJsBridge
->JSCExecutor::JSCExecutor
:
JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate>delegate,std::shared_ptr<MessageQueueThread>messageQueueThread,constfolly::dynamic&jscConfig)throw(JSException):m_delegate(delegate),m_messageQueueThread(messageQueueThread),m_nativeModules(delegate?delegate->getModuleRegistry():nullptr),m_jscConfig(jscConfig){initOnJSVMThread();installGlobalProxy(m_context,"nativeModuleProxy",exceptionWrapMethod<&JSCExecutor::getNativeModule>());}
JSCExecutor
的构造函数做了一条非常重要的事情:在JSContext中设置了一个全局代理nativeModuleProxy
,其最终指向JSCExecutor
类的getNativeModule
方法。至于说nativeModuleProxy
为什么非常重要,留个悬念,后文再解,我们先看看installGlobalProxy
。
voidinstallGlobalProxy(JSGlobalContextRefctx,constchar*name,JSObjectGetPropertyCallbackcallback){JSClassDefinitionproxyClassDefintion=kJSClassDefinitionEmpty;proxyClassDefintion.attributes|=kJSClassAttributeNoAutomaticPrototype;proxyClassDefintion.getProperty=callback;constboolisCustomJSC=isCustomJSCPtr(ctx);JSClassRefproxyClass=JSC_JSClassCreate(isCustomJSC,&proxyClassDefintion);JSObjectRefproxyObj=JSC_JSObjectMake(ctx,proxyClass,nullptr);JSC_JSClassRelease(isCustomJSC,proxyClass);Object::getGlobalObject(ctx).setProperty(name,Value(ctx,proxyObj));}
结合上面两段代码总结一下:
nativeModuleProxy
在JSContext中是一个具有JSObjectGetPropertyCallback
属性的对象(object);JSObjectGetPropertyCallback
非常神奇,其特点是在JS中访问对象属性(object.propertyName)时会触发callback;nativeModuleProxy
对应的callback最终会调用JSCExecutor::getNativeModule
。
在JSCExecutor
构造函数中还调用了initOnJSVMThread
方法:
voidJSCExecutor::initOnJSVMThread(){installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");}
initOnJSVMThread
方法中有个值得关注的点:在JSContext中hook了JSCExecutor::nativeFlushQueueImmediate
。简单讲,hook后,在JS中调用global.nativeFlushQueueImmediate(...)
,实际调用Native的JSCExecutor::nativeFlushQueueImmediate
方法。另外,在构造函数中也将nativemodule注册信息转换为JSCNativeModules
格式存储了下来。
JsToNativeBridge
坐标定位到NativeToJsBridge::NativeToJsBridge
->JsToNativeBridge::JsToNativeBridge
:
JsToNativeBridge(std::shared_ptr<ModuleRegistry>registry,std::shared_ptr<InstanceCallback>callback):m_registry(registry),m_callback(callback){}
沿着调用链一路下来,最终module注册信息传入JsToNativeBridge
,随后JStoNative的调用将用到这份信息。至此,RN初始化过程中与本文讨论的通信机制相关的内容基本结束。总结下来大概有几点:
- 收集了所有曝露给JS的module(也可称之为生成了一份nativemodule注册表);
- 在JSContext中设置了
nativeModuleProxy
以及nativeFlushQueueImmediate
; - 初始化了相关的类,如:
NativeToJsBridge
、JsToNativeBridge
以及JSCExecutor
等。
现在可以说是『万事具备,只欠东风』——由JS发起对Native的调用了。下面我们沿着调用路径继续往下分析。
JSNativeModules
这小节我们在JS中^-^
//JSimport{NativeModules}from'react-native';varCalendarManager=NativeModules.CalendarManager;CalendarManager.addEvent('BirthdayParty','4PrivetDrive,Surrey');
回到之前那个例子,其调用的是CalendarManager
module的addEvent:location:
方法。展开上面的调用,最终的形式是:
NativeModules.CalendarManager.addEvent('BirthdayParty','4PrivetDrive,Surrey')
NativeModules
定义在node_modules->react-native->Libraries->BatchedBridge->NativeModules.js。
letNativeModules:{[moduleName:string]:Object}={};if(global.nativeModuleProxy){NativeModules=global.nativeModuleProxy;}
nativeModuleProxy
正是上文提到的,由Native注入JS的具有JSObjectGetPropertyCallback
属性的object。因此,JS中的NativeModules.CalendarManager
(取属性值)等价于调用:
JSCExecutor::getNativeModule(NativeModules,`CalendarManager`)
格式化:JS中NativeModules.moduleName
等价于Native的:
JSCExecutor::getNativeModule(NativeModules,`moduleName`)
也这是为什么说nativeModuleProxy
非常重要的原因,所有从JStoNative的调用都需要其作为中间代理。此时,又要切入Native环境了。
ModuleRegistry::getConfig
由于接下来马上要用到nativemodule的信息,在此先提前准备好。定位到ModuleRegistry::getConfig
,通过上文可知在ModuleRegistry
中存储了nativemodule的信息,
folly::Optional<ModuleConfig>ModuleRegistry::getConfig(conststd::string&name){autoit=modulesByName_.find(name);NativeModule*module=modules_[it->second].get();//stringname,objectconstants,arraymethodNames(methodIdisindex),[arraypromiseMethodIds],[arraysyncMethodIds]folly::dynamicconfig=folly::dynamic::array(name);std::vector<MethodDescriptor>methods=module->getMethods();for(auto&descriptor:methods){methodNames.push_back(std::move(descriptor.name));}if(!methodNames.empty()){config.push_back(std::move(methodNames));}returnModuleConfig({it->second,config});}
getConfig
方法最终将nativemodule信息组装成一个数组,其格式:[modulename,module导出的constants,[methodNames],[promiseMethodIds],[syncMethodIds]]下面是RCTWebSocketModule
导出的信息:
在这过程中RCTModuleData.methods
起到关键作用:ModuleRegistry::getConfig->RCTNativeModule::getMethods->RCTModuleData.methods
//RCTModuleData.m-(NSArray<id<RCTBridgeMethod>>*)methods{if(!_methods){NSMutableArray<id<RCTBridgeMethod>>*moduleMethods=[NSMutableArraynew];unsignedintmethodCount;Classcls=_moduleClass;while(cls&&cls!=[NSObjectclass]&&cls!=[NSProxyclass]){Method*methods=class_copyMethodList(object_getClass(cls),&methodCount);for(unsignedinti=0;i<methodCount;i){Methodmethod=methods[i];SELselector=method_getName(method);if([NSStringFromSelector(selector)hasPrefix:@"__rct_export__"]){IMPimp=method_getImplementation(method);NSArray*entries=((NSArray*(*)(id,SEL))imp)(_moduleClass,selector);id<RCTBridgeMethod>moduleMethod=[[RCTModuleMethodalloc]initWithMethodSignature:entries[1]JSMethodName:entries[0]isSync:((NSNumber*)entries[2]).boolValuemoduleClass:_moduleClass];[moduleMethodsaddObject:moduleMethod];}}}}return_methods;}
__rct_export__
是不是很熟悉,RCTModuleData.methods
会遍历所有以__rct_export__
为前缀的方法并执行以导出曝露给JS的接口。
JSCNativeModules::createModule
书接前文,JStoNativeNativeModules.moduleName(JS)->JSCExecutor::getNativeModule->JSCNativeModules::getModule->JSCNativeModules::createModule沿调用链来到JSCNativeModules::createModule
:
folly::Optional<Object>JSCNativeModules::createModule(conststd::string&name,JSContextRefcontext){if(!m_genNativeModuleJS){autoglobal=Object::getGlobalObject(context);m_genNativeModuleJS=global.getProperty("__fbGenNativeModule").asObject();m_genNativeModuleJS->makeProtected();}autoresult=m_moduleRegistry->getConfig(name);ValuemoduleInfo=m_genNativeModuleJS->callAsFunction({Value::fromDynamic(context,result->config),Value::makeNumber(context,result->index)});folly::Optional<Object>module(moduleInfo.asObject().getProperty("module").asObject());returnmodule;}
在createModule
方法中,通过ModuleRegistry::getConfig
(第8
行)拿到了要调用的nativemodule的信息(包括导出的常量、曝露的接口等)。同时获取了JSContext中名为__fbGenNativeModule
的属性(第4
行),从名称可知其作用是生成JS端的NativeModule信息。__fbGenNativeModule
定义在NativeModules.js
中
//exportthismethodasaglobalsowecancallitfromnativeglobal.__fbGenNativeModule=genModule;
NativeModules.genModule
NativeModules.moduleName(JS)->JSCExecutor::getNativeModule->JSCNativeModules::getModule->JSCNativeModules::createModule->NativeModules.genModule(JS)
functiongenModule(config:?ModuleConfig,moduleID:number):?{name:string,module?:Object}{const[moduleName,constants,methods,promiseMethods,syncMethods]=config;constmodule={};methods&&methods.forEach((methodName,methodID)=>{constmethodType=isPromise?'promise':isSync?'sync':'async';module[methodName]=genMethod(moduleID,methodID,methodType);});Object.assign(module,constants);return{name:moduleName,module};}
genModule
会遍历传入的methods
数组,分别调用genMethod
生成JS侧的方法:
functiongenMethod(moduleID:number,methodID:number,type:MethodType){letfn=null;fn=function(...args:Array<any>){args=args.slice(0,args.length-callbackCount);BatchedBridge.enqueueNativeCall(moduleID,methodID,args,onFail,onSuccess);};fn.type=type;returnfn;}
genMethod
生成的函数非常简单,只是将对native的调用信息入队。至此,由NativeModules.moduleName
发起的调用链终于结束,最终返回结果:以methodName
为key,genMethod
生成的function为value的JSobject。即NativeModules.moduleName
等价于{methodName:fn}
。
JS中的moduleID、methodID对应nativemoudle注册表中的数组下标。
当然,事情并没有结束,继续扩展NativeModules.moduleName.methodName(args)
。通过上述分析,可知:NativeModules.moduleName.methodName(args)
等价于:BatchedBridge.enqueueNativeCall(moduleID,methodID,args,onFail,onSuccess)
MessageQueue.enqueueNativeCall
NativeModules.moduleName.methodName
->BatchedBridge.enqueueNativeCall
constMessageQueue=require('MessageQueue');constBatchedBridge=newMessageQueue();
可以看到BatchedBridge
就是MessageQueue
enqueueNativeCall(moduleID:number,methodID:number,params:Array<any>,onFail:?Function,onSucc:?Function){this._queue[MODULE_IDS].push(moduleID);this._queue[METHOD_IDS].push(methodID);this._queue[PARAMS].push(params);constnow=newDate().getTime();if(global.nativeFlushQueueImmediate&&(now-this._lastFlush>=MIN_TIME_BETWEEN_FLUSHES_MS||this._inCall===0)){varqueue=this._queue;this._queue=[[],[],[],this._callID];this._lastFlush=now;global.nativeFlushQueueImmediate(queue);}}
在enqueueNativeCall
方法中将调用信息(moduleID、methodID、params等入队),若离上一次flushqueue的时间超过5ms(MIN_TIME_BETWEEN_FLUSHES_MS
)则立即flushqueue(出于性能考虑)。对nativeFlushQueueImmediate
是否还有影像?(在JSCExecutor
构造函数中讲过了^_^)global.nativeFlushQueueImmediate(queue)
等价于JSCExecutor::nativeFlushQueueImmediate(queue)
JsToNativeBridge::callNativeModules
JSCExecutor::nativeFlushQueueImmediate
->JSCExecutor::flushQueueImmediate
->JsToNativeBridge::callNativeModules
沿着调用链,定位到JsToNativeBridge::callNativeModules
。
voidcallNativeModules(JSExecutor&executor,folly::dynamic&&calls,boolisEndOfBatch)override{for(auto&call:parseMethodCalls(std::move(calls))){m_registry->callNativeMethod(call.moduleId,call.methodId,std::move(call.arguments),call.callId);}}`callNativeModules`会逐个解析从JS传过来的callqueue中的每个调用。```cppvoidModuleRegistry::callNativeMethod(unsignedintmoduleId,unsignedintmethodId,folly::dynamic&¶ms,intcallId){modules_[moduleId]->invoke(methodId,std::move(params),callId);}
沿着调用链后面还有一些细节问题,由于篇幅关系,不再展开,主要如下:
最终在RCTModuleMethod.invokeWithBridge
中执行了调用[_invocationinvokeWithTarget:module];
至此,从JStoNative的调用结束!
小结
JStoNative的调用时序图:
整个过程大概分为两个阶段:
- NativeModules.moduleName—该过程主要是获取nativemodule的信息(moduleID、methodID),最终封装为JSobject({methodName:fn});
- NativeModules.moduleName.methodName(params)—执行调用。
今天就到这了,下篇我们将分析NativetoJS。