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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

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