当前位置:

ReactNative源码——通信机制(1/2)

访客 2024-04-23 679 0

本文通过分析源码,逐步解析ReactNative中JStoNative的通信机制。

本文同时发表于我的个人博客

Overview


本文详细分析了JStoNative的调用过程,包括:ReactNative的初始化、nativemodule注册、JS获取nativemodule信息、JS调用nativemodule。

本文分析的源码基于ReactNative0.47,为了叙述方便后文将ReactNative简称为RN。另外,为了精减篇幅聚焦重点,文中所列源码均做过简化,删除非关键代码。由于本文具体分析的是iOS侧的实现,故后文所有内容都是针对iOS平台(其中有一大部分是共用的)。

准备工作


阅读源码大概有两类目的,一是想通过源码了解某个实现细节;二是想了解其实现的整体结构、原理。对于第一种情形可以直接深入源码,快速抓住关注点;而后者如果也一股脑扎入源码,很容易陷入各种实现细节而迷失方向(尤其是大型源码)。此时,应先了解代码整体结构,抓住关键路径,必要时辅以类图。

本文讨论的RN通信机制是整个RN的核心,贯穿始末,涉及Objective-C/JavaCJavaScript间的交互。因此,在深入分析前有必要先了解一下RN整体结构以及关键类的类图。

RN最大的优势在于跨平台、热更新。因此,RN库本身也需要在多个平台(iOS、Android)上运行,从上图可知,在RN中CJS部分的实现为多平台共用,在此基础上再分化为各平台实现。其中,iOS平台特有的实现(Objective-CC)主要集中在React下,以C语言实现的共有部分在ReactCommon下:

JS侧与本文关系紧密的内容主要集中在:

下的BatchedBridge.jsMessageQueue.js以及NativeModules.js中。

我们先大概了解几个关键类及其间的关系,对整体结构有个大致印象:

如上图:

  • RCTBridgeRCTCxxBridge属于iOS平台特有,前者是RN对业务层接口(图中其他类都属于内部类,业务层无感知),具体工作在其子类RCTCxxBridge中完成;
  • 整个RN的核心在跨平台的C层,其中很多类的功能从其名称即可略知一二,后文也会有详细的描述;
  • 在RN中肯定少不了JS的支持,从上图可知,JS与Native的通信发生在JavaScriptC间,这也是本文分析的重点。

说到通信,无外乎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宏,相当定义了loadmoduleName方法。正是在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];}
  • 上述代码第8RCTGetModuleClasses()即是获取通过RCTRegisterModule注册的module类(即所有曝露给JS的类);
  • 通过RCTRegisterModule注册的module默认使用init方法进行初始化,若某个module的初始化需要参数,可通过RCTBridgeDelegate->extraModulesForBridgemoduleProvider提供已初始化的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_executorJSExecutor类型的指针,从上文可知JSExecutor是个C抽象类,m_executor实际指向JSCExecutor的实例,作为JS的引擎,无论是NativetoJS还是JStoNative最终都需要该类来处理,后面我们会逐一分析;
  • m_delegateJsToNativeBridge类型的指针,顾名思义,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
  • 初始化了相关的类,如:NativeToJsBridgeJsToNativeBridge以及JSCExecutor等。

现在可以说是『万事具备,只欠东风』——由JS发起对Native的调用了。下面我们沿着调用路径继续往下分析。

JSNativeModules

这小节我们在JS中^-^

//JSimport{NativeModules}from'react-native';varCalendarManager=NativeModules.CalendarManager;CalendarManager.addEvent('BirthdayParty','4PrivetDrive,Surrey');

回到之前那个例子,其调用的是CalendarManagermodule的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&&params,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。

发表评论

  • 评论列表
还没有人评论,快来抢沙发吧~