ReactNative 源码解析 (一)
2020/6/13 23:25:45
本文主要是介绍ReactNative 源码解析 (一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
虽然在跨平台这块谷歌搞了 Flutter 出来,但是从目前的生态和大厂应用上来讲,ReactNative 优势更明显些。虽然这是一个 15 年就推出的跨平台框架,但是这几年 ReactNative 团队也一直在对它进行优化,尤其在大家平时诟病的 Bridge 那块,做了很大的调整,代码也基本上从开始的 OC 形式直接改成了 cpp 的桥接,基本上达到了和 JavaScript 的无缝衔接,另外一点这样也可以做到安卓 iOS 双端代码统一。个人觉得学习它的设计思想和设计模式要远远大于它未来的生态,所以基于 0.61.0 版本,总结了一下 ReactNative 的源码,本篇作为开篇先从 ReactNative 的业务代码开始,一步步纵深探究下其内部原理。
开篇先甩张图,看一下它的大体结构,ReactNative 架构分层还是比较明显的,最上层业务层,是我们日常编写的业务组件,其中包括用 Native 编写的和用 JS 编写。中间是框架层,Bridge 部分除了 RCTBridge 和 RCTCxxBridge 之外,其余都是由 cpp 构建。最下面是 iOS 的系统库,大名鼎鼎的 JavaScriptCore,cpp 和 JSCore 进行无缝衔接,让前端的小伙伴可以直接上手 Native 开发,具体细节后面都会说,这里只是先简单的了解一下 ReactNative 的架构组成。从ReactNative的初始化开始
根据 ReactNative 官方文档描述,当我们想要一个 ViewController 成为 RN 的容器的话,具体实现应该是这样的:
- (void)viewDidLoad { [super viewDidLoad]; NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"]; // 初始化rootView RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName: @"RNHighScores" initialProperties:nil launchOptions: nil]; self.view = rootView; } 复制代码
- 根据本地 JSBundle 的 url 初始化一个 RootView。
- 再把这个 rootView 赋值给 VC 的 View。
在 RCTRootView 初始化方法里面还会创建一个 RCTBridge,这个 RCTBridge 就是 ReactNative 框架层中的 Bridge 部分,也是极其重要的部分
// RCTRootView.m - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions { RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions]; return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; } 复制代码
// RCTBridge.m - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleListProvider)block launchOptions:(NSDictionary *)launchOptions { return [self initWithDelegate:nil bundleURL:bundleURL moduleProvider:block launchOptions:launchOptions]; } - (void)setUp { ... Class bridgeClass = self.bridgeClass; ... self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self]; [self.batchedBridge start]; } - (Class)bridgeClass { return [RCTCxxBridge class]; } 复制代码
在 RCTRootView.m
内部最后会走到 RCTCxxBridge 的 -start
方法。
// RCTCxxBridge.mm - (void)start { // 1.创建一个常驻的线程 _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil]; [_jsThread start]; dispatch_group_t prepareBridge = dispatch_group_create(); // 2.加载原生模块 [self registerExtraModules]; (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]; [self registerExtraLazyModules]; // 3.创建Instance实例 _reactInstance.reset(new Instance); __weak RCTCxxBridge *weakSelf = self; // 4.JSExecutorFactory工厂,生产环境它的真实类型是JSIExecutor // Prepare executor factory (shared_ptr for copy into block) std::shared_ptr<JSExecutorFactory> executorFactory; if (!self.executorClass) { if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) { id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate; executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self]; } if (!executorFactory) { executorFactory = std::make_shared<JSCExecutorFactory>(nullptr); } } else { id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass]; executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) { if (error) { [weakSelf handleError:error]; } })); } // 5.初始化底层Bridge dispatch_group_enter(prepareBridge); [self ensureOnJavaScriptThread:^{ [weakSelf _initializeBridge:executorFactory]; dispatch_group_leave(prepareBridge); }]; // 6.加载JS dispatch_group_enter(prepareBridge); __block NSData *sourceCode; [self loadSource:^(NSError *error, RCTSource *source) { if (error) { [weakSelf handleError:error]; } sourceCode = source.data; dispatch_group_leave(prepareBridge); } onProgress:^(RCTLoadingProgress *progressData) { ... }]; // 7.执行JS 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]; } }); RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } 复制代码
-start
方法贯穿了整个 ReactNative 的启动流程,整个 -start
方法主要做了以下事情:
- 创建了个 JS 线程 _jsThread,并在内部开了个 runloop 让它长驻,初始化底层 Bridge、执行 JS 代码都是在这个线程里执行。
- 加载原生模块,将所有暴漏给JS使用的原生 RCTBridgeModule 初始化,每个原生模块都被初始化为 RCTModuleData,并将其仍到数组里。
- 初始化 Instance 实例,Instance 是公共层 Bridge 的最上层,数据公共部分的入口,到这里已经进入了 cpp 文件。Instance 是对底层 Bridge 的一层包装,提供了一些执行Call JS 的API。
- JSExecutorFactory 使用工厂模式,在不同场景构建出不同的 JSExecutor,其中生产环境使用 JSCExecutorFactory,用于构建 JSIExecutor,这个东西是底层 Bridge 的一个中间环节,先按住不讲后面细说。
- 在 JS 线程初始化底层 Bridge。
- 加载 JS 代码。
- 执行 JS 代码。
上面源码用到了一个 dispatch_group_t
类型的 prepareBridge
,dispatch_group_t
和 dispatch_group_notify
联合使用保证异步代码同步按顺序执行,从上面的启动流程来看,原生模块的加载在主线程,底层 Bridge的初始化是在 JS 线程,JS 代码的加载可以同步也可以异步,这些工作都是异步执行的,dispatch_group_notify
能够保证这些工作都执行完毕,在执行 JS 代码。关于 Bridge 的构建和 JS 代码的加载执行,流程都较长下面会细说。
原生模块加载
先用一个官方文档的例子,看看原生的模块都是如何被 ReactNative 进行加载并提供给 JS 侧使用的,在 ReactNative 中创建一个原生模块,以文档日历模块为例:
// CalendarManager.h #import <React/RCTBridgeModule.h> @interface CalendarManager : NSObject <RCTBridgeModule> @end 复制代码
// CalendarManager.m #import "CalendarManager.h" #import <React/RCTLog.h> @implementation CalendarManager RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) { RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); } @end 复制代码
- 创建一个 NSObject 的子类,叫 CalendarManager,并且遵循
RCTBridgeModule
协议。 - 在源文件内实现
RCT_EXPORT_MODULE()
。 - 使用
RCT_EXPORT_METHOD
宏,实现需要给 JS 导出的方法。
这样一个原生模块就创建完了,可以让 JS 侧直接调用:
// index.js import {NativeModules} from 'react-native'; const CalendarManager = NativeModules.CalendarManager; CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey'); 复制代码
这样一个简单的调用就结束了,我们只知道这样调用就能把参数传递到 CalendarManager里面来,然而并不知道 ReactNative背后做了什么,知其然不知其所以然 那我们深入看下源码,先看看 RCT_EXPORT_MODULE()
做了什么。
这是一个宏的嵌套函数,层层展开如下:
// RCTBridgeModule.h RCT_EXTERN void RCTRegisterModule(Class); \ + (NSString *)moduleName { return @#js_name; } \ + (void)load { RCTRegisterModule(self); } void RCTRegisterModule(Class moduleClass) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ RCTModuleClasses = [NSMutableArray new]; RCTModuleClassesSyncQueue = dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT); }); RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], @"%@ does not conform to the RCTBridgeModule protocol", moduleClass); // Register module dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{ [RCTModuleClasses addObject:moduleClass]; }); } 复制代码
展开宏自动帮我们实现了三个函数,@#
的意思是自动把宏的参数 js_name
转成字符通过 +moduleName
返回原生模块名称,重写 + (void)load
函数,调用 RCTRegisterModule()
把类注册到原生模块类集合。App在启动后,实际上所有模块都走了 +load
方法,也就是说都调用了 RCTRegisterModule()
方法进行注册。RCTRegisterModule()
里面首先判断注册的模块是否遵循了 RCTBridgeModule
协议,如果遵循了则会放入到 RCTModuleClasses
数组中,这里处于数组线程安全考虑,使用栅栏函数加了读写锁,其实还有个读函数后面会说。
第一个宏说完了再来看看第二个宏,RCT_EXPORT_METHOD
都做了些啥。RCT_EXPORT_METHOD
也是个展开宏,但是比上面的要复杂些,展开如下:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) { RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); } //展开如下 + (const RCTMethodInfo *)__rct_export__390 { static RCTMethodInfo config = { "", "addEvent:(NSString *)name location:(NSString *)location", NO, }; return &config; } - (void)addEvent:(NSString *)name location:(NSString *)location { RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); } 复制代码
看展开结果,RCT_EXPORT_METHOD
宏首先帮我们补全了 - (void)
,使得 - (void)
加上参数还原了我们上面给 JS 定义的方法,还声成了一个以 __rct_export__
拼接上 __LINE__
与 __COUNTER__
为方法名的函数,这是两个C语言宏,分别代表着行号与一个内置计数器,大概意思是要生成一个唯一的标识。该函数返回了一个 RCTMethodInfo
类型的对象, 包含了导出函数信息,有 JS 名、原生函数名、是否为同步函数。
这些方法定义好后,是如何被 JS 所加载的,那我们继续深入源码,探究一下 CalendarManager
原生模块,是如何被 ReactNative 加载并被 JS 调用到的。
回到 -start
方法的第三步,加载原生模块:
// RCTCxxBridge.mm - (void)start { dispatch_group_t prepareBridge = dispatch_group_create(); // 加载手动导出的原生模块 [self registerExtraModules]; // 加载自动导出的原生模块 (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]; // 加载调试模式所需原生模块 [self registerExtraLazyModules]; ... } 复制代码
我们以自动注册的模块为例,探究下具体实现部分:
// RCTCxxBridge.mm - (NSArray<RCTModuleData *> *)_registerModulesForClasses:(NSArray<Class> *)moduleClasses lazilyDiscovered:(BOOL)lazilyDiscovered { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil); NSArray *moduleClassesCopy = [moduleClasses copy]; NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count]; for (Class moduleClass in moduleClassesCopy) { if (RCTTurboModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) { continue; } NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); // Check for module name collisions RCTModuleData *moduleData = _moduleDataByName[moduleName]; if (moduleData) { ...省略一些代码 } moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; _moduleDataByName[moduleName] = moduleData; [_moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } [_moduleDataByID addObjectsFromArray:moduleDataByID]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); return moduleDataByID; } 复制代码
这里面主要创建了三个表:
- _moduleDataByID数组:通过 RCTGetModuleClasses() 将前面注册到数组中的模块取出来,循环遍历创建RCTModuleData类型对象,存储到_moduleDataByID数组中。实际上RCTModuleData把原生模块包装了一层,原生模块的实例只是RCTModuleData中的一个属性。这里不仅实例化了原生模块,还做了一些其他事情,这个后面会说
- _moduleDataByName字典:key就是前面提到的,模块导出宏自动添加的 +moduleName 返回值,如果没有设置,默认为当前类名。
- _moduleClassesByID数组:将所有模块的Class装进数组。
到这里所有原生模块就都被 ReactNative 加载并初始化完毕。这些原生模块都是JS侧通过Bridge来进行调用,JSBridge
应该都是耳熟能详的东西了,专门用来做 JS 和原生进行交互使用的工具,不管 Cordova
框架 也好还是我前面几篇文章说的 WKJavaScriptBridge
也好,都是使用的这种桥接技术,万变不离其宗,ReactNative 也是通过 JSBridge
来做交互的,只不过是个 cpp
版的 bridge。这个 bridge 的构建还得从上面的 -start
说起。-start
函数在第五步里面初始化了底层Bridge,我们在深入探究下底层 Bridge 的初始化流程。
底层Bridge构建
-start
中关于底层 bridge 构建的相关代码如下:
// RCTCxxBridge.mm - (void)start { // 创建JSThread _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil]; _jsThread.name = RCTJSThreadName; _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive; #if RCT_DEBUG _jsThread.stackSize *= 2; #endif [_jsThread start]; dispatch_group_t prepareBridge = dispatch_group_create(); ... _reactInstance.reset(new Instance); __weak RCTCxxBridge *weakSelf = self; ... dispatch_group_enter(prepareBridge); // 在_jsThread中初始化底层bridge [self ensureOnJavaScriptThread:^{ [weakSelf _initializeBridge:executorFactory]; dispatch_group_leave(prepareBridge); }]; ... } 复制代码
-start
的时候,创建了一个 JS 线程_jsThread
,这里涉及到线程保活的知识,子线程默认不开启 runloop,子线程在执行完任务后会被释放掉,所以这里在子线程开启了个 runloop,让线程一直存活,底层 Bridge 初始化,js bundle 包的加载/运行都在 JS 线程执行。- 在
_jsThread
线程中构建底层bridge。
继续看 _initializeBridge:
都做了什么:
// RCTCxxBridge.mm - (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory { __weak RCTCxxBridge *weakSelf = self; _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) { if (error) { [weakSelf handleError:error]; } }); ... [self _initializeBridgeLocked:executorFactory]; } - (void)_initializeBridgeLocked:(std::shared_ptr<JSExecutorFactory>)executorFactory { _reactInstance->initializeBridge( std::make_unique<RCTInstanceCallback>(self), executorFactory, _jsMessageThread, [self _buildModuleRegistryUnlocked]); } 复制代码
- 创建一个名为
_jsMessageThread
线程,绑定了_jsThread
的 runloop,使其任务执行完毕不退出,被RCTCxxBridge
持有,传递给底层 bridge。 - 初始化底层bridge,这是一个
C++
函数,函数内部初始化了一个叫NativeToJsBridge
的实例。
// Instance.cpp void Instance::initializeBridge( std::unique_ptr<InstanceCallback> callback, std::shared_ptr<JSExecutorFactory> jsef, std::shared_ptr<MessageQueueThread> jsQueue, std::shared_ptr<ModuleRegistry> moduleRegistry) { callback_ = std::move(callback); moduleRegistry_ = std::move(moduleRegistry); jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable { nativeToJsBridge_ = std::make_unique<NativeToJsBridge>( jsef.get(), moduleRegistry_, jsQueue, callback_); std::lock_guard<std::mutex> lock(m_syncMutex); m_syncReady = true; m_syncCV.notify_all(); }); CHECK(nativeToJsBridge_); } 复制代码
根据代码不难看出,最主要的工作是在上面传进来的线程中,同步执行 NativeToJsBridge
的创建,nativeToJsBridge_
的创建传递了三个参数:
-
InstanceCallback:底层调用结束后给上层的回调。
-
JSExecutorFactory:生产环境实际为
JSIExecutor
,后面这个类会细说。 -
MessageQueueThread:上层传递过来的
_jsMessageThread
。 -
ModuleRegistry:它负责两个非常核心的任务:一是生成中间版本的原生模块配置信息,进一步加工就可以最终导入给 JS 端,二是作为JS call Native的中转站。
ModuleRegistry
上面说了 moduleRegistry 中包括了所有native module信息,具体还要回到 RCTCxxBridge.mm 看代码:
// RCTCxxBridge.mm - (std::shared_ptr<ModuleRegistry>)_buildModuleRegistryUnlocked { ... auto registry = std::make_shared<ModuleRegistry>( createNativeModules(_moduleDataByID, self, _reactInstance), moduleNotFoundCallback); ... return registry; } 复制代码
//RCTCxxUtils.mm std::vector<std::unique_ptr<NativeModule>> createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance) { std::vector<std::unique_ptr<NativeModule>> nativeModules; for (RCTModuleData *moduleData in modules) { if ([moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) { nativeModules.emplace_back(std::make_unique<CxxNativeModule>( instance, [moduleData.name UTF8String], [moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; }, std::make_shared<DispatchMessageQueueThread>(moduleData))); } else { nativeModules.emplace_back(std::make_unique<RCTNativeModule>(bridge, moduleData)); } } return nativeModules; } 复制代码
_buildModuleRegistryUnlocked
主要负责构建并返回一个 ModuleRegistry
实例,_moduleDataByID
正是前面初始化原生模块时候的 RCTModuleData
数组,这里遍历数组,将 RCTModuleData
模块生成对应的 cpp
版本的 NativeModule
,NativeModule
定义了一套接口用于获取原生模块信息、调用原生模块函数:
// NativeModule.h class NativeModule { public: virtual ~NativeModule() {} //获取模块信息 virtual std::string getName() = 0; virtual std::vector<MethodDescriptor> getMethods() = 0; virtual folly::dynamic getConstants() = 0; //调用原生函数 virtual void invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) = 0; virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic&& args) = 0; }; 复制代码
RCTNativeModule
实际上就是个 C++ 版本的元生模块描述类,它是对 OC 版本 RCTModuleData
的封装。
至此我们所有原生的模块信息都已经准备完毕,目前来看所有的原生模块都注册到了 ModuleRegistry
中,那不妨猜想以下,是不是只要 JS 调用了 ModuleRegistry
就可以执行原生模块的方法了?事实确实如此,要想理解这个调用过程,需要先了解一下 NativeToJsBridge
,NativeToJsBridge
是一 个C++ 类,前面说到的 Instance
是对 NativeToJsBridge
的一层包装,NativeToJsBridge
比 Instance
更接近底层。
NativeToJsBridge
NativeToJsBridge
顾名思义是 Native Call JS 的集大成者,那么它和JS Call Native有什么关系呢,实际上它内部还持有着 JSExecutor
和 JsToNativeBridge
,并将 JsToNativeBridge
最终传递给 JSIExecutor
,由 JSIExecutor
来触发JS Call Native。实际 Native Call JS
底层也是 JSIExecutor
实现,这个 JSIExecutor
具体是做什么的一直没说,这个先按住不说,后面再说,先看看 NativeToJsBridge
的定义:
// NativeToJsBridge.cpp class NativeToJsBridge { public: friend class JsToNativeBridge; // 必须在主线程调用 NativeToJsBridge( JSExecutorFactory* jsExecutorFactory, std::shared_ptr<ModuleRegistry> registry, std::shared_ptr<MessageQueueThread> jsQueue, std::shared_ptr<InstanceCallback> callback); virtual ~NativeToJsBridge(); // 传入module ID、method ID、参数用于在JS侧执行一个函数 void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args); // 通过callbackId调用JS侧的回调 void invokeCallback(double callbackId, folly::dynamic&& args); // 开始执行JS application. 如果bundleRegistry非空,就会使用RAM的方式 读取JS源码文件 // 否则就假定 startupCode 已经包含了所有的JS源码文件 void loadApplication( std::unique_ptr<RAMBundleRegistry> bundleRegistry, std::unique_ptr<const JSBigString> startupCode, std::string sourceURL); void loadApplicationSync( std::unique_ptr<RAMBundleRegistry> bundleRegistry, std::unique_ptr<const JSBigString> startupCode, std::string sourceURL); private: std::shared_ptr<JsToNativeBridge> m_delegate; std::unique_ptr<JSExecutor> m_executor; std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread; }; 复制代码
因为在模块加载那里举的例子并没有包含Native给JS的回调,实际上从上面的代码能看出来,如果有回调的话会触发 NativeToJsBridge 的 invokeCallback 方法,把 callbackId 回调给JS侧,JS 侧拿到 callbackId 就可以执行相应的回调了。先简单介绍下它的成员变量和方法:
成员变量
- m_delegate:JsToNativeBridge类型的引用,主要用于JS call Native
- m_executor:这个是在前面说的工厂创建出来的类,生产环境是JSIExecutor,调试环境是RCTObjcExecutor,这是底层通信的集大成者,不管是JS Call Native还是Native Call JS都是基于它来进行。
- m_executorMessageQueueThread:并非NativeToJsBridge自己初始化,而是作为初始化参数传递进来的,是最外层的_jsMessageThread。这个线程具体做什么用后面说。
主要方法
-
void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args); 这个函数的意义就是通过module ID和method ID以及参数去调用JS方法
-
void invokeCallback(double callbackId, folly::dynamic&& args); 这个函数的意义就是通过callbackId和参数触发一个JS的回调。通常是JS call Native method之后,native把一些异步的执行结果再以callback的形式回调给JS。
-
void loadApplication( std::unique_ptr bundleRegistry, std::unique_ptr startupCode, std::string sourceURL); 这个方法的作用是执行JS代码,他还有一个兄弟叫做 loadApplicationSync,顾名思义,他兄弟是一个同步函数,所以他自己就是异步执行JS代码。
构造函数
// NativeToJsBridge.cpp NativeToJsBridge::NativeToJsBridge( JSExecutorFactory *jsExecutorFactory, std::shared_ptr<ModuleRegistry> registry, std::shared_ptr<MessageQueueThread> jsQueue, std::shared_ptr<InstanceCallback> callback) : m_destroyed(std::make_shared<bool>(false)), m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)), m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)), m_executorMessageQueueThread(std::move(jsQueue)), m_inspectable(m_executor->isInspectable()) {} 复制代码
NativeToJsBridge
内部把原生信息 registry
和外部传入的 callback
作为入参生成了 JsToNativeBridge
并持有, 这也是 JsToNativeBridge
能够 Call Native 的原因,jsExecutorFactory 又通过JsToNativeBridge 和外部传入的js线程生成了一个 executor 并持有,在生产环境下,这个executor就是 JSIExecutor。JS Call Native 实际上就是 JSIExecutor 来调用 JsToNativeBridge 实现,JSIExecutor 上面也有提到一直按住没表,那我们再来看下JSIExecutor。
JSIExecutor
通过上面的流程,我们明白了 JSIExecutor
相比 NativeToJsBridge
显得更底层,我们可以理解为 Instance
是 NativeToJsBridge
的包装,JSIExecutor
是 NativeToJsBridge
的包装,实际上在 Instance
中调用 callJSFunction:
,它的调用顺序应该是这样的:Instance->NativeToJsBridge->JSIExecutor
,JSIExecutor
会调用更底层,这个后面说。同样JS想要调用Native,也是一样:JSIExecutor->JsToNativeBridge->ModuleRegistry
。那么我们再往底层分析,先看看 JSIExecutor
的构造函数。
// JSCExecutorFactory.mm return std::make_unique<JSIExecutor>( facebook::jsc::makeJSCRuntime(), delegate, JSIExecutor::defaultTimeoutInvoker, std::move(installBindings)); 复制代码
简要分析下它里面的几个关键属性:
- jsi::Runtime runtime_:实际上就是JSCRuntime,内部实现了Runtime的接口,提供了可以执行JS的能力,内部比较复杂,这是基于C语言版的JavaScriptCore实现,用于创建JS上下文,执行JS,像JS注入原生对象等功能。
- delegate_:这个delegate是在NativeToJsBridge中初始化JSIExecutor是传递进来的参数,这个参数正是JsToNativeBridge对象,负责JS Call Native。
- nativeModules_:由外部传入的 ModuleRegistry 构造而成,在JS Call Native的时候,会通过 JSINativeModules 里面的 ModuleRegistry 来获取原生模块信息,并把这个信息通过 __fbGenNativeModule 函数传递给 JS 侧,由此可见,原生模块信息并不是主动导入到 JS 侧的,而是 JS 侧到原生获取的,大致流程如下:
// JSINativeModules.cpp Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) { ... // 调用createModule方法 auto module = createModule(rt, moduleName); ... auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first; return Value(rt, result->second); } 复制代码
那么createModule方法做了什么呢:
// JSINativeModules.cpp folly::Optional<Object> JSINativeModules::createModule( Runtime& rt, const std::string& name) { ... if (!m_genNativeModuleJS) { // 获取JS侧全局函数__fbGenNativeModule m_genNativeModuleJS = rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule"); } // 根据模块名,去原生模块注册对象中取出模块信息 auto result = m_moduleRegistry->getConfig(name); ... // 调用__fbGenNativeModule并把原生模块信息传递过去 Value moduleInfo = m_genNativeModuleJS->call( rt, valueFromDynamic(rt, result->config), static_cast<double>(result->index)); ... return module; } 复制代码
NativeModuleProxy
NativeModuleProxy 是 getModule 的唯一入口:
// JSIExecutor.cpp class JSIExecutor::NativeModuleProxy : public jsi::HostObject { public: // 构造函数 JSIExecutor实例作为NativeModuleProxy构造函数的入参 NativeModuleProxy(JSIExecutor &executor) : executor_(executor) {} // NativeModuleProxy 的 get方法 用于获取native module信息 Value get(Runtime &rt, const PropNameID &name) override { return executor_.nativeModules_.getModule(rt, name); } }; 复制代码
那么 NativeModuleProxy 这个 cpp 类又是在哪里使用的呢?全局搜索 NativeModuleProxy,你会发现只有一个地方再使用 NativeModuleProxy,就是 JSIExecutor 的 loadApplicationScript 方法,源码如下:
// JSIExecutor.cpp void JSIExecutor::loadApplicationScript( std::unique_ptr<const JSBigString> script, std::string sourceURL) { runtime_->global().setProperty( *runtime_, "nativeModuleProxy", Object::createFromHostObject( *runtime_, std::make_shared<NativeModuleProxy>(*this))); // ... } 复制代码
runtime 是一个 JSCRuntime 类型对象,通过调用 rumtime_->global()
获得一个全局的 global 对象。然后又通过 setProperty
方法给 global 对象设置了一个名为 nativeModuleProxy
的对象。JS 侧的 global 对象通过 "nativeModuleProxy"
这个名字即可访问到 native 侧的 NativeModuleProxy。本质上,JS侧的 global.nativeModuleProxy
就是native侧的 nativeModuleProxy
。换句话说,我们在 JS 侧的 NativeModules 对应的就是 native 侧的 nativeModuleProxy
。我们不妨再深入看以下 JS 侧的源码:
let NativeModules: {[moduleName: string]: Object, ...} = {}; if (global.nativeModuleProxy) { NativeModules = global.nativeModuleProxy; } else if (!global.nativeExtensions) { const bridgeConfig = global.__fbBatchedBridgeConfig; invariant( bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules', ); ... } 复制代码
我们在写 ReactNative 代码时使用的 NativeModules 正是原生端的 nativeModuleProxy 对象,文章开头的例子 NativeModules.CalendarManager
,实际上都会先到 Native 侧的 nativeModuleProxy,再到J SINativeModules,再调用到 getModule 方法,通过 CalendarManager 模块名来获取 CalendarManager 的信息,然后 Call 全局的 JS 函数 __fbGenNativeModule
,把原生信息传递给 JS 侧。我们可以顺便再分析下 JS 侧拿到配置信息做了什么:
// react-native/Libraries/BatchedBridge/NativeModules.js // 生成原生模块信息 function genModule( config: ?ModuleConfig, moduleID: number, ): ?{name: string, module?: Object} { const [moduleName, constants, methods, promiseMethods, syncMethods] = config; const module = {}; // 添加 JS版原生模块函数 methods && methods.forEach((methodName, methodID) => { const isPromise = promiseMethods && arrayContains(promiseMethods, methodID); const isSync = syncMethods && arrayContains(syncMethods, methodID); const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async'; // 生成JS函数 module[methodName] = genMethod(moduleID, methodID, methodType); }); // 添加原生模块导出常量 Object.assign(module, constants); if (module.getConstants == null) { module.getConstants = () => constants || Object.freeze({}); } else { ... } return {name: moduleName, module}; } // 导出genModule到全局变量global上以便native可以调用 global.__fbGenNativeModule = genModule; // 生成函数 genMethod(moduleID: number, methodID: number, type: MethodType) { let fn = null; if (type === 'promise') { fn = function(...args: Array<any>) { return new Promise((resolve, reject) => { // 函数入队 BatchedBridge.enqueueNativeCall( moduleID, methodID, args, data => resolve(data), errorData => reject(createErrorFromErrorData(errorData)), ); }); }; } else if (type === 'sync') { fn = function(...args: Array<any>) { return global.nativeCallSyncHook(moduleID, methodID, args); }; } else { fn = function(...args: Array<any>) { ... BatchedBridge.enqueueNativeCall(...); }; } fn.type = type; return fn; } 复制代码
最后这个 return {name: moduleName, module}
会返回到原生端,原生端会取出 module,再将它构造成 JS 对象给 JS 使用:
Value moduleInfo = m_genNativeModuleJS->call( rt, valueFromDynamic(rt, result->config), static_cast<double>(result->index)); CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null"; folly::Optional<Object> module( moduleInfo.asObject(rt).getPropertyAsObject(rt, "module")); 复制代码
到这里 JSIExecutor
的初始化完成了,JSBridge 就算搭建完了,以后 Native call JS 都会先后经由 Instance、NativeToJSBridge、JSIExecutor最终到达JS。
加载JS
分析到这里,原生模块已全部加载完毕,也已将所有原生模块信息导出给 JS 侧使用,RCTCxxBridge 到 _reactInstance 到 instance 背后的 NativeToJsBridge、JSIExecutor 构建 Bridge 的整个流程也已经全部走完。但是最后一步,JS是怎么调用到原生和原生怎么给JS回调的还并未展开,我们先回到 RCTCxxBrige 的 -start
方法:
- (void)start { // 异步加载 js bundle包 [self loadSource:^(NSError *error, RCTSource *source) { if (error) { [weakSelf handleError:error]; } sourceCode = source.data; dispatch_group_leave(prepareBridge); } onProgress:^(RCTLoadingProgress *progressData) { // 展示加载bundle 的 loadingView ... }]; } // 等待native moudle 和 JS 代码加载完毕后就执行JS 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]; } }); 复制代码
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress { ... __weak RCTCxxBridge *weakSelf = self; [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) { if (error) { [weakSelf handleError:error]; return; } onSourceLoad(error, source); }]; } + (void)loadBundleAtURL:(NSURL *)scriptURL onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)onComplete { // 尝试同步加载 int64_t sourceLength; NSError *error; NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL runtimeBCVersion:JSNoBytecodeFileFormatVersion sourceLength:&sourceLength error:&error]; if (data) { onComplete(nil, RCTSourceCreate(scriptURL, data, sourceLength)); return; } ... // 同步加载失败异步加载 if (isCannotLoadSyncError) { attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete); } else { onComplete(error, nil); } } static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoadProgressBlock onProgress, RCTSourceLoadBlock onComplete) { scriptURL = sanitizeURL(scriptURL); if (scriptURL.fileURL) { // 异步加载 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSError *error = nil; NSData *source = [NSData dataWithContentsOfFile:scriptURL.path options:NSDataReadingMappedIfSafe error:&error]; onComplete(error, RCTSourceCreate(scriptURL, source, source.length)); }); return; } } 复制代码
实际上 JS 代码的加载和底层 Bridge 的创建,都是并发执行的,因为 dispatch_group 的缘故,只有在他们都执行完毕和原生模块初始化完毕后,才会执行 JS。
执行JS
终于到了最后异步,执行JS代码,其实就是把js bundle包经过层层传递,最终交给 JavaScriptCore 去执行。
/ RCTCxxBridge.mm - (void)start { ... // 等待”原生模块实例创建完毕、底层Bridge初始化完毕、js bundle包加载完毕“,在JS线程执行js源码 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]; } }); } 复制代码
最后通过executeSourceCode执行代码,executeSourceCode 源码如下:
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync { // JS bundle包执行完毕回调 dispatch_block_t completion = ^{ // 执行暂存的Native call JS [self _flushPendingCalls]; dispatch_async(dispatch_get_main_queue(), ^{ // 主线程发送通知 [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self->_parentBridge userInfo:@{@"bridge": self}]; // 开启定时任务,最终用于驱动JS端定时任务 [self ensureOnJavaScriptThread:^{ [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; }]; }); }; // 根据sync来选择执行JS的方式(同步、异步) if (sync) { [self executeApplicationScriptSync:sourceCode url:self.bundleURL]; completion(); } else { [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion]; } } 复制代码
上述代码主要做了两件事情:
- 构建回调,在JS执行完进行回调。
- 执行 JS 代码。
- (void)enqueueApplicationScript:(NSData *)script url:(NSURL *)url onComplete:(dispatch_block_t)onComplete { // 底层转化为在JS线程执行JS bundle [self executeApplicationScript:script url:url async:YES]; if (onComplete) { _jsMessageThread->runOnQueue(onComplete); } } 复制代码
由于底层js bundle最终会在JS线程执行,因此 _jsMessageThread->runOnQueue(onComplete)
可以保证先执行完 JS bundle,后执行 onComplete 回调。
- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async { [self _tryAndHandleError:^{ NSString *sourceUrlStr = deriveSourceURL(url); ... // 根据 async 来同步加载、异步加载 self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),sourceUrlStr.UTF8String, !async); }]; } 复制代码
// Instance.cpp void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string, std::string sourceURL, bool loadSynchronously) { ... loadApplication(nullptr, std::move(string), std::move(sourceURL)); } void Instance::loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry, std::unique_ptr<const JSBigString> string, std::string sourceURL) { nativeToJsBridge_->loadApplication(std::move(bundleRegistry), std::move(string), std::move(sourceURL)); } 复制代码
// NativeToJsBridge.cpp void NativeToJsBridge::loadApplication( std::unique_ptr<RAMBundleRegistry> bundleRegistry, std::unique_ptr<const JSBigString> startupScript, std::string startupScriptSourceURL) { // 派发到JS线程执行js bundle runOnExecutorQueue( [...] (JSExecutor* executor) mutable { ... executor->loadApplicationScript(std::move(*startupScript), std::move(startupScriptSourceURL)); }); } 复制代码
如上,loadApplication
和 loadApplicationSync
这两个方法实现基本一致,都是调用了成员变量 m_executor的loadApplicationScript
方法,区别在于 loadApplication
把代码放到了 m_executorMessageQueueThread
中去执行,而 loadApplicationSync
在当前线程执行。
// JSIexecutor.cpp void JSIExecutor::loadApplicationScript( std::unique_ptr<const JSBigString> script, std::string sourceURL) { runtime_->global().setProperty( *runtime_, "nativeModuleProxy", Object::createFromHostObject( *runtime_, std::make_shared<NativeModuleProxy>(*this))); runtime_->global().setProperty( *runtime_, "nativeFlushQueueImmediate", Function::createFromHostFunction( *runtime_, PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"), 1, [this]( jsi::Runtime &, const jsi::Value &, const jsi::Value *args, size_t count) { callNativeModules(args[0], false); return Value::undefined(); })); runtime_->global().setProperty( *runtime_, "nativeCallSyncHook", Function::createFromHostFunction( *runtime_, PropNameID::forAscii(*runtime_, "nativeCallSyncHook"), 1, [this]( jsi::Runtime &, const jsi::Value &, const jsi::Value *args, size_t count) { return nativeCallSyncHook(args, count); })); // 最终调用到JavaScriptCore的JSEvaluateScript函数 runtime_->evaluateJavaScript( std::make_unique<BigStringBuffer>(std::move(script)), sourceURL); flush(); } 复制代码
- 通过 setProperty 方法给 global 对象设置了 nativeModuleProxy 的对象。JS 侧的 NativeModules 对应的就是 native 侧的 nativeModuleProxy。
- 向 global 中注入了 nativeFlushQueueImmediate,nativeCallSyncHook 两个方法。
- 调用
runtime_->evaluateJavaScript
方法,最终调用到 JavaScriptCore 的JSEvaluateScript
函数,SEvaluateScript 的作用就是在 JS 环境中执行 JS 代码。 - JS 脚本执行完成,执行
flush
操作。flush 函数的主要作用就是执行 JS 侧的队列中缓存的对 native 的方法调用。
void JSIExecutor::flush() { // 如果JSIExecutor的flushedQueue_函数不为空,则通过函数flushedQueue_获取待调用的方法queue,然后执行callNativeModules if (flushedQueue_) { callNativeModules(flushedQueue_->call(*runtime_), true); return; } // 以"__fbBatchedBridge"作为属性key去global中取对应的值也就是batchedBridge,batchedBridge本质上是JS侧的MessageQueue类实例化的一个对象 Value batchedBridge = runtime_->global().getProperty(*runtime_, "__fbBatchedBridge"); if (!batchedBridge.isUndefined()) { // 绑定batchedBridge bindBridge(); callNativeModules(flushedQueue_->call(*runtime_), true); } else if (delegate_) { // 如果没有获取到JS侧定义的batchedBridge对象,则直接执行callNativeModules方法,即没有bind操作。 callNativeModules(nullptr, true); } } 复制代码
BatchedBridge:批处理桥,ReactNative 在处理 JS 和 Native 通信并非调用一次执行一次,而是将调用消息保存到队列,在适当的实际进行flush。
flush()
flush
函数有必要着重讲一下,在 javaScript 代码被执行完毕后,会马上执行 flush() 函数,flush 内会先判断 flushedQueue_
是否存在,flushedQueue_
如果存在,就会直接执行 callNativeModules 调用原生模块的方法,如果不存在,就去 JS 侧获取 batchedBridge
,batchedBridge
是什么,js 侧有个 BatchedBridge.js
,如下:
// BatchedBridge.js const MessageQueue = require('./MessageQueue'); const BatchedBridge: MessageQueue = new MessageQueue(); Object.defineProperty(global, '__fbBatchedBridge', { configurable: true, value: BatchedBridge, }); module.exports = BatchedBridge; 复制代码
如果 JS 调用过 Native,BatchedBridge 就会被初始化,BatchedBridge 对象实际上就是 JS 侧的 MessageQueue,在初始化完之后,定义了一个叫 __fbBatchedBridge
的全局属性,并把 BatchedBridge 对象作为这个属性的 value 值,等待着被 JSIExecutor
使用。
再回到 flush()
方法里面,native 侧通过 __fbBatchedBridge
拿到 batchedBridge,先判断它是否存在,如果不存在,说明 JS 从来没有调用过 Native,callNativeModules 直接传空,如果存在,就说明 JS 侧有调用,Native 侧就需要进行绑定 JSBridge 的方法,将 JS 的调用队列拿过来进行执行。因为在 JS 被加载的过程中,可能会存在 JS 调用 Native,所以这里提前执行一次 flush
,将这些调用全部执行。
bindBridge()
flush()另一个重要的地方就是 bindBridge:
// JSIExecutor.cpp void JSIExecutor::bindBridge() { std::call_once(bindFlag_, [this] { SystraceSection s("JSIExecutor::bindBridge (once)"); Value batchedBridgeValue = runtime_->global().getProperty(*runtime_, "__fbBatchedBridge"); if (batchedBridgeValue.isUndefined()) { throw JSINativeException( "Could not get BatchedBridge, make sure your bundle is packaged correctly"); } Object batchedBridge = batchedBridgeValue.asObject(*runtime_); callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction( *runtime_, "callFunctionReturnFlushedQueue"); invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction( *runtime_, "invokeCallbackAndReturnFlushedQueue"); flushedQueue_ = batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue"); callFunctionReturnResultAndFlushedQueue_ = batchedBridge.getPropertyAsFunction( *runtime_, "callFunctionReturnResultAndFlushedQueue"); }); } 复制代码
bindBridge一共绑定了四个方法:
- callFunctionReturnFlushedQueue
- invokeCallbackAndReturnFlushedQueue
- flushedQueue
- callFunctionReturnResultAndFlushedQueue
故名思义,这几个方法都是由 native 触发,并把 JS 侧的 flushedQueue
返回给 Native 侧进行处理。以 callFunctionReturnFlushedQueue
为例,Native 侧的 callFunctionReturnFlushedQueue_
指针指向了 JS 侧的 callFunctionReturnFlushedQueue
方法,当调用 callFunctionReturnFlushedQueue_
的时候会直接调用到 JS 侧,JS 侧的方法定义如下:
// MessageQueue.js callFunctionReturnFlushedQueue( module: string, method: string, args: any[], ): null | [Array<number>, Array<number>, Array<any>, number] { this.__guard(() => { this.__callFunction(module, method, args); }); return this.flushedQueue(); } 复制代码
// MessageQueue.js flushedQueue(): null | [Array<number>, Array<number>, Array<any>, number] { this.__guard(() => { this.__callImmediates(); }); const queue = this._queue; this._queue = [[], [], [], this._callID]; return queue[0].length ? queue : null; } 复制代码
JS 会先根据 moduleId 和 methodId 完成调用,然后会执行 flushedQueue 将队列清空,在将其返回给 Nativ e侧,Native 侧拿到队列就会继续执行 JSIExecutor->callNativeModules->ModuleRegistry
那一串流程了。至此整个 JS 执行流程全部走完,开始执行 completion()
,代码又回到 RCTCxxBridge.mm 的 executeSourceCode:sync:
内:
// RCTCxxBridge.mm dispatch_block_t completion = ^{ ... dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self->_parentBridge userInfo:@{@"bridge": self}]; ... }); }; 复制代码
然后 bridge 会向外发送一个名为 RCTJavaScriptDidLoadNotification
的通知,这个通知在哪里实现呢,可以全局搜一下我们发现我们又回到了最顶端,RCTRootView
里。
// RCTRootView.m - (void)javaScriptDidLoad:(NSNotification *)notification { ... [self bundleFinishedLoading:bridge]; ... } - (void)bundleFinishedLoading:(RCTBridge *)bridge { ... [self runApplication:bridge]; ... } - (void)runApplication:(RCTBridge *)bridge { NSString *moduleName = _moduleName ?: @""; NSDictionary *appParameters = @{ @"rootTag": _contentView.reactTag, @"initialProps": _appProperties ?: @{}, }; RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters); [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[moduleName, appParameters] completion:NULL]; } 复制代码
最后执行到 runApplication
,runApplication 内部会调用 enqueueJSCall,AppRegistry 是组件名,runApplication 是组件的方法,args 是传递的参数,接着会走 RCTCxxBridge->Instance->NativeToJsBridge->JSIExecutor->callFunctionReturnFlushedQueue_
这一大长串,callFunctionReturnFlushedQueue_
实际上就是 JS 侧的 callFunctionReturnFlushedQueue
方法,JS 侧开始执行入口渲染界面,完事返回 flushedQueue
给 Native,Native 再去遍历 queue 根据 moduleId、methodId 去执行原生模块的方法。至此,整个交互流程结束。
flushedQueue
JS 桥接让 JS 和 Native 可以直接交互的同时,也会带来性能损耗的问题。尤其像 ReactNative 这样的框架,JS 与 Native 的交互非常频繁,可以想象 scrollView 的滚动,动画的实现等等,将会带来非常大的性能开销。flushedQueue 虽然不能完美的解决这个问题,但是也优化到了极致。JS Call Native 并不是直接调用的 Native 方法,而是将调用消息先存放到队列:
// MessageQueue.js const MIN_TIME_BETWEEN_FLUSHES_MS = 5; enqueueNativeCall( moduleID: number, methodID: number, params: any[], onFail: ?Function, onSucc: ?Function, ) { this.processCallbacks(moduleID, methodID, params, onFail, onSucc); this._queue[MODULE_IDS].push(moduleID); this._queue[METHOD_IDS].push(methodID); this._queue[PARAMS].push(params); const now = Date.now(); if ( global.nativeFlushQueueImmediate && now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ) { const queue = this._queue; this._queue = [[], [], [], this._callID]; this._lastFlush = now; global.nativeFlushQueueImmediate(queue); } } 复制代码
可以看到 _queue
这个变量 push 进去的模块名和方法名都是 ID,并不是真正的类型,实际上 Native 侧存了一个映射表,拿到 JS 侧传过来的 ID 映射到具体的类、方法,通过 NSInvocation 组装参数发送消息。至于为什么传 ID 过去,目的是减少通信数据量,降低沟通成本
。另外JS侧把这些数据存放到 _queue 后并没有主动发给 Native 侧,而是在适当的时机 Native 过来取,正是通过 bindBridge()
里面绑定的方法。那有同学可能会问了,这个时机是什么时候?在我们日常开发中,往往都是只有事件响应了,才会做代码执行,消息传递,这个事件可能是屏幕点击事件,也就可能是 timer 事件、系统事件等等,ReactNative 也是一样,它的所有 UI 页面都是 Native 实现,当 Native 有事件触发就会Call JS,也可能是主动调用 JS,这有可能会更新 UI,也有可能是单纯出发个事件例如按钮的点击,待 JS 侧处理完业务逻辑后会执行 flushQueue
,将消息队列返回给 Native 侧。这样做最主要的目的还是为了减少沟通成本,提升桥接性能,这一点与 Cordova
框架的设计几乎一致。压缩数据大小加上降低交互频次,几乎可以说是优化到极限了
。
如果 Native 始终不调用 JS 侧是不是队列里面的消息就一直不会被执行?上面源码的 MIN_TIME_BETWEEN_FLUSHES_MS
的常量不知道大家有没有注意到,每次 enqueueNativeCall 的时候都会拿上一次清空 queue 的时间(flushQueue 或者 invokeCallback 或者执行 nativeFlushQueueImmediate 都会重置这个时间)和现在比较,如果 JS call Native 批量调用时间间隔 >= 5毫秒,那就执行一次 nativeFlushQueueImmediate()
,这个函数是干嘛的之前没有说,我也是追着 JS 侧的源码才发现这个函数。这也是一个全局函数,也是 JS 侧唯一主动调用 Native 侧的方法,其他 JS 调用 Native 都是被动等 Native 过来取。然后我在全局搜索了以下,最终发现这个方法是在 JSIExecutor 进行 loadApplicationScript
的时候给 JS 侧注入的,JS 侧调用这个方法,Native 侧就会执行 callNativeModules()
把消息分发到各个模块。
至此,ReactNative 的初始化流程,原生模块的加载,底层 Bridge 的构建,JS代码的加载、执行过程,全部分析完毕,后续会着手对 ReactNative UI 渲染层面上继续分析,分析它是如何动态修改我们的 UI 组件。
这篇关于ReactNative 源码解析 (一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值