RN源码探索笔记

可能大家都知道RN的实现机制主要是两个常驻线程,一个JS线程,一个UI线程,相互通信,js处理计算,给主线程发消息渲染。基于这一点的基础上探索RN的实现细节。

首先因为不清楚RN工程中如何将JS标签转变成原生的RCTView,所以想到断点调试数据,看调用顺序是怎样的。因为涉及线程频繁的切换,断点调试的探索过程还是花费了较久的时间。

由于断点了RCTView 的init 方法发现,所有的视图创建都是有RCTUIManager调用createView方法,传代一个tag标志,一个props字典创建的view。现在问题是谁调用了RCTUIManager呢,又是如何调用的呢,看程序的调用栈发现,是由RCTBatchBridge 解析找到RCTUIManager ,并调用对应的createView方法的,打印了RTCBatchBridge的结构和内容发现:

RCT中记录了几个主要的数组,一组数组名为 _moduleDataByID,装了原生最主要的一些处理Module,其中包括了RCTUIManager 的module,长度为80,此处RCTUIManager在数组中的下标为70 ,下文中会用到这个序号。应该可以猜想到,这个数组的长度肯定可以变大。


RN源码探索笔记_第1张图片
RCTBatchBridge类结构.png

从 RCTBatchBridge 的类结构中可以看到,记录Module的数组中装的并不是RCTUIManager这些类的实例,而是RCTModuleData的实例。
作者用 RCTModuleData 对象类来描述了一个 例如RCTUIManager Module的实例,记录了每个Module的class,暴露那些方法(NSArray),


RCTModuleData部分属性.png

这样RCTBatchBridge就记录了原生主要的功能Module的表了。

JS要想访问原生的Module类,一定得利用JSContext,暴露原生方法,所以在RN的工程代码中搜索关健词,找到一下一段代码:


RN源码探索笔记_第2张图片
原生对js暴露的方法呼叫function

于是在此处断点,并重新进入RN页面,发现此处穿戴的参数call数字里记录的都是数字,例如下图:


RN源码探索笔记_第3张图片
JS呼叫原生传代的方法

此处图上对于参数的解析是猜想,这就是最初要特意提到RCTUIManager在 RCTBatchBridge 的module数组中的下标为70的原因。

在RCTBatchBridge调用Module的实现工程中可以看到,bridge用JS传代的序号,在自己的module数组中,使用序号作为下标,取出对应的Module,并调用Module中对应方法列表的序号的function。


RN源码探索笔记_第4张图片
RCTBatchBridge 调用Module.png

那么又有一个问题,原生代码,为什么可以直接使用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声明


RN源码探索笔记_第5张图片
OCExportFuncs.png

前面宏生成的方法名前缀都为rct_export,原生使用动态运行时,找到改Module export的所有方法列表

RN源码探索笔记_第6张图片
export方法查找.png

以上是原生注册moduleclass和function的注册部分,那么js如何知道原生到底export了那些module呢,包括顺序如何获取呢?

我在RCTBatchBridge的start方法中看到调用了一个叫injectJSONConfiguration的方法,方法的是现实


RN源码探索笔记_第7张图片
injectJSONConfiguration实现.png

原生调用了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静态数组中注册,bridge创建启动时会读取RCTModuleClasses数组的值,构建原生希望暴露给JS的所有对象的Module对象数组,start方法中调用injectJSONConfiguration方法,将数据写到global对象中,供js调用。

下面是NativeModule.js中在global.__fbBatchedBridgeConfig对象中读取原生module的信息,if分支是安卓的分支,else是iOS的逻辑分支。

RN源码探索笔记_第8张图片
global.__fbBatchedBridgeConfig.png

这样便是由原生向js传带了NativeModule的顺序。
并且在NativeModule.js中找到了应证:


RN源码探索笔记_第9张图片
NativeModule.js.png

在上面的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调用了glaobal下的nativeRequireModuleConfig的方法,传入了ModuleName,那方法实现是什么样的呢?

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调用原生Module的方法时传带的信息是一串id的数组了。

通过以上的一些尝试算是明白了RN的大致调用的实现方式,此处只是浅显的描述了view的构建过程。其他Module的调用原理也是类似的。

你可能感兴趣的:(RN源码探索笔记)