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 ,下文中会用到这个序号。应该可以猜想到,这个数组的长度肯定可以变大。


RCTBatchBridge类结构.png

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


RCTModuleData部分属性.png

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

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


原生对js暴露的方法呼叫function

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


JS呼叫原生传代的方法

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

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


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声明


OCExportFuncs.png

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

export方法查找.png

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

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


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的逻辑分支。

global.__fbBatchedBridgeConfig.png

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


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的调用原理也是类似的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,663评论 25 708
  • 本篇文章是讲述 iOS 无埋点数据收集 SDK 系列的第二篇。在第一篇 中主要介绍了 SDK 整体实现思路以及...
    zerygao阅读 12,288评论 4 64
  • 1.总论 1.怎样理解公共空间、城市公共空间、城市空间、开放空间和外部空间等概念以及它们之间的关系? 公共空间:有...
    Sriyhan阅读 543评论 0 2
  • 笑来老师在这篇里给我们讲了绝大多数人都会遇到的那三个大坑(凑热闹、随大流、操别人的心),不想把话说满,但确实几乎无...
    照进来的光阅读 237评论 0 1
  • 梦里梦见梦是梦,心里心念心是心。爱留爱意爱是爱。空留空白空是空。 如果有一天,我老了,还是空无一人,你懂我的寂寞就...
    图格那黎99阅读 810评论 2 15