美文网首页移动开发
RN源码探索笔记

RN源码探索笔记

作者: 10m每秒滑行 | 来源:发表于2018-02-24 19:12 被阅读0次

    可能大家都知道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的调用原理也是类似的。

    相关文章

      网友评论

        本文标题:RN源码探索笔记

        本文链接:https://www.haomeiwen.com/subject/smxcxftx.html