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

​ 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。

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

推荐阅读更多精彩内容

  • 使用App.png 本文结构 目前App的几种常见的开发模式 关于React-Native的一点小看法 React...
    ZeroJ阅读 5,183评论 0 22
  • React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如...
    零度_不结冰阅读 672评论 0 1
  • 我为什么写这个主题呢,这是因为今天早上有两个同学发烧了,老师说今天这两个同学很可能有腮腺炎的细菌。下午更恐怖...
    破雷神龙阅读 336评论 0 4
  • correct = tf.nn.in_top_k(prediction, target, K): K --- 表示...
    律动的时间线阅读 477评论 0 0
  • 我小的时候是城市里的孩子,但是我却特别喜欢农村,那时候我的暑假寒假基本上都是在乡下度过的,小伙伴们也爱跟我玩,好像...
    安然ZCR阅读 462评论 4 5