美文网首页知识积累iOS开发React Native开发
React Native源码解析-native和js通信

React Native源码解析-native和js通信

作者: Iceguest | 来源:发表于2017-10-11 21:38 被阅读325次

    ​ React Native(以下简称RN)的目标是用基于react的JavaScript写代码,在iOS/Android平台上原生渲染,正如他们的口号"Learn Once,Write anywhere!",只要学会了大前端,iOS/Android/Web通吃,这样就很神奇了,react.js还是那个react.js,模块化、虚拟DOM、JSX语法概念一样没少,甚至可以基于流行的flux单向数据流来架构我们的应用,而在客户端并不是一个web页面,而是纯原生渲染,性能比纯web页提升很多,而且还顺带具有像web页一样的动态更新能力。

    ​ 随着版本的迭代更新,RN功能和相关特性也越来越多,代码复杂度也随之上升,这里先不论RN的争议和发展趋势,而来学习下其优秀的架构设计和代码实现。我们就用一个最简单的项目来进行剖析,运行命令react-native init RNDemo,这里我的RN版本号为:0.47.0,RN最核心的当属js与native的通信机制,理解了这套机制则比较容易理解RN整个架构。

    Native模块

    RCTBridgeModule

    native导出给js的类称之为模块类,如RCTUIManager RCTTiming等,每个模块类都实现了RCTBridgeModule协议

    @protocol RCTBridgeModule <NSObject>
    
    #define RCT_EXPORT_MODULE(js_name) \
    RCT_EXTERN void RCTRegisterModule(Class); \
    + (NSString *)moduleName { return @#js_name; } \
    + (void)load { RCTRegisterModule(self); }
    
    // Implemented by RCT_EXPORT_MODULE
    + (NSString *)moduleName;
    
    @optional
    
    @property (nonatomic, weak, readonly) RCTBridge *bridge;
    @property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
    
    #define RCT_EXPORT_METHOD(method) \
      RCT_REMAP_METHOD(, method)
    
    #define RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(method) \
      RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(, method)
    
    #define RCT_REMAP_METHOD(js_name, method) \
      _RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
      - (void)method;
      
    #define RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(js_name, method) \
      _RCT_EXTERN_REMAP_METHOD(js_name, method, YES) \
      - (id)method;
      
    #define RCT_EXTERN_MODULE(objc_name, objc_supername) \
      RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
    
    #define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
      objc_name : objc_supername \
      @end \
      @interface objc_name (RCTExternModule) <RCTBridgeModule> \
      @end \
      @implementation objc_name (RCTExternModule) \
      RCT_EXPORT_MODULE(js_name)
      
    #define RCT_EXTERN_METHOD(method) \
      _RCT_EXTERN_REMAP_METHOD(, method, NO)
    
    #define RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(method) \
      _RCT_EXTERN_REMAP_METHOD(, method, YES)
    
    #define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
      + (NSArray *)RCT_CONCAT(__rct_export__, \
        RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
        return @[@#js_name, @#method, @is_blocking_synchronous_method]; \
      }
      
    - (NSArray<id<RCTBridgeMethod>> *)methodsToExport;
    - (NSDictionary<NSString *, id> *)constantsToExport;
    - (void)batchDidComplete;
    - (void)partialBatchDidFlush;
    
    @end
    

    该协议定义了模块导出方法、导出常量、模块运行队列等,还定义了很多宏。

    RCT_EXPORT_MODULE

    模块类被加载进runtime的时候,执行load方法,将该模块类的Class信息添加到一个全局数组RCTModuleClasses里去,RCTGetModuleClasses()方法可获取该数组。

    RCT_EXPORT_METHOD(method)

    RN在e9095b2版本移除了bang神博客所说的从data数据段获取导出方法的黑魔法,而是给每个导出方法添加一个对应的方法。

    比如method为doSomething,则宏展开后为

    + (NSArray *)__rct_export__(行号和系统计数){ 
        return @[@"", @"doSomething", @(NO)];
      }
    - (void)doSomething;
    

    RCTModuleData方法- (NSArray<id<RCTBridgeMethod>> *)methods,通过遍历模块运行时方法列表,找到有__rct_export__前缀的方法,根据方法返回的数组实例化RCTModuleMethod,从而收集到所有RCT_EXPORT_METHOD对应的导出方法。

    模块配置

    所有的模块配置存放在ModuleRegistryC++类中,JSCExecutorgetNativeModule方法可获得指定模块的配置,最终是从ModuleRegistry类方法getConfig拿到。JSCNativeModules管理Native的导出模块,JSCNativeModules构造函数传入JsToNativeBridgegetModuleRegistry方法返回的ModuleRegistry指针,JsToNativeBridge管理js调用Native所需配置、方法等,是js调用native的native响应方。

    RCTAppState模块为例,拿到的模块信息如下:

    struct ModuleConfig {
      size_t index;
      folly::dynamic config;
    };
    {
      21;
      [AppState,{initialAppState:unknown},[getCurrentAppState,addListener,removeListeners]];
    }
    

    index是模块index,config动态数组依次存放moudlename、export constants、export methodNames array 、promiseMethodId array 、syncMethodId array。

    Native模块生成

    Native模块初始化

    在应用启动delegate里,创建了一个RCTRootView实例,这个实例初始化了RCTBridge实例,在其- (void)setUp方法里:

    self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
    [self.batchedBridge start];
    

    self.batchedBridgeRCTCxxBridge的实例,用于批量桥接,之前版本的RCTBatchedBridge已不再实现,在- (void)start方法主要步骤是:

    • 发送js即将加载通知
    • 创建常驻线程_jsThread,native和js互相调用默认会在该线程执行,也可以自己指定模块的运行队列

      _jsThread = [[NSThread alloc] initWithTarget:self
                                            selector:@selector(runJSRunLoop)
                                              object:nil];
      _jsThread.name = RCTJSThreadName;
      _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
      [_jsThread start];
      
    • 初始化所有native modules

      - (void)_initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
      {
        ...
        NSArray<id<RCTBridgeModule>> *extraModules = nil;
        if (self.delegate) {
          if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
            extraModules = [self.delegate extraModulesForBridge:_parentBridge];
          }
        } else if (self.moduleProvider) {
          extraModules = self.moduleProvider();
        }
        ...
        NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
        NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
        NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
        ...
        // Set up moduleData for pre-initialized module instances
        for (id<RCTBridgeModule> module in extraModules) {
          Class moduleClass = [module class];
          NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
      
          if (RCT_DEBUG) {
            ...
          }
      
          // Instantiate moduleData container
          RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
                                                                             bridge:self];
          moduleDataByName[moduleName] = moduleData;
          [moduleClassesByID addObject:moduleClass];
          [moduleDataByID addObject:moduleData];
        }
        ...
        // Set up moduleData for automatically-exported modules
        for (Class moduleClass in RCTGetModuleClasses()) {
          NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
          
          if ([moduleName isEqual:@"RCTJSCExecutor"]) {
            continue;
          }
      
          // Check for module name collisions
          RCTModuleData *moduleData = moduleDataByName[moduleName];
          if (moduleData) {
            if (moduleData.hasInstance) {
              // Existing module was preregistered, so it takes precedence
              continue;
            } else if ([moduleClass new] == nil) {
              // The new module returned nil from init, so use the old module
              continue;
            } else if ([moduleData.moduleClass new] != nil) {
              // Both modules were non-nil, so it's unclear which should take precedence
              RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
                          "name '%@', but name was already registered by class %@",
                          moduleClass, moduleName, moduleData.moduleClass);
            }
          }
      
          // Instantiate moduleData
          // TODO #13258411: can we defer this until config generation?
          moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
                                                           bridge:self];
          moduleDataByName[moduleName] = moduleData;
          [moduleClassesByID addObject:moduleClass];
          [moduleDataByID addObject:moduleData];
        }
        ...
      
        // Store modules
        _moduleDataByID = [moduleDataByID copy];
        _moduleDataByName = [moduleDataByName copy];
        _moduleClassesByID = [moduleClassesByID copy];
        ...
        // Dispatch module init onto main thead for those modules that require it
        for (RCTModuleData *moduleData in _moduleDataByID) {
          if (moduleData.hasInstance &&
              (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
            (void)[moduleData instance];
          }
        }
        ...
        // From this point on, RCTDidInitializeModuleNotification notifications will
        // be sent the first time a module is accessed.
        _moduleSetupComplete = YES;
      
        [self _prepareModulesWithDispatchGroup:dispatchGroup];
      
        ...
      }
      

      主要步骤:

      1. 得到bridgeModule数组extraModules

        从delegate实现方或传入的moduleProvider属性获取extraModules,这里两者都为空

      2. 实例化moduleClassesByID Class数组,moduleDataByID RCTModuleData*模块数据数组,moduleDataByName名字模块数据字典,临时保存,作用见名思义

      3. 遍历extraModules,填充第二步数组和字典

      4. RCTGetModuleClasses()得到声明了RCT_EXPORT_MODULE的所有模块Class,遍历数组,先检查命名冲突,再以moduleClass为参数实例化RCTModuleData,然后填充第二步数组和字典

        RCTModuleData类管理导出给js的模块数据,包括Class信息,导出方法,导出常量等

      5. 遍历_moduleDataByID,调用RCTModuleData对应实例的instance方法,初始化RCTModuleData

      6. 执行_prepareModulesWithDispatchGroup方法,初始化除白名单外的模块导出常量

    • 实例化Instance类,该类在下文有介绍

      _reactInstance.reset(new Instance);
      
    • 实例化抽象工厂类JSExecutorFactory

      __weak RCTCxxBridge *weakSelf = self;
        std::shared_ptr<JSExecutorFactory> executorFactory;
        if (!self.executorClass) {
          BOOL useCustomJSC =
            [self.delegate respondsToSelector:@selector(shouldBridgeUseCustomJSC:)] &&
            [self.delegate shouldBridgeUseCustomJSC:self];
          // The arg is a cache dir.  It's not used with standard JSC.
          executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object
            ("UseCustomJSC", (bool)useCustomJSC)
      #if RCT_PROFILE
            ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch)
      #endif
          ));
        } else {
          id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass];
          executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) {
            if (error) {
              [weakSelf handleError:error];
            }
          }));
        }
      

      本地JavaScriptCore运行会实例化JSCExecutorFactory, 浏览器远程调试模式会实例RCTObjcExecutorFactory,这里我们就以JSCExecutorFactory为例分析,远程调试会在另一篇做分析。

    • _jsThread线程上初始化桥接

      - (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
      {
        if (!self.valid) {
          return;
        }
      
        RCTAssertJSThread();
        __weak RCTCxxBridge *weakSelf = self;
        _jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
          if (error) {
            [weakSelf handleError:error];
          }
        });
      
        RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil);
        // This can only be false if the bridge was invalidated before startup completed
        if (_reactInstance) {
          // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
          _reactInstance->initializeBridge(
            std::unique_ptr<RCTInstanceCallback>(new RCTInstanceCallback(self)),
            executorFactory,
            _jsMessageThread,
            [self _buildModuleRegistry]);
      
      #if RCT_PROFILE
          ...
      #endif
        }
      
        RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
      }
      

      主要步骤:

      1. 实例化RCTMessageThread类,RCTMessageThread类封装了在_jsThread常驻线程上的同步和异步执行任务的方法。
      2. Instance实例_reactInstance执行了初始化方法void initializeBridge(..), 注入了所需依赖,Instance是iOS/Android与javacriptCore交互的入口类。初始化了模块注册表ModuleRegistry实例,ModuleRegistry是C++类,ios/android均需填充模块信息数组
    • 加载jsbundle源文件

      RCTJavaScriptLoader封装了加载jsbundle文件的方法,主要步骤是需要下载则通过RCTMultipartDataTask下载js文件,最后返回NSData数据

    • 执行解析jsbundle

      dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
            RCTCxxBridge *strongSelf = weakSelf;
            if (sourceCode && strongSelf.loading) {
              [strongSelf executeSourceCode:sourceCode sync:NO];
            }
          });
      

      在上述组任务结束时,收到通知,在最高等级全局队列执行加载完的jsbundle

      - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
      {
        // This will get called from whatever thread was actually executing JS.
        dispatch_block_t completion = ^{
          // Flush pending calls immediately so we preserve ordering
          [self _flushPendingCalls];
      
          // Perform the state update and notification on the main thread, so we can't run into
          // timing issues with RCTRootView
          dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter]
             postNotificationName:RCTJavaScriptDidLoadNotification
             object:self->_parentBridge userInfo:@{@"bridge": self}];
      
            // Starting the display link is not critical to startup, so do it last
            [self ensureOnJavaScriptThread:^{
              // Register the display link to start sending js calls after everything is setup
              [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
            }];
          });
        };
      
        if (sync) {
          [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
          completion();
        } else {
          [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
        }
      
      #if RCT_DEV
        ...
      #endif
      }
      

      主要步骤:

      1. 清空调用队列_pendingCalls_pendingCount置0

      2. 发送js加载完毕通知,_jsThread线程,设置RCTDisplayLink监测js线程帧率,原理在另一篇作分析

      3. 执行解析jsbundle,这里是异步执行

        执行解析中间过程为了解耦和扩展性,引入了NativeToJsBridge等类,层次很多,可能看到这就有点晕了,来看下这块的执行过程。

    jsBundle执行

    首先,上文提到的JSCExecutorFactory,运用了标准的工厂模式:

    JSCExecutor是本地JavaScriptCore具体执行的产品类,是跨平台的C++类,是一个非常重要的类。

    JSCExecutor构造时向JSContext注入了全局函数

    installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
    installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
    ...
    installGlobalProxy(m_context, "nativeModuleProxy",
                           exceptionWrapMethod<&JSCExecutor::getNativeModule>());
    ...
    installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");                     
    

    installNativeHook方法同时注册了回调函数exceptionWrapMethod<method>(),当JS端调用对应方法,exceptionWrapMethod全局函数被调用。

    MessageQueueThreadRCTMessageThread的抽象基类,封装了在消息队列里同步和异步执行的方法,MessageQueueThread是需要各平台各自实现的。

    class MessageQueueThread {
     public:
      virtual ~MessageQueueThread() {}
      virtual void runOnQueue(std::function<void()>&&) = 0;
      // runOnQueueSync and quitSynchronous are dangerous.  They should only be
      // used for initialization and cleanup.
      virtual void runOnQueueSync(std::function<void()>&&) = 0;
      // Once quitSynchronous() returns, no further work should run on the queue.
      virtual void quitSynchronous() = 0;
    };
    

    Instance类依赖了很多类,它是一个C++类,是iOS/Android与javacriptCore交互的入口类,封装了native与js的交互,包括解析js字符流,调用js方法,设置jscontext全局变量,发出回调等。

    class RN_EXPORT Instance {
     public:
      ~Instance();
      void initializeBridge(
        std::unique_ptr<InstanceCallback> callback,
        std::shared_ptr<JSExecutorFactory> jsef,
        std::shared_ptr<MessageQueueThread> jsQueue,
        std::shared_ptr<ModuleRegistry> moduleRegistry);
    
      void setSourceURL(std::string sourceURL);
    
      void loadScriptFromString(
        std::unique_ptr<const JSBigString> string,
        std::string sourceURL,
        bool loadSynchronously);
      void loadUnbundle(
        std::unique_ptr<JSModulesUnbundle> unbundle,
        std::unique_ptr<const JSBigString> startupScript,
        std::string startupScriptSourceURL,
        bool loadSynchronously);
      bool supportsProfiling();
      void startProfiler(const std::string& title);
      void stopProfiler(const std::string& title, const std::string& filename);
      void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
      void *getJavaScriptContext();
      void callJSFunction(std::string&& module, std::string&& method, folly::dynamic&& params);
      void callJSCallback(uint64_t callbackId, folly::dynamic&& params);
    
      // This method is experimental, and may be modified or removed.
      template <typename T>
      Value callFunctionSync(const std::string& module, const std::string& method, T&& args) {
        CHECK(nativeToJsBridge_);
        return nativeToJsBridge_->callFunctionSync(module, method, std::forward<T>(args));
      }
    
      #ifdef WITH_JSC_MEMORY_PRESSURE
      void handleMemoryPressure(int pressureLevel);
      #endif
    
     private:
      void callNativeModules(folly::dynamic&& calls, bool isEndOfBatch);
      void loadApplication(
        std::unique_ptr<JSModulesUnbundle> unbundle,
        std::unique_ptr<const JSBigString> startupScript,
        std::string startupScriptSourceURL);
      void loadApplicationSync(
        std::unique_ptr<JSModulesUnbundle> unbundle,
        std::unique_ptr<const JSBigString> startupScript,
        std::string startupScriptSourceURL);
    
      std::shared_ptr<InstanceCallback> callback_;
      std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
      std::shared_ptr<ModuleRegistry> moduleRegistry_;
    
      std::mutex m_syncMutex;
      std::condition_variable m_syncCV;
      bool m_syncReady = false;
    };
    

    Instance类依赖的InstanceCallback JSExecutorFactory MessageQueueThread ModuleRegistry等类,都需要平台各自实现,Instance是作为一个跨平台的接口封装类,如iOS中,callback成员变量是指向InstanceCallback的子类RCTInstanceCallbackjsef成员则动态指向JSExecutorFactory的具体工厂类,JSExecutorFactory则可以选择对应的具体产品类。jsQueue指向RCTMessageThread类,moduleRegistry需要各平台自行填充。Instance类函数实现基本都是通过NativeToJsBridge具体实现的,在Instance执行initializeBridge时,在MessageQueueThread同步初始化了NativeToJsBridge的实例nativeToJsBridge_,那么我们来看看NativeToJsBridge类。

    ​ 在RCTCxxBridge类的enqueueApplicationScript:url:onComplete:方法,根据jsbundle类型去执行对应的方法。jsbundle目前有三种类型:String RAMBundle BCBundleString表示普通jsbundle,用bundle命令整合出来的。RAMBundle是用unbundle命令打出来的bundle,它除了生成整合的js文件index.ios.bundle 外,还会生成各个单独的未整合js文件,全部放在js-modules目录下, bundle头四个字节固定为0xFB0BD1E5BCBundle是js字节码bundle类型,并未用到,就以普通jsbundle为例

    self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
                                                     sourceUrlStr.UTF8String, false);
    

    核心是通过Instance实例去执行解析, InstanceloadScriptFromString方法调用到NativeToJsBridgeloadApplicationloadApplicationSync的方法,NativeToJsBridge实例在Instance类构造的时候在_jsThread线程初始化。

    void NativeToJsBridge::loadApplication(
        std::unique_ptr<JSModulesUnbundle> unbundle,
        std::unique_ptr<const JSBigString> startupScript,
        std::string startupScriptSourceURL) {
      runOnExecutorQueue(
          [unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
           startupScript=folly::makeMoveWrapper(std::move(startupScript)),
           startupScriptSourceURL=std::move(startupScriptSourceURL)]
            (JSExecutor* executor) mutable {
        auto unbundle = unbundleWrap.move();
        if (unbundle) {
          executor->setJSModulesUnbundle(std::move(unbundle));
        }
        executor->loadApplicationScript(std::move(*startupScript),
                                        std::move(startupScriptSourceURL));
      });
    }
    

    普通jsbundle调用到JSExecutor子类loadApplicationScript方法,以子类JSCExecutor为例:

    void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
      SystraceSection s("JSCExecutor::loadApplicationScript",
                        "sourceURL", sourceURL);
    
      std::string scriptName = simpleBasename(sourceURL);
      ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
      String jsSourceURL(m_context, sourceURL.c_str());
    
      // TODO t15069155: reduce the number of overrides here
    #ifdef WITH_FBJSCEXTENSIONS
      ...
    #elif defined(__APPLE__)
      BundleHeader header;
      memcpy(&header, script->c_str(), std::min(script->size(), sizeof(BundleHeader)));
      auto scriptTag = parseTypeFromHeader(header);
    
      if (scriptTag == ScriptTag::BCBundle) {
        using file_ptr = std::unique_ptr<FILE, decltype(&fclose)>;
        file_ptr source(fopen(sourceURL.c_str(), "r"), fclose);
        int sourceFD = fileno(source.get());
    
        JSValueRef jsError;
        JSValueRef result = JSC_JSEvaluateBytecodeBundle(m_context, NULL, sourceFD, jsSourceURL, &jsError);
        if (result == nullptr) {
          throw JSException(m_context, jsError, jsSourceURL);
        }
      } else
    #endif
      {
        String jsScript;
        {
          SystraceSection s_("JSCExecutor::loadApplicationScript-createExpectingAscii");
          ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_START);
          jsScript = adoptString(std::move(script));
          ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP);
        }
        #ifdef WITH_FBSYSTRACE
        fbsystrace_end_section(TRACE_TAG_REACT_CXX_BRIDGE);
        #endif
    
        SystraceSection s_("JSCExecutor::loadApplicationScript-evaluateScript");
        evaluateScript(m_context, jsScript, jsSourceURL);
      }
    
      flush();
    
      ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
      ReactMarker::logMarker(ReactMarker::RUN_JS_BUNDLE_STOP);
    }
    

    该方法主要步骤:

    1. 根据JSBundle header得到JSBundle类型来执行对应的js上下文。

      evaluateScript是由jshelps组件封装的,最终调用到JavaScriptCore的方法JSEvaluateScriptjschelps主要封装了JavaScriptCore的相关函数,以及JSStringRef的C++对象封装String类,JSObjectRef的C++对象封装Object类,JSValueRef的C++对象封装Value类等。

    2. 调用flush()

      native调用jsflushedQueue方法,返回js端待调用方法队列,然后native执行,清空该队列

    至此,Native模块生成和相关准备工作完成,模块配置存放在ModuleRegistry类中。

    JS模块

    本地jscore运行时,当js需要调用到native模块的时候,通过nativeModuleProxy执行native所注入方法,返回对应的模块信息,而当远程调试模式时,native向global.__fbBatchedBridgeConfig注入了所有模块列表信息,同样是由native端生成的,如下:

    type ModuleConfig = [
      string, /* name */
      ?Object, /* constants */
      Array<string>, /* functions */
      Array<number>, /* promise method IDs */
      Array<number>, /* sync method IDs */
    ];
    
    globalconfig

    在JS中同样也存在供native调用的模块,node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js中,模块保存在_lazyCallableModules中。

    js modules

    JS模块生成

    registerCallableModule(name: string, module: Object) {
        this._lazyCallableModules[name] = () => module;
      }
    
      registerLazyCallableModule(name: string, factory: void => Object) {
        let module: Object;
        let getValue: ?(void => Object) = factory;
        this._lazyCallableModules[name] = () => {
          if (getValue) {
            module = getValue();
            getValue = null;
          }
          return module;
        };
      }
    

    通过外部注册,填充_lazyCallableModules数组,

    Native 调用 JS

    在RN里,封装了底层细节,外部暴露出的是通过RCTCxxBridge方法enqueueJSCall:method:args:completion调用,如native向js发送时间消息的方法sendEventWithName:body实现就是调用该方法。该方法实现如下:

    - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
    {
      if (!self.valid) {
        return;
      }
    
      /**
       * AnyThread
       */
      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil);
    
      RCTProfileBeginFlowEvent();
      [self _runAfterLoad:^{
        RCTProfileEndFlowEvent();
    
        if (self->_reactInstance) {
          self->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
                                               [RCTConvert folly_dynamic:args ?: @[]]);
    
          // ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
          // the block is invoked after callJSFunction
          if (completion) {
            if (self->_jsMessageThread) {
              self->_jsMessageThread->runOnQueue(completion);
            } else {
              RCTLogWarn(@"Can't invoke completion without messageThread");
            }
          }
        }
      }];
    
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
    }
    

    主要通过调用Instance类的callsJSFunction方法,最终调用到JSCExecutor::callFunction方法

    void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
      SystraceSection s("JSCExecutor::callFunction");
    
      // This weird pattern is because Value is not default constructible.
      // The lambda is inlined, so there's no overhead.
      auto result = [&] {
        try {
          if (!m_callFunctionReturnResultAndFlushedQueueJS) {
            bindBridge();
          }
          return m_callFunctionReturnFlushedQueueJS->callAsFunction({
            Value(m_context, String::createExpectingAscii(m_context, moduleId)),
            Value(m_context, String::createExpectingAscii(m_context, methodId)),
            Value::fromDynamic(m_context, std::move(arguments))
          });
        } catch (...) {
          std::throw_with_nested(
            std::runtime_error("Error calling " + moduleId + "." + methodId));
        }
      }();
    
      callNativeModules(std::move(result));
    }
    

    callFunction方法先执行js端方法callFunctionReturnFlushedQueue(在MessageQueue.js文件中),返回js端消息队列,然后native解析队列,即调用callNativeModules,这个过程在下文JS调用Native有分析。

    总体来说还是使用JSCHelpers中封装的C++方法evaluateScript(JSContextRef, JSStringRef, JSStringRef),在常驻线程来执行js语句,返回结果native解析。

    JS调用Native

    node_modules/react-native/Libraries/BatchedBridge/NativeModules.js文件中:

    let NativeModules : {[moduleName: string]: Object} = {};
    if (global.nativeModuleProxy) {
      NativeModules = global.nativeModuleProxy;
    } else {
      ...
    }
     module.exports = NativeModules;
    

    本地JavascriptCore执行时,nativeModuleProxy全局函数在JSCExecutor构造时,通过installGlobalProxy方法注入了,这里的else分支是浏览器远程调试走的。当取nativeModuleProxy属性,如执行const RCTAppState = NativeModules.AppState;JSObjectGetPropertyCallback回调在C++端被触发,调用到JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName)方法,该方法通过JSCNativeModulesgetModule方法拿到native对应配置,如第一节​模块配置中拿到对应的配置表。

    ​ js端也有 有BatchedBridge概念,node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js中,const BatchedBridge = new MessageQueue();BatchedBridge对象实际上是MessageQueue的实例,转到当前目录下的MessageQueue.js文件。

    ​ js需要调用native方法的时候,调用enqueueNativeCall函数,比如js端执行方法:

    UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload)
    

    这段代码是在ReactNativeStack-dev.js中,用于js端通告native创建视图,UIManager实际是NativeModules对象,本地JavacriptCore运行时,NativeModules对象方法在native的JSCExecutor::getNativeModule方法中通过调用js方法global.__fbGenNativeModule建立,global.__fbGenNativeModule即指向genModule方法对象,genModule方法中调用genMethod,genMethod中持有闭包,将native方法调用通过BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);方法加入队列处理,故上述方法调用最终通过enqueueNativeCall调用。

    enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
        if (onFail || onSucc) {
          if (__DEV__) {
            ...
          }
          // Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
          // to indicate fail (0) or success (1)
          onFail && params.push(this._callID << 1);
          onSucc && params.push((this._callID << 1) | 1);
          this._successCallbacks[this._callID] = onSucc;
          this._failureCallbacks[this._callID] = onFail;
        }
    
        if (__DEV__) {
          ...
        }
        this._callID++;
    
        this._queue[MODULE_IDS].push(moduleID);
        this._queue[METHOD_IDS].push(methodID);
    
        if (__DEV__) {
          ...
        }
        this._queue[PARAMS].push(params);
    
        const now = new Date().getTime();
        if (global.nativeFlushQueueImmediate &&
            (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
             this._inCall === 0)) {
          var queue = this._queue;
          this._queue = [[], [], [], this._callID];
          this._lastFlush = now;
          global.nativeFlushQueueImmediate(queue);
        }
        Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
        if (__DEV__ && this.__spy && isFinite(moduleID)) {
          ...
        } else if (this.__spy) {
          this.__spy({type: TO_NATIVE, module: moduleID + '', method: methodID, args: params});
        }
      }
    

    enqueueNativeCall_queue依次插入moduleID methodID params ,flushedQueue方法会把当前的_callID插入到_queue最后,紧接着判断相邻两次flushQueue时间超过MIN_TIME_BETWEEN_FLUSHES_MS即5ms,或者当前没有正在处理的方法,则执行全局nativeFlushQueueImmediate函数。nativeFlushQueueImmediate函数传入_queue参数,它在native端之前通过installNativeHook注入了,js端调用后native端收到函数回调,最终对应执行JSCExecutor类的nativeFlushQueueImmediate方法,该方法最终调用到JsToNativeBridgecallNativeModules方法,callNativeModules解析出js透传的参数_queue,然后动态调用方法。

    for (auto& call : parseMethodCalls(std::move(calls))) {
          m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
        }
    

    m_registry是ModuleRegistry的实例

    void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
      if (moduleId >= modules_.size()) {
        throw std::runtime_error(
          folly::to<std::string>("moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
      }
      modules_[moduleId]->invoke(methodId, std::move(params), callId);
    }
    

    invoke方法即以反射去动态执行方法,具体执行方法各平台各自实现,iOS上实际执行的类是RCTNativeModule,它的invoke方法在参数对应的module指定线程队列执行invokeInner方法,然后经过转换参数等操作,最终调用到RCTModuleMethod类的invokeWithBridge:module:arguments方法,通过NSInvocationinvokeWithTarget方法实现动态调用,并返回调用结果,中间经过了处理method name, methodSignature等过程,此处代码可浏览RCTModuleMethod的类实现。另外在processMethodSignature方法中,将cbID和返回结果暂存,调用成功通过JSCExecutorm_invokeCallbackAndReturnFlushedQueueJS属性 ,调用到js里MessageQueue类的invokeCallbackAndReturnFlushedQueue方法,js端拿到返回值,js调用native的闭环形成。

    ​ 那么还有一个问题,js只是把消息加入了队列,js什么时候去让native去取js的消息队列处理?

    1. js端超时机制

      需要注意的是,远程调试模式并没有超时机制,global.nativeFlushQueueImmediate始终是 undefined的。

      每次消息入队的时候,会检查距离上次队列清空完成是否超过5ms,超过则调用nativeFlushQueueImmediate 清空队列,native注册回调被调用,否则立即入队,由于js是单线程的,5ms内也不会积压很多消息,所以不用担心处理效率问题。

    2. native主动调用

      native调用js方法,native调用enqueueJSCall:method:args:completion方法会取到js消息队列,其实包含

        folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS;
        folly::Optional<Object> m_callFunctionReturnFlushedQueueJS;
        folly::Optional<Object> m_flushedQueueJS;
        folly::Optional<Object> m_callFunctionReturnResultAndFlushedQueueJS;
      

      处理方法都会返回js消息队列,即native每次调用js,都会主动去取js队列,比如事件消息、timer等。

    综上所述,js调用native实际上是有两种机制的:

    1. native向jscontext的 global注入全局对象,同时注册相应的回调,如nativeFlushQueueImmediate,js函数被调用,对应native回调被响应
    2. js组成消息队列,native调用flushedQueue主动去取

    第一种是JSPatch所采用的,不过它注册的回调是一个block, 第二种机制是最复杂的,对于模块,需要两端维护一份配置表,但是最高效的,js方需要执行native方法,仅需传递moduleId methodId arguments必要参数给native,而方法真正执行是在native方异步执行的,返回结果异步返回给js方,如果换成方式1,native方法在jscontext同步执行,明显影响效率,而且 当短时间内有很多条消息,JS并不会去频繁调用native,会在5ms内去累积消息,然后发送给native。

    相关文章

      网友评论

        本文标题:React Native源码解析-native和js通信

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