? React Native(以下簡(jiǎn)稱RN)的目標(biāo)是用基于react的JavaScript寫代碼,在iOS/Android平臺(tái)上原生渲染砰嘁,正如他們的口號(hào)"Learn Once,Write anywhere!"职抡,只要學(xué)會(huì)了大前端丰涉,iOS/Android/Web通吃吁脱,這樣就很神奇了尤溜,react.js還是那個(gè)react.js,模塊化竭宰、虛擬DOM空郊、JSX語(yǔ)法概念一樣沒(méi)少,甚至可以基于流行的flux單向數(shù)據(jù)流來(lái)架構(gòu)我們的應(yīng)用切揭,而在客戶端并不是一個(gè)web頁(yè)面狞甚,而是純?cè)秩荆阅鼙燃僿eb頁(yè)提升很多廓旬,而且還順帶具有像web頁(yè)一樣的動(dòng)態(tài)更新能力哼审。
? 隨著版本的迭代更新,RN功能和相關(guān)特性也越來(lái)越多孕豹,代碼復(fù)雜度也隨之上升涩盾,這里先不論RN的爭(zhēng)議和發(fā)展趨勢(shì),而來(lái)學(xué)習(xí)下其優(yōu)秀的架構(gòu)設(shè)計(jì)和代碼實(shí)現(xiàn)励背。我們就用一個(gè)最簡(jiǎn)單的項(xiàng)目來(lái)進(jìn)行剖析春霍,運(yùn)行命令react-native init RNDemo
,這里我的RN版本號(hào)為:0.47.0叶眉,RN最核心的當(dāng)屬js與native的通信機(jī)制址儒,理解了這套機(jī)制則比較容易理解RN整個(gè)架構(gòu)。
Native模塊
RCTBridgeModule
native導(dǎo)出給js的類稱之為模塊類衅疙,如RCTUIManager
RCTTiming
等莲趣,每個(gè)模塊類都實(shí)現(xiàn)了RCTBridgeModule
協(xié)議
@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
該協(xié)議定義了模塊導(dǎo)出方法、導(dǎo)出常量炼蛤、模塊運(yùn)行隊(duì)列等妖爷,還定義了很多宏。
RCT_EXPORT_MODULE
模塊類被加載進(jìn)runtime的時(shí)候,執(zhí)行load
方法絮识,將該模塊類的Class
信息添加到一個(gè)全局?jǐn)?shù)組RCTModuleClasses
里去绿聘,RCTGetModuleClasses()
方法可獲取該數(shù)組。
RCT_EXPORT_METHOD(method)
RN在e9095b2
版本移除了bang神博客所說(shuō)的從data數(shù)據(jù)段獲取導(dǎo)出方法的黑魔法次舌,而是給每個(gè)導(dǎo)出方法添加一個(gè)對(duì)應(yīng)的方法熄攘。
比如method為doSomething
,則宏展開后為
+ (NSArray *)__rct_export__(行號(hào)和系統(tǒng)計(jì)數(shù)){
return @[@"", @"doSomething", @(NO)];
}
- (void)doSomething;
RCTModuleData
方法- (NSArray<id<RCTBridgeMethod>> *)methods
彼念,通過(guò)遍歷模塊運(yùn)行時(shí)方法列表挪圾,找到有__rct_export__
前綴的方法,根據(jù)方法返回的數(shù)組實(shí)例化RCTModuleMethod
,從而收集到所有RCT_EXPORT_METHOD
對(duì)應(yīng)的導(dǎo)出方法逐沙。
模塊配置
所有的模塊配置存放在ModuleRegistry
C++類中哲思,JSCExecutor
的getNativeModule
方法可獲得指定模塊的配置,最終是從ModuleRegistry
類方法getConfig
拿到吩案。JSCNativeModules
管理Native的導(dǎo)出模塊棚赔,JSCNativeModules
構(gòu)造函數(shù)傳入JsToNativeBridge
的getModuleRegistry
方法返回的ModuleRegistry
指針,JsToNativeBridge
管理js調(diào)用Native所需配置徘郭、方法等靠益,是js調(diào)用native的native響應(yīng)方。
以RCTAppState
模塊為例残揉,拿到的模塊信息如下:
struct ModuleConfig {
size_t index;
folly::dynamic config;
};
{
21;
[AppState,{initialAppState:unknown},[getCurrentAppState,addListener,removeListeners]];
}
index是模塊index胧后,config動(dòng)態(tài)數(shù)組依次存放moudlename、export constants抱环、export methodNames array 壳快、promiseMethodId array 、syncMethodId array江醇。
Native模塊生成
Native模塊初始化
在應(yīng)用啟動(dòng)delegate里濒憋,創(chuàng)建了一個(gè)RCTRootView
實(shí)例,這個(gè)實(shí)例初始化了RCTBridge
實(shí)例陶夜,在其- (void)setUp
方法里:
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];
self.batchedBridge
是RCTCxxBridge
的實(shí)例,用于批量橋接裆站,之前版本的RCTBatchedBridge
已不再實(shí)現(xiàn)条辟,在- (void)start
方法主要步驟是:
- 發(fā)送js即將加載通知
-
創(chuàng)建常駐線程
_jsThread
,native和js互相調(diào)用默認(rèn)會(huì)在該線程執(zhí)行宏胯,也可以自己指定模塊的運(yùn)行隊(duì)列_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數(shù)組
extraModules
從delegate實(shí)現(xiàn)方或傳入的
moduleProvider
屬性獲取extraModules
羽嫡,這里兩者都為空 實(shí)例化
moduleClassesByID
Class
數(shù)組,moduleDataByID
RCTModuleData*
模塊數(shù)據(jù)數(shù)組,moduleDataByName
名字模塊數(shù)據(jù)字典,臨時(shí)保存肩袍,作用見名思義遍歷
extraModules
杭棵,填充第二步數(shù)組和字典-
從
RCTGetModuleClasses()
得到聲明了RCT_EXPORT_MODULE
的所有模塊Class
,遍歷數(shù)組,先檢查命名沖突,再以moduleClass
為參數(shù)實(shí)例化RCTModuleData
魂爪,然后填充第二步數(shù)組和字典RCTModuleData
類管理導(dǎo)出給js的模塊數(shù)據(jù)先舷,包括Class
信息,導(dǎo)出方法滓侍,導(dǎo)出常量等 遍歷
_moduleDataByID
蒋川,調(diào)用RCTModuleData
對(duì)應(yīng)實(shí)例的instance
方法,初始化RCTModuleData
類執(zhí)行
_prepareModulesWithDispatchGroup
方法撩笆,初始化除白名單外的模塊導(dǎo)出常量
-
-
實(shí)例化
Instance
類捺球,該類在下文有介紹_reactInstance.reset(new Instance);
-
實(shí)例化抽象工廠類
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運(yùn)行會(huì)實(shí)例化
JSCExecutorFactory
, 瀏覽器遠(yuǎn)程調(diào)試模式會(huì)實(shí)例RCTObjcExecutorFactory
夕冲,這里我們就以JSCExecutorFactory
為例分析氮兵,遠(yuǎn)程調(diào)試會(huì)在另一篇做分析。 -
在
_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, @""); }
主要步驟:
- 實(shí)例化
RCTMessageThread
類歹鱼,RCTMessageThread
類封裝了在_jsThread常駐線程上的同步和異步執(zhí)行任務(wù)的方法泣栈。 -
Instance
實(shí)例_reactInstance
執(zhí)行了初始化方法void initializeBridge(..)
, 注入了所需依賴,Instance
是iOS/Android與javacriptCore交互的入口類醉冤。初始化了模塊注冊(cè)表ModuleRegistry
實(shí)例秩霍,ModuleRegistry
是C++類,ios/android均需填充模塊信息數(shù)組
- 實(shí)例化
-
加載jsbundle源文件
RCTJavaScriptLoader
封裝了加載jsbundle文件的方法蚁阳,主要步驟是需要下載則通過(guò)RCTMultipartDataTask
下載js文件铃绒,最后返回NSData
數(shù)據(jù) -
執(zhí)行解析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]; } });
在上述組任務(wù)結(jié)束時(shí),收到通知螺捐,在最高等級(jí)全局隊(duì)列執(zhí)行加載完的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 }
主要步驟:
清空調(diào)用隊(duì)列
_pendingCalls
颠悬,_pendingCount
置0發(fā)送js加載完畢通知,
_jsThread
線程定血,設(shè)置RCTDisplayLink
監(jiān)測(cè)js線程幀率赔癌,原理在另一篇作分析-
執(zhí)行解析jsbundle,這里是異步執(zhí)行
執(zhí)行解析中間過(guò)程為了解耦和擴(kuò)展性澜沟,引入了
NativeToJsBridge
等類灾票,層次很多,可能看到這就有點(diǎn)暈了茫虽,來(lái)看下這塊的執(zhí)行過(guò)程刊苍。
jsBundle執(zhí)行
首先,上文提到的JSCExecutorFactory
濒析,運(yùn)用了標(biāo)準(zhǔn)的工廠模式:
JSCExecutor
是本地JavaScriptCore具體執(zhí)行的產(chǎn)品類正什,是跨平臺(tái)的C++類,是一個(gè)非常重要的類号杏。
JSCExecutor
構(gòu)造時(shí)向JSContext注入了全局函數(shù)
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
...
installGlobalProxy(m_context, "nativeModuleProxy",
exceptionWrapMethod<&JSCExecutor::getNativeModule>());
...
installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");
installNativeHook
方法同時(shí)注冊(cè)了回調(diào)函數(shù)exceptionWrapMethod<method>()
婴氮,當(dāng)JS端調(diào)用對(duì)應(yīng)方法,exceptionWrapMethod
全局函數(shù)被調(diào)用。
? MessageQueueThread
是RCTMessageThread
的抽象基類主经,封裝了在消息隊(duì)列里同步和異步執(zhí)行的方法荣暮,MessageQueueThread
是需要各平臺(tái)各自實(shí)現(xiàn)的。
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
類依賴了很多類旨怠,它是一個(gè)C++類渠驼,是iOS/Android與javacriptCore交互的入口類,封裝了native與js的交互鉴腻,包括解析js字符流迷扇,調(diào)用js方法,設(shè)置jscontext全局變量爽哎,發(fā)出回調(diào)等蜓席。
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
等類,都需要平臺(tái)各自實(shí)現(xiàn)课锌,Instance
是作為一個(gè)跨平臺(tái)的接口封裝類厨内,如iOS中,callback
成員變量是指向InstanceCallback
的子類RCTInstanceCallback
渺贤,jsef
成員則動(dòng)態(tài)指向JSExecutorFactory
的具體工廠類雏胃,JSExecutorFactory
則可以選擇對(duì)應(yīng)的具體產(chǎn)品類。jsQueue
指向RCTMessageThread
類志鞍,moduleRegistry
需要各平臺(tái)自行填充瞭亮。Instance
類函數(shù)實(shí)現(xiàn)基本都是通過(guò)NativeToJsBridge
具體實(shí)現(xiàn)的,在Instance
執(zhí)行initializeBridge
時(shí)固棚,在MessageQueueThread
同步初始化了NativeToJsBridge
的實(shí)例nativeToJsBridge_统翩,那么我們來(lái)看看NativeToJsBridge
類。
? 在RCTCxxBridge
類的enqueueApplicationScript:url:onComplete:
方法此洲,根據(jù)jsbundle類型去執(zhí)行對(duì)應(yīng)的方法厂汗。jsbundle目前有三種類型:String
RAMBundle
BCBundle
,String
表示普通jsbundle呜师,用bundle
命令整合出來(lái)的娶桦。RAMBundle
是用unbundle
命令打出來(lái)的bundle,它除了生成整合的js文件index.ios.bundle
外汁汗,還會(huì)生成各個(gè)單獨(dú)的未整合js文件趟紊,全部放在js-modules
目錄下, bundle頭四個(gè)字節(jié)固定為0xFB0BD1E5
碰酝。BCBundle
是js字節(jié)碼bundle類型,并未用到戴差,就以普通jsbundle為例
self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
sourceUrlStr.UTF8String, false);
核心是通過(guò)Instance
實(shí)例去執(zhí)行解析送爸, Instance
的loadScriptFromString
方法調(diào)用到NativeToJsBridge
的loadApplication
和loadApplicationSync
的方法,NativeToJsBridge
實(shí)例在Instance
類構(gòu)造的時(shí)候在_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調(diào)用到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);
}
該方法主要步驟:
-
根據(jù)JSBundle header得到JSBundle類型來(lái)執(zhí)行對(duì)應(yīng)的js上下文墨吓。
evaluateScript
是由jshelps
組件封裝的,最終調(diào)用到JavaScriptCore的方法JSEvaluateScript
纹磺。jschelps
主要封裝了JavaScriptCore的相關(guān)函數(shù)帖烘,以及JSStringRef
的C++對(duì)象封裝String
類,JSObjectRef
的C++對(duì)象封裝Object
類橄杨,JSValueRef
的C++對(duì)象封裝Value
類等秘症。 -
調(diào)用
flush()
native調(diào)用js
flushedQueue
方法,返回js端待調(diào)用方法隊(duì)列式矫,然后native執(zhí)行乡摹,清空該隊(duì)列
至此,Native模塊生成和相關(guān)準(zhǔn)備工作完成采转,模塊配置存放在ModuleRegistry
類中聪廉。
JS模塊
本地jscore運(yùn)行時(shí),當(dāng)js需要調(diào)用到native模塊的時(shí)候故慈,通過(guò)nativeModuleProxy
執(zhí)行native所注入方法板熊,返回對(duì)應(yīng)的模塊信息,而當(dāng)遠(yuǎn)程調(diào)試模式時(shí)察绷,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調(diào)用的模塊,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;
};
}
通過(guò)外部注冊(cè),填充_lazyCallableModules
數(shù)組情萤,
Native 調(diào)用 JS
在RN里鸭蛙,封裝了底層細(xì)節(jié),外部暴露出的是通過(guò)RCTCxxBridge
方法enqueueJSCall:method:args:completion
調(diào)用筋岛,如native向js發(fā)送時(shí)間消息的方法sendEventWithName:body
實(shí)現(xiàn)就是調(diào)用該方法娶视。該方法實(shí)現(xiàn)如下:
- (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, @"");
}
主要通過(guò)調(diào)用Instance
類的callsJSFunction
方法,最終調(diào)用到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
方法先執(zhí)行js端方法callFunctionReturnFlushedQueue
(在MessageQueue.js文件中),返回js端消息隊(duì)列睁宰,然后native解析隊(duì)列肪获,即調(diào)用callNativeModules
,這個(gè)過(guò)程在下文JS調(diào)用Native
有分析柒傻。
總體來(lái)說(shuō)還是使用JSCHelpers
中封裝的C++方法evaluateScript(JSContextRef, JSStringRef, JSStringRef)
孝赫,在常駐線程來(lái)執(zhí)行js語(yǔ)句,返回結(jié)果native解析红符。
JS調(diào)用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執(zhí)行時(shí)青柄,nativeModuleProxy
全局函數(shù)在JSCExecutor
構(gòu)造時(shí)伐债,通過(guò)installGlobalProxy
方法注入了,這里的else
分支是瀏覽器遠(yuǎn)程調(diào)試走的致开。當(dāng)取nativeModuleProxy
屬性峰锁,如執(zhí)行const RCTAppState = NativeModules.AppState;
,JSObjectGetPropertyCallback
回調(diào)在C++端被觸發(fā)双戳,調(diào)用到JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName)
方法虹蒋,該方法通過(guò)JSCNativeModules
的getModule
方法拿到native對(duì)應(yīng)配置,如第一節(jié)?模塊配置中拿到對(duì)應(yīng)的配置表飒货。
? js端也有 有BatchedBridge
概念魄衅,node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js
中,const BatchedBridge = new MessageQueue();
膏斤,BatchedBridge
對(duì)象實(shí)際上是MessageQueue
的實(shí)例徐绑,轉(zhuǎn)到當(dāng)前目錄下的MessageQueue.js
文件。
? js需要調(diào)用native方法的時(shí)候莫辨,調(diào)用enqueueNativeCall
函數(shù)傲茄,比如js端執(zhí)行方法:
UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload)
這段代碼是在ReactNativeStack-dev.js
中,用于js端通告native創(chuàng)建視圖沮榜,UIManager
實(shí)際是NativeModules
對(duì)象盘榨,本地JavacriptCore運(yùn)行時(shí),NativeModules
對(duì)象方法在native的JSCExecutor::getNativeModule
方法中通過(guò)調(diào)用js方法global.__fbGenNativeModule
建立蟆融,global.__fbGenNativeModule
即指向genModule
方法對(duì)象,genModule
方法中調(diào)用genMethod
,genMethod
中持有閉包草巡,將native方法調(diào)用通過(guò)BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
方法加入隊(duì)列處理,故上述方法調(diào)用最終通過(guò)enqueueNativeCall
調(diào)用型酥。
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
方法會(huì)把當(dāng)前的_callID
插入到_queue
最后山憨,緊接著判斷相鄰兩次flushQueue
時(shí)間超過(guò)MIN_TIME_BETWEEN_FLUSHES_MS
即5ms,或者當(dāng)前沒(méi)有正在處理的方法弥喉,則執(zhí)行全局nativeFlushQueueImmediate
函數(shù)郁竟。nativeFlushQueueImmediate
函數(shù)傳入_queue
參數(shù),它在native端之前通過(guò)installNativeHook
注入了由境,js端調(diào)用后native端收到函數(shù)回調(diào)棚亩,最終對(duì)應(yīng)執(zhí)行JSCExecutor
類的nativeFlushQueueImmediate
方法,該方法最終調(diào)用到JsToNativeBridge
的callNativeModules
方法虏杰,callNativeModules
解析出js透?jìng)鞯膮?shù)_queue
讥蟆,然后動(dòng)態(tài)調(diào)用方法。
for (auto& call : parseMethodCalls(std::move(calls))) {
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
m_registry是ModuleRegistry
的實(shí)例
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
方法即以反射去動(dòng)態(tài)執(zhí)行方法纺阔,具體執(zhí)行方法各平臺(tái)各自實(shí)現(xiàn)瘸彤,iOS上實(shí)際執(zhí)行的類是RCTNativeModule
,它的invoke
方法在參數(shù)對(duì)應(yīng)的module
指定線程隊(duì)列執(zhí)行invokeInner
方法,然后經(jīng)過(guò)轉(zhuǎn)換參數(shù)等操作笛钝,最終調(diào)用到RCTModuleMethod
類的invokeWithBridge:module:arguments
方法钧栖,通過(guò)NSInvocation
的invokeWithTarget
方法實(shí)現(xiàn)動(dòng)態(tài)調(diào)用低零,并返回調(diào)用結(jié)果,中間經(jīng)過(guò)了處理method name, methodSignature等過(guò)程拯杠,此處代碼可瀏覽RCTModuleMethod
的類實(shí)現(xiàn)。另外在processMethodSignature
方法中啃奴,將cbID
和返回結(jié)果暫存潭陪,調(diào)用成功通過(guò)JSCExecutor
的m_invokeCallbackAndReturnFlushedQueueJS
屬性 ,調(diào)用到j(luò)s里MessageQueue
類的invokeCallbackAndReturnFlushedQueue
方法,js端拿到返回值最蕾,js調(diào)用native的閉環(huán)形成依溯。
? 那么還有一個(gè)問(wèn)題,js只是把消息加入了隊(duì)列瘟则,js什么時(shí)候去讓native去取js的消息隊(duì)列處理黎炉?
-
js端超時(shí)機(jī)制
需要注意的是,遠(yuǎn)程調(diào)試模式并沒(méi)有超時(shí)機(jī)制醋拧,
global.nativeFlushQueueImmediate
始終是 undefined的慷嗜。每次消息入隊(duì)的時(shí)候,會(huì)檢查距離上次隊(duì)列清空完成是否超過(guò)5ms丹壕,超過(guò)則調(diào)用
nativeFlushQueueImmediate
清空隊(duì)列庆械,native注冊(cè)回調(diào)被調(diào)用,否則立即入隊(duì)菌赖,由于js是單線程的缭乘,5ms內(nèi)也不會(huì)積壓很多消息,所以不用擔(dān)心處理效率問(wèn)題琉用。 -
native主動(dòng)調(diào)用
native調(diào)用js方法堕绩,native調(diào)用
enqueueJSCall:method:args:completion
方法會(huì)取到j(luò)s消息隊(duì)列,其實(shí)包含folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional<Object> m_callFunctionReturnFlushedQueueJS; folly::Optional<Object> m_flushedQueueJS; folly::Optional<Object> m_callFunctionReturnResultAndFlushedQueueJS;
處理方法都會(huì)返回js消息隊(duì)列邑时,即native每次調(diào)用js奴紧,都會(huì)主動(dòng)去取js隊(duì)列,比如事件消息刁愿、timer等绰寞。
綜上所述,js調(diào)用native實(shí)際上是有兩種機(jī)制的:
- native向jscontext的 global注入全局對(duì)象铣口,同時(shí)注冊(cè)相應(yīng)的回調(diào)滤钱,如
nativeFlushQueueImmediate
,js函數(shù)被調(diào)用脑题,對(duì)應(yīng)native回調(diào)被響應(yīng) - js組成消息隊(duì)列件缸,native調(diào)用
flushedQueue
主動(dòng)去取
第一種是JSPatch
所采用的,不過(guò)它注冊(cè)的回調(diào)是一個(gè)block, 第二種機(jī)制是最復(fù)雜的叔遂,對(duì)于模塊他炊,需要兩端維護(hù)一份配置表争剿,但是最高效的,js方需要執(zhí)行native方法痊末,僅需傳遞moduleId
methodId
arguments
必要參數(shù)給native蚕苇,而方法真正執(zhí)行是在native方異步執(zhí)行的,返回結(jié)果異步返回給js方凿叠,如果換成方式1涩笤,native方法在jscontext同步執(zhí)行,明顯影響效率盒件,而且 當(dāng)短時(shí)間內(nèi)有很多條消息蹬碧,JS并不會(huì)去頻繁調(diào)用native,會(huì)在5ms內(nèi)去累積消息炒刁,然后發(fā)送給native恩沽。