可能大家都知道RN的實現(xiàn)機制主要是兩個常駐線程焦影,一個JS線程,一個UI線程封断,相互通信斯辰,js處理計算,給主線程發(fā)消息渲染坡疼”蛏耄基于這一點的基礎上探索RN的實現(xiàn)細節(jié)。
首先因為不清楚RN工程中如何將JS標簽轉變成原生的RCTView,所以想到斷點調(diào)試數(shù)據(jù)闸氮,看調(diào)用順序是怎樣的剪况。因為涉及線程頻繁的切換,斷點調(diào)試的探索過程還是花費了較久的時間蒲跨。
由于斷點了RCTView 的init 方法發(fā)現(xiàn)译断,所有的視圖創(chuàng)建都是有RCTUIManager調(diào)用createView方法,傳代一個tag標志或悲,一個props字典創(chuàng)建的view∷镞洌現(xiàn)在問題是誰調(diào)用了RCTUIManager呢,又是如何調(diào)用的呢巡语,看程序的調(diào)用棧發(fā)現(xiàn)翎蹈,是由RCTBatchBridge 解析找到RCTUIManager ,并調(diào)用對應的createView方法的男公,打印了RTCBatchBridge的結構和內(nèi)容發(fā)現(xiàn):
RCT中記錄了幾個主要的數(shù)組荤堪,一組數(shù)組名為 _moduleDataByID,裝了原生最主要的一些處理Module枢赔,其中包括了RCTUIManager 的module澄阳,長度為80,此處RCTUIManager在數(shù)組中的下標為70 糠爬,下文中會用到這個序號寇荧。應該可以猜想到,這個數(shù)組的長度肯定可以變大执隧。
從 RCTBatchBridge 的類結構中可以看到揩抡,記錄Module的數(shù)組中裝的并不是RCTUIManager這些類的實例,而是RCTModuleData的實例镀琉。
作者用 RCTModuleData 對象類來描述了一個 例如RCTUIManager Module的實例峦嗤,記錄了每個Module的class,暴露那些方法(NSArray)屋摔,
這樣RCTBatchBridge就記錄了原生主要的功能Module的表了烁设。
JS要想訪問原生的Module類,一定得利用JSContext钓试,暴露原生方法装黑,所以在RN的工程代碼中搜索關健詞,找到一下一段代碼:
于是在此處斷點弓熏,并重新進入RN頁面恋谭,發(fā)現(xiàn)此處穿戴的參數(shù)call數(shù)字里記錄的都是數(shù)字,例如下圖:
此處圖上對于參數(shù)的解析是猜想挽鞠,這就是最初要特意提到RCTUIManager在 RCTBatchBridge 的module數(shù)組中的下標為70的原因疚颊。
在RCTBatchBridge調(diào)用Module的實現(xiàn)工程中可以看到狈孔,bridge用JS傳代的序號,在自己的module數(shù)組中材义,使用序號作為下標均抽,取出對應的Module,并調(diào)用Module中對應方法列表的序號的function其掂。
那么又有一個問題油挥,原生代碼,為什么可以直接使用js傳代過來的序號呢清寇,或者說喘漏,js怎么知道原生module列表中的序號對應的Module是什么呢?這個可能就需要在js代碼中去找答案了华烟。
首先原生要聲明那些nativeModule要向js暴露翩迈,暴露哪些方法。我們可以先看看RCTUIManager的部分聲明盔夜。
宏聲明導出module
RCT_EXPORT_MODULE()
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
及在+load方法中注冊负饲。
暴露方法
在看原生RCTUIManager的部分export聲明
前面宏生成的方法名前綴都為rct_export,原生使用動態(tài)運行時喂链,找到改Module export的所有方法列表
以上是原生注冊moduleclass和function的注冊部分返十,那么js如何知道原生到底export了那些module呢,包括順序如何獲取呢椭微?
我在RCTBatchBridge的start方法中看到調(diào)用了一個叫injectJSONConfiguration的方法洞坑,方法的是現(xiàn)實
原生調(diào)用了js的 global 對象下注冊了 __fbBatchedBridgeConfig 的對象,對象打印如下:
{"remoteModuleConfig":
[["ExceptionsManager"],
["JSCExecutor"],
["ViewManager"],
["ARTNodeManager"],
["ARTGroupManager"],
["ARTRenderableManager"],
["ARTShapeManager"],
["ARTSurfaceViewManager"],
["ARTTextManager"],
["AccessibilityManager"],
["ActionSheetManager"],
["ActivityIndicatorViewManager"],
["AdSupport"],
["AlertManager"],
["AppState"],
["AssetsLibraryRequestHandler"],
["AsyncLocalStorage"],
["CameraRollManager"],
["Clipboard"],
["DataRequestHandler"],
["DatePickerManager"],
["DeviceInfo"],
["DevLoadingView"],
["DevMenu"],
["DevSettings"],
["EventDispatcher"],
["FileRequestHandler"],
["GIFImageDecoder"],
["HTTPRequestHandler"],
["I18nManager"],
["ImageEditingManager"],
["ImageLoader"],
["ImagePickerIOS"],
["ImageStoreManager"],
["ImageViewManager"],
["JSCSamplingProfiler"],
["KeyboardObserver"],
["LinkingManager"],
["LocalAssetImageLoader"],
["LocationObserver"],
["ModalHostViewManager"],
["NativeAnimatedModule"],
["NavigatorManager"],
["NavItemManager"],
["NetInfo"],
["Networking"],
["PerfMonitor"],
["PhotoLibraryImageLoader"],
["PickerManager"],
["PlatformConstants"],
["ProgressViewManager"],
["PushNotificationManager"],
["RawTextManager"],
["RedBox"],
["RefreshControlManager"],
["ScrollContentViewManager"],
["ScrollViewManager"],
["SegmentedControlManager"],
["SettingsManager"],
["SliderManager"],
["SourceCode"],
["StatusBarManager"],
["SwitchManager"],
["TabBarItemManager"],
["TabBarManager"],
["TextFieldManager"],
["TextManager"],
["TextViewManager"],
["Timing"],
["TVNavigationEventEmitter"],
["UIManager"],
["Vibration"],
["WebSocketExecutor"],
["WebSocketModule"],
["WebViewManager"],
["DevModule"],
["ImagePickerManager"],
["UiModule"],
["NetModule"],
["MapModule"]]}
也就是說蝇率,原生Module在 +load方法中將自己的class向原生RCTModuleClasses靜態(tài)數(shù)組中注冊迟杂,bridge創(chuàng)建啟動時會讀取RCTModuleClasses數(shù)組的值,構建原生希望暴露給JS的所有對象的Module對象數(shù)組本慕,start方法中調(diào)用injectJSONConfiguration方法排拷,將數(shù)據(jù)寫到global對象中,供js調(diào)用锅尘。
下面是NativeModule.js中在global.__fbBatchedBridgeConfig對象中讀取原生module的信息监氢,if分支是安卓的分支,else是iOS的邏輯分支藤违。
這樣便是由原生向js傳帶了NativeModule的順序浪腐。
并且在NativeModule.js中找到了應證:
在上面的js注視信息中說到
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
module的具體信息是懶加載的,用的時候才會去構建顿乒,也就是议街,此時js只拿到了module的classname,如何拿到module的function信息呢淆游?
我在NativeModule.js中找到loadModule的function
function loadModule(name: string, moduleID: number): ?Object {
invariant(global.nativeRequireModuleConfig,
'Can\'t lazily create module without nativeRequireModuleConfig');
const config = global.nativeRequireModuleConfig(name);
const info = genModule(config, moduleID);
return info && info.module;
}
js調(diào)用了glaobal下的nativeRequireModuleConfig的方法傍睹,傳入了ModuleName,那方法實現(xiàn)是什么樣的呢犹菱?
context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid) {
return nil;
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", @{ @"moduleName": moduleName });
NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
return RCTNullIfNil(result);
};
從上面一段代碼中可以看到拾稳,是由原生向js暴露了查詢Module詳細方法列表的方法,返回方法的列表腊脱。
所以js調(diào)用原生Module的方法時傳帶的信息是一串id的數(shù)組了访得。
通過以上的一些嘗試算是明白了RN的大致調(diào)用的實現(xiàn)方式,此處只是淺顯的描述了view的構建過程陕凹。其他Module的調(diào)用原理也是類似的悍抑。