ReactNative源码分析 - 通信机制

1.ReactNative源码分析 - 概述
2.ReactNative源码分析 - JavaScriptCore C语言篇
3.ReactNative源码分析 - 启动流程
4.ReactNative源码分析 - 通信机制
5.ReactNative源码分析 - 渲染原理

  • 0、前言
  • 一、Native&JS底层通信桥如何搭建
    • 1.原生端底层通信桥搭建
    • 2.原生模块信息如何导入到JS端
    • 3.JS端底层通信桥搭建
  • 二、Native call JS
  • 三、JS call Native

0、前言

  • 本文主要分析ReactNative框架中Native&JS的通信机制,主要包括以下三个部分:
    • 1.Native&JS底层通信桥如何搭建;
    • 2.JS call Native调用流程;
    • 3.Native call JS调用流程;

通信机制的分析贯穿ReactNative框架:JS层 》 C++公共层 》OC层,函数调用流程相对较长。

一、Native&JS底层通信桥如何搭建

1.原生端底层通信桥搭建

  • Native&JS交互最终通过JSCRuntime来实现,基本原理在JavaScriptCore C语言篇分析过。
    JSCRuntime中Native&JS交互都是在JS Context全局对象global上做文章:原生端给global对象设置属性供JS端使用,JS端给global对象设置属性供原生端使用。global对象是最根本的桥接对象。

  • 启动流程篇在最后执行js bundle代码之前,先向JS端(global对象)注入原生对象作为交互代理,作用分别是:

    • 1.原生模块信息代理nativeModuleProxy,用于JS端获取原生模块信息。
    • 2.原生函数调用代理nativeFlushQueueImmediate,用于JS端调用原生模块函数。
    • 3.原生“同步”函数调用代理nativeCallSyncHook,用于JS端同步调用原生模块函数(同步函数几乎不会用到,整个ReactNative框架只用到一次,不用关注)
  • 1.原生模块信息代理nativeModuleProxy
    向JS端注入原生模块信息代理nativeModuleProxy。它关联原生对象(结构体)HostObjectProxy,JS端获取原生模块信息是,触发取值回调,最终会触发NativeModuleProxy获取原生模块信息

// JSIExecutor.cpp
void JSIExecutor::loadApplicationScript(...) {
  // 1.向JS端注入原生模块代理NativeModuleProxy
  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(*this)));
 ....
}
// JSCRuntime.cpp
jsi::Object JSCRuntime::createObject(std::shared_ptr<jsi::HostObject> ho) {
  // 原生模块代理
  struct HostObjectProxy : public detail::HostObjectProxyBase {
    // 取值回调
    static JSValueRef getProperty(JSContextRef ctx,JSObjectRef object,JSStringRef propertyName,JSValueRef* exception) {
      auto proxy = static_cast<HostObjectProxy*>(JSObjectGetPrivate(object));
      auto& rt = proxy->runtime;
      jsi::PropNameID sym = rt.createPropNameID(propertyName);
      jsi::Value ret;
      try {
        // 取值(获取原生模块信息)
        ret = proxy->hostObject->get(rt, sym);
      } catch (const jsi::JSError& error) {
        ...
      }
      return rt.valueRef(ret);
    }
    
    static bool setProperty(...) { ... }
    static void finalize(JSObjectRef obj) { ... }
  };

  // 构建hostObjectClass
  std::call_once(hostObjectClassOnceFlag, []() {
    JSClassDefinition hostObjectClassDef = kJSClassDefinitionEmpty;
    ...
    hostObjectClassDef.getProperty = HostObjectProxy::getProperty;
    hostObjectClass = JSClassCreate(&hostObjectClassDef);
  });

  // 创建JS对象,关联原生对象HostObjectProxy
  JSObjectRef obj =
      JSObjectMake(ctx_, hostObjectClass, new HostObjectProxy(*this, ho));
  return createObject(obj);
}
  • 2.原生函数调用代理nativeFlushQueueImmediate
    向JS端注入原生函数调用代理nativeFlushQueueImmediate,它关联原生对象HostFunctionMetadata,JS端发起JS call Native会触发回调函数,最终触发HostFunctionType hostFunction_发起函数调用
// JSIExecutor.cpp
void JSIExecutor::loadApplicationScript(...) {
  ...
  // 2.向JS端注入原生函数调用代理
  runtime_->global().setProperty(
      *runtime_,
      "nativeFlushQueueImmediate",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
          1,
          [this](jsi::Runtime&, const jsi::Value&, const jsi::Value* args, size_t count) {
            // 调用原生模块函数(批量)
            callNativeModules(args[0], false);
            return Value::undefined();
          }));
 ....
}
// JSCRuntime.cpp
jsi::Function JSCRuntime::createFunctionFromHostFunction(
    const jsi::PropNameID& name,
    unsigned int paramCount,
    jsi::HostFunctionType func) {
  
  // 原生函数调用代理
  class HostFunctionMetadata : public HostFunctionProxy {
   public:
    static void initialize(JSContextRef ctx, JSObjectRef object) { ... }

    // 函数调用回调
    static JSValueRef call(JSContextRef ctx,JSObjectRef function,JSObjectRef thisObject,size_t argumentCount,const JSValueRef arguments[],JSValueRef* exception) {
      HostFunctionMetadata* metadata =
          static_cast<HostFunctionMetadata*>(JSObjectGetPrivate(function));
      JSCRuntime& rt = *(metadata->runtime);
      ...
      JSValueRef res;
      jsi::Value thisVal(rt.createObject(thisObject));
      
      try {
       // hostFunction_() 调用关联对象函数
        res = rt.valueRef(
            metadata->hostFunction_(rt, thisVal, args, argumentCount));
      } catch (...) { ... }
      return res;
    }

    HostFunctionMetadata(JSCRuntime* rt,jsi::HostFunctionType hf,unsigned ac,JSStringRef n)
        : HostFunctionProxy(hf),runtime(rt),argCount(ac),name(JSStringRetain(n)){}
    JSCRuntime* runtime;
    unsigned argCount;
    JSStringRef name;
  };

  std::call_once(hostFunctionClassOnceFlag, []() {
    JSClassDefinition functionClass = kJSClassDefinitionEmpty;
    ...
    functionClass.callAsFunction = HostFunctionMetadata::call;
    hostFunctionClass = JSClassCreate(&functionClass);
  });

  // 创建JS对象,关联原生对象HostFunctionMetadata
  JSObjectRef funcRef = JSObjectMake(
      ctx_,
      hostFunctionClass,
      new HostFunctionMetadata(this, func, paramCount, stringRef(name)));
  return createObject(funcRef).getFunction(*this);
}

2.原生模块信息如何导入到JS端

JS端批处理桥的代码在react-native/Libraries/BatchedBridge目录下。下面基于Native&JS底层Bridge、NativeModules,从使用端(JS端)开始分析原生模块信息导入JS端的完整流程。使用例子TestManager(DEMO

原生模块导出流程.jpg
  • 1.JS端业务层使用NativeModules获取原生模块TestManager相关信息
const testManager = NativeModules.TestManager;
  • 2.JS端获取原生模块信息通过NativeModules.js,它主要负责两件事情:
    • 1.生成原生模块信息(详见下文步骤4);
    • 2.获取原生模块信息。
      NativeModules即原生端注入Js端的的原生模块信息代理nativeModuleProxyNativeModules.TestManager会触发HostObjectProxy取值回调getProperty,最终通过NativeModuleProxy获取原生模块TestManager相关信息。
// NativeModules.js

// 设置原生模块信息生成函数genModule,供原生端调用生成原生模块
global.__fbGenNativeModule = genModule;

// 获取原生模块(nativeModuleProxy是注入JS环境的原生对象)
let NativeModules: {[moduleName: string]: Object} = {};
NativeModules = global.nativeModuleProxy;
// JSCRuntime.cpp
struct HostObjectProxy : public detail::HostObjectProxyBase {
    // 取值回调
    static JSValueRef getProperty(...) {
      ...
      // 取值(获取原生模块)
      ret = proxy->hostObject->get(rt, sym);
      return rt.valueRef(ret);
    }
}
  • 3.取值回调使用负责导出原生模块的代理hostObject(即NativeModuleProxy)来获取原生模块信息
class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
 public:
  NativeModuleProxy(JSIExecutor& executor) : executor_(executor) {}
  // 获取原生模块, name:原生模块名
  Value get(Runtime& rt, const PropNameID& name) override {
    return executor_.nativeModules_.getModule(rt, name);
  }
};
  • 4.NativeModuleProxy进一步通过JSINativeModules来获取。JSINativeModules主要负责:创建并持有导入JS端最终版原生模块信息。
    它采用懒加载机制:首次获取某个原生模块信息需先创建并缓存(m_objects);非首次获取则直接取缓存数据,并就原路返回JS端,流程完毕。

注:由此可见原生模块信息最终版并不在JS端缓存,而是缓存在原生端,每次执行NativeModules.XXXX都会触发JS端去原生端获取。ReactNative框架JS端封装原生模块通常只执行一次记录为全局数据缓存起来,避免重复经过Bridge做数据传递。

// JSINativeModules.cpp
// 根据模块名称,获取原生模块
Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
  std::string moduleName = name.utf8(rt);

  // 懒加载
  const auto it = m_objects.find(moduleName);
  if (it != m_objects.end()) {
    return Value(rt, it->second);
  }
  // 首次获取时创建、缓存
  auto module = createModule(rt, moduleName);
  auto result =
      m_objects.emplace(std::move(moduleName), std::move(*module)).first;
  return Value(rt, result->second);
}

生成在JS端使用的最终版原生模块信息,需要两个步骤:
- 1.通过原生模块信息注册机ModuleRegistry生成C++版本的原生模块信息;
- 2.使用JS端导入的__fbGenNativeModule函数(即genModule函数)生成最终版。这个设计非常灵活:原生端传入基本的原生模块数据,最终由JS端(使用者)来决定如何生成。

  • 注:ReactNative注入JS环境供两端调用的函数,命名规则是:JS导出供Native使用的属性都是以“__fb”开头,Native导出供JS使用则以“native”开头
// NativeModules.js
// 设置原生模块信息生成函数genModule,供原生端调用生成原生模块
global.__fbGenNativeModule = genModule;
// JSINativeModules.cpp
folly::Optional<Object> JSINativeModules::createModule(
    Runtime& rt,
    const std::string& name) 
{
  // JS端导入的原生模块生成函数
  if (!m_genNativeModuleJS) {
    m_genNativeModuleJS =
        rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
  }

  // 1.生成C++版本原生模块信息
  auto result = m_moduleRegistry->getConfig(name);
 
  // 2.调用js端注入的工具函数,传入C++版本模块配置信息,生成JS端模块信息
  Value moduleInfo = m_genNativeModuleJS->call(
      rt,
      valueFromDynamic(rt, result->config),
      static_cast<double>(result->index));

  // 取出module,构造为js对象
  folly::Optional<Object> module(
      moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));

  return module;
}
  • 5.生成C++版本的原生模块信息,是数组形式[name, constants, methodNames, promiseMethodIds, syncMethodIds]
    methodId即函数在数组中的索引index,ReactNative JS call Native都是通过id(moduleId、methodId)来找到对象的模块、函数。
    • name:string,模块名称
    • constants:object,导出常量 {常量名:常量值, ... }
    • methodNames:array[string],方法js名称集合
    • promiseMethodIds:array[int],promise函数id(methodId)集合。
    • syncMethodIds:array[int],同步函数id集合

此处使用了facebook的开源库folly,它扩展了C++的动态能力,使之能和动态化JavaScript无缝衔接。构建了哈希表modulesByName_存放<模块名:模块id>,应该是为了提高查找效率。

// ModuleRegistry.cpp
folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
  // 初始化原生模块哈希表modulesByName_ <模块名, 模块id>
  if (modulesByName_.empty() && !modules_.empty()) {
    moduleNames();
  }
  
  // 根据原生模块名,查找模块索引 index
  auto it = modulesByName_.find(name);
  size_t index = it->second;

  // 获取原生模块RCTNativeModule
  NativeModule *module = modules_[index].get();
  
  // 构建包含原生模块信息(常量、函数)的动态数组
  folly::dynamic config = folly::dynamic::array(name);
  config.push_back(module->getConstants());
  {
    std::vector<MethodDescriptor> methods = module->getMethods();
    folly::dynamic methodNames = folly::dynamic::array;
    folly::dynamic promiseMethodIds = folly::dynamic::array;
    folly::dynamic syncMethodIds = folly::dynamic::array;

    for (auto& descriptor : methods) {
      methodNames.push_back(std::move(descriptor.name));
      if (descriptor.type == "promise") {
        promiseMethodIds.push_back(methodNames.size() - 1); // 索引(id)
      } else if (descriptor.type == "sync") {
        syncMethodIds.push_back(methodNames.size() - 1);
      }
    }

    if (!methodNames.empty()) {
      config.push_back(std::move(methodNames));
      if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
        config.push_back(std::move(promiseMethodIds));
        if (!syncMethodIds.empty()) {
          config.push_back(std::move(syncMethodIds));
        }
      }
    }
  }
  return ModuleConfig{index, config};
}

例子TestManager,导出函数、常量,和最终生成的C++版本原生模块如下

// FYRNTestManager.m

RCT_EXPORT_MODULE(TestManager)

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSNumber *)secondsSinceUnixEpoch) { ... }

RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) { ... }

RCT_REMAP_METHOD(findEventsWithResolver, findEvents:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { ... } 

- (NSDictionary *)constantsToExport {
  return @{
           @"age" : @18,
           @"name" : @"fyfy",
           @"tag" : @"Handsome" };
}
// 生成C++版本原生模块信息
[ 
    'TestManager',
    { name: 'fyfy', tag: 'Handsome', age: 18 },
    [ 'addEvent', 'findEvents', 'findEventsWithResolver' ],
    [ 2 ] 
]
  • 6.JS端函数genModule跟局传入的C++版本原生模块信息来生成最终版。
    • 根据导出函数信息,生成对应的JS版函数。genMethod生成的JS函数包装JS call Native,同步函数是直接发起原生函数调用,其他类型函数则调用BatchedBridge.enqueueNativeCall把调用信息入队,等待执行。
    • 添加原生模块常量
      至此,原生模块信息加工完毕,从JS端返回Native端,取出module内容作为最终结果,再原路JS端。JS端可以愉快地调用导出原生模块函数、使用原生模块常量了。
// NativeModules.js

// 生成原生模块信息
function genModule( config: ?ModuleConfig, moduleID: number,): ?{name: string, module?: Object} {
  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  const module = {};

 // 添加 JS版原生模块函数
  methods &&
    methods.forEach((methodName, methodID) => {  //  methodID即index
      const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
      const isSync = syncMethods && arrayContains(syncMethods, methodID);
      const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
      // genMethod 生成函数
      module[methodName] = genMethod(moduleID, methodID, methodType);
    });
  
  // 添加原生模块导出常量
  Object.assign(module, constants);
  module.getConstants = () => constants;
  const result = {name: moduleName, module};
  return result;
}

// 生成函数
genMethod(moduleID: number, methodID: number, type: MethodType) {
  let fn = null;
  if (type === 'promise') {
    fn = function(...args: Array<any>) {
      return new Promise((resolve, reject) => {
        // 函数入队
        BatchedBridge.enqueueNativeCall(
          moduleID,
          methodID,
          args,
          data => resolve(data),
          errorData => reject(createErrorFromErrorData(errorData)),
        );
      });
    };
  } else if (type === 'sync') {
    fn = function(...args: Array<any>) {
      return global.nativeCallSyncHook(moduleID, methodID, args);
    };
  } else {
    fn = function(...args: Array<any>) {
      ...
      BatchedBridge.enqueueNativeCall(...);
    };
  }
  fn.type = type;
  return fn;
}

例子TestManager最终生成的JS端原生模块最终版如下,原生端会取出module,传递到JS端使用。

{ 
    name: 'TestManager',
    module: { 
      addEvent: { [Function: fn] type: 'async' }, // addEvent 函数类型,新增type属性
      findEvents: { [Function: fn] type: 'async' },
      findEventsWithResolver: { [Function: fn] type: 'promise' },
      name: 'fyfy',
      tag: 'Handsome',
      age: 18,
      getConstants: [Function] 
    }
  }

3.JS端底层通信桥搭建

  • BatchedBridge:即批处理桥,JS call Native并非调用一次执行一次,而是把调用操作暂存,在适当的时机批量进行调用。
    BatchedBridge即MessageQueue对象,以属性名__fbBatchedBridge注入JS环境全局对象global,供原生端使用。
// BatchedBridge.js
const BatchedBridge = new MessageQueue();
Object.defineProperty(global, '__fbBatchedBridge', {
  configurable: true,
  value: BatchedBridge,
});
  • 注:ReactNative框架多处使用批处理思想,常用在调用频繁,无需同步响应的场景,例如JS call Native、渲染流程……

  • MessageQueue:消息队列,JS&Native互调在JS端都是由它管理,从这里可以看出几个要点
    • 1.JS模块并不导出到Native端,仅仅是注册到MessageQueue中的JS模块表_lazyCallableModules
      Native call JS都是在原生端通过字符串编码来进行,最终通过callFunctionReturnFlushedQueue触发。
    • 2.JS call Native并非每次调用就立即触发,而是批处理。JS端调用原生模块函数时把调用信息存放在_queue,在适当时机触发调用。
    • 3.JS call Native的回调,以调用id(_callID)作为key,暂存在MessageQueue中的回调表_successCallbacks、_failureCallbacks,等原生函数执行完毕时发起回调,最终通过invokeCallbackAndReturnFlushedQueue触发回调。这里使用位运算左移/右移/按位与来区分失败、成功回调。
// MessageQueue.js

const MIN_TIME_BETWEEN_FLUSHES_MS = 5;  // JS call Native批量调用时间间隔 >= 5毫秒

class MessageQueue {
  // JS模块表 { 模块名:模块函数集合getter }
  _lazyCallableModules: {[key: string]: (void) => Object};
  // Native模块函数调用信息暂存表(缓冲区)  [[MODULE_IDS], [METHOD_IDS], [PARAMS],   _callID], _callID仅需传递1个,原生端自动叠加计算其他callID值
  _queue: [number[], number[], any[], number];
  // Native函数回调 暂存表 { callID:回调函数 }
  _successCallbacks: {[key: number]: ?Function};
  _failureCallbacks: {[key: number]: ?Function};
  // Native函数调用id,自增
  _callID: number;
  // 上一次刷新缓冲区的时间
  _lastFlush: number;
  
  ///////////////////////  Native端使用 //////////////////
  
  // 调用JS函数,返回Native函数调用暂存表
  callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {  
    this.__callFunction(module, method, args);
    return this.flushedQueue();
  }

  // 执行Native函数对应的JS回调,返回Native函数调用暂存表
  invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
    this.__invokeCallback(cbID, args);
    return this.flushedQueue();
  }

  // 执行JS异步任务,返回Native函数调用暂存表 并清空
  flushedQueue() {
    this.__callImmediates();
    const queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

  /////////////////////// JS端使用 //////////////////
  
  // 注册JS模块
  registerCallableModule(name: string, module: Object) {
    this._lazyCallableModules[name] = () => module;
  }
  // 注册JS模块(懒加载)
  registerLazyCallableModule(name: string, factory: void => Object) {
    let module: Object;
    let getValue: ?(void) => Object = factory;
    this._lazyCallableModules[name] = () => {
      if (getValue) {
        module = getValue(); // 加载JS模块
        getValue = null;
      }
      return module;
    };
  }

  // 原生函数调用入队,执行
  enqueueNativeCall(moduleID: number,methodID: number,params: any[], onFail: ?Function, onSucc: ?Function,
  ) {
    if (onFail || onSucc) {

      // 位运算左移对_callID进行编码,作为 失败、成功回调参数值,最后一位用于区分失败回调(0)、成功回调(1)
      onFail && params.push(this._callID << 1);
      onSucc && params.push((this._callID << 1) | 1);
      // 暂存回调
      this._successCallbacks[this._callID] = onSucc;
      this._failureCallbacks[this._callID] = onFail;
    }

    // 调用id自增
    this._callID++;

    // 暂存函数回调函数
    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);
    
    // 暂存函数调用信息
    this._queue[PARAMS].push(params);

    // 距上一次批量调用时间间隔大于5ms,则执行调用
    // nativeFlushQueueImmediate 原生端注入的调用代理
    const now = Date.now();
    if (
      global.nativeFlushQueueImmediate &&
      now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
    ) {
      const queue = this._queue;
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
      global.nativeFlushQueueImmediate(queue);
    }
}
// JSIExecutor.cpp
void JSIExecutor::bindBridge() {
  // 获取js对象MessageQueue
  std::call_once(bindFlag_, [this] {
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
    }

    // 绑定JS端注入的函数
    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);    
    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "callFunctionReturnFlushedQueue");
    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "invokeCallbackAndReturnFlushedQueue");
    flushedQueue_ =
        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
  });
}

总结

  • JSIExecutor、MessageQueue是两端交互的核心,通过这两者注入代理对象供另一方调用,以实现Native&JS数据传递、互相调用。

  • JS call Native的触发时机有:

    • 1.调用enqueueNativeCall函数入队(存入暂存表)时发现距离上一次调用大于5毫秒时,通过nativeFlushQueueImmediate执行调用;
    • 2.执行flushedQueue时(flushedQueue用于执行JS端setImmediate异步任务,在此不展开讨论),把原生模块调用信息作为返回值传递到原生端,执行调用;
    • 3.通过callFunctionReturnFlushedQueue执行JS call Native也会触发flushedQueue,同样返回原生模块调用信息
    • 4.通过invokeCallbackAndReturnFlushedQueue执行JS回调,同理。
      笔者猜想这种设计的目的是:保证能及时发起函数调用的前提下,减少调用频率。毕竟 JS call Native的调用是非常频繁的。

二、Native call JS

基于上面对Native&JS底层Bridge的分析,下面分析完整的Native call JS流程

Native call JS.jpg
  • 1.原生端以字符串编码形式,通过RCTBridge发起Native call JS。以RCTRootView运行根组件为例:调用JS模块AppRegistry函数runApplication
// RCTRootView.m
- (void)runApplication:(RCTBridge *)bridge
{
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}
  • 2.RCTCxxBridge执行入队函数(module:js模块名,method:js方法名,args:参数, completion:回调)。
    这里通过_runAfterLoad 判断js bundle是否运行完毕,完毕则直接使用Instance继续中转JS call Native;否则先暂存在_pendingCalls等待完毕信号。
    从这里也可以看出Native call JS由JS线程管理。
// RCTCxxBridge.mm  
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion 
{
  [self _runAfterLoad:^(){
      // convertIdToFollyDynamic OC转C++动态类型
      strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],convertIdToFollyDynamic(args ?: @[]));
     ...
  }];
}

- (void)_runAfterLoad:(RCTPendingCall)block
{
  if (self.loading || _pendingCount > 0) {
    _pendingCount++;
    dispatch_block_t jsQueueBlock = ^{
      if (self.loading) {
        [self->_pendingCalls addObject:block];
      } else {
        block();
        self->_pendingCount--;
      }
    };
    // JS线程执行
    [self ensureOnJavaScriptThread:jsQueueBlock];
  } else {
    block();
  }
}
  • 3.Instance使用NativeToJsBridge中转调用
// Instance.cpp
void Instance::callJSFunction(std::string &&module, std::string &&method,
                              folly::dynamic &&params) {
  nativeToJsBridge_->callFunction(std::move(module), std::move(method),
                                  std::move(params));
}
  • 4.NativeToJsBridge使用JSIExecutor中转调用
// NativeToJsBridge.cpp
void NativeToJsBridge::callFunction(module, method, arguments) {
  runOnExecutorQueue([...](JSExecutor* executor) {
      executor->callFunction(module, method, arguments);
    });
}
  • 5.JSIExecutor是最终调用者,使用JS端MessageQueue注入函数callFunctionReturnFlushedQueue发起JS call Native。 JS call Native会返回JS端暂存的原生模块调用信息,并发起 Native call JS(下文会详细分析)。
// JSIExecutor.cpp
void JSIExecutor::callFunction( moduleId, methodId, arguments) {
  // 首次使用触发`bindBridge`绑定;
  if (!callFunctionReturnFlushedQueue_) {
    bindBridge();
  }
  
  // 执行JS模块函数
  Value ret = Value::undefined();
  ret = callFunctionReturnFlushedQueue_->call(*runtime_, moduleId, methodId, valueFromDynamic(*runtime_, arguments));

  // 调用原生模块函数
  callNativeModules(ret, true);
}
  • 6.MessageQueue根据模块id、函数名,在JS模块表_lazyCallableModules查找到对应模块,发起调用。
// MessageQueue.js
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
    // 调用JS模块函数
    this.__callFunction(module, method, args);
    // 执行JS异步任务,返回Native函数调用暂存表
    return this.flushedQueue();
}

// 调用JS函数 module:模块名, method:函数名,args:参数
__callFunction(module: string, method: string, args: any[]): any {
    // 获取JS模块 函数集合
    const moduleMethods = this.getCallableModule(module);
    // 根据函数名method,取出对应函数,进行调用
    const result = moduleMethods[method].apply(moduleMethods, args);
    return result;
}

// 获取JS模块
getCallableModule(name: string) {
    const getValue = this._lazyCallableModules[name];
    return getValue ? getValue() : null;
}

该例子中JS模块AppRegistry事先调用registerCallableModule把自身注册到BatchedBridge中的JS模块表。到此流程完毕。

// AppRegistry.js

const AppRegistry = {
  // 运行组件
  runApplication(appKey: string, appParameters: any): void {
    ...
    runnables[appKey].run(appParameters);
  },
}

// 注册JS模块 AppRegistry
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);

三、JS call Native

还是用原生模块信息导入JS端的例子TestManager来分析。这里分析JS端调用原生模块带回调的函数,其他类型的函数调用可在此基础上自行分析。对比两个图,左边的路线是类似的。

JS call Native.jpg
  • 1.JS端业务层使用NativeModules获取原生模块TestManager,触发上文原生模块导入JS端流程,最终获取到原生模块信息。执行带回调的原生模块函数findEvents
import { NativeModules,} from 'react-native';

const manager = NativeModules.TestManager;
manager.findEvents((error, events) => {
  if (!error) {
      this.setState({events: events});
  }
});
  • 2.根据上述原生模块信息导出流程,可知findEvents函数会调用BatchedBridge.enqueueNativeCall,把调用信息(模块id、函数id、参数)入队暂存
// NativeModules.js
function genMethod(moduleID: number, methodID: number, type: MethodType) {
    ...
    fn = function(...args: Array<any>) {
      ...
      // 函数入队
      BatchedBridge.enqueueNativeCall(
        moduleID,
        methodID,
        args,
        onFail,
        onSuccess,
      );
    };
    ...
    fn.type = type;
    return fn;
}

原生模块调用信息、回调信息暂存于MessageQueue,等待调用时机(见上文)进行批量调用,把调用信息传递到原生端执行。

// MessageQueue.js

// 原生函数调用入队,执行
  enqueueNativeCall(moduleID: number, methodID: number, params: any[], onFail: ?Function, onSucc: ?Function,) {
    // 暂存回调
    if (onFail || onSucc) {
      this._successCallbacks[this._callID] = onSucc;
      this._failureCallbacks[this._callID] = onFail;
    }
    // 暂存函数调用信息
    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);
    this._queue[PARAMS].push(params);
    ...
    global.nativeFlushQueueImmediate(queue);
  }
  • 3/4 JS call Native的触发时机(详见上文),原生模块信息由JS端传递到原生端JSIExecutor,触发callNativeModules
    此处会把调用信息由JSValue类型转化为C++动态类型,并调用JsToNativeBridge中转。
// JSIExecutor.cpp
void JSIExecutor::callNativeModules(const Value& queue, bool isEndOfBatch) {
  // dynamicFromValue 函数调用信息由 JSValue 转 c++ 动态类型
  delegate_->callNativeModules(
      *this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}
  • 5.JsToNativeBridge管理JS call Native,把批量原生模块调用信息由C++动态类型解析为MethodCall集合(原生模块函数调用信息结构体)并逐个调用。我们多次强调ModuleRegistry是真正拥有原生模块信息者,原生模块函数调用必然中转到它头上。
// JsToNativeBridge.cpp
void callNativeModules(JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
    // 解析函数调用元素,依次执行函数调用
    for (auto& call : parseMethodCalls(std::move(calls))) {
      m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
    }
    ...
  }
// MethodCall.cpp
struct MethodCall {
  int moduleId;  // 模块id
  int methodId;  // 函数id
  folly::dynamic arguments; // 调用参数
  int callId;    // 调用id
};
  • 6.ModuleRegistry接收原生模块调用信息,根据moduleId获取到对应原生模块,接着中转函数调用。此时函数调用已经精确到对应的原生模块
// ModuleRegistry.cpp
// 原生模块函数调用
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
  // 根据索引获取对应模块,调用函数
  modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
  • 7.RCTNativeModule把函数执行线程切换到原生模块指定的执行线程。JavaScriptCore C语言篇提到关联了原生对象的JS对象,属性获取回调函数、函数调用回调函数的执行线程,与JS脚本执行的线程一致,因此可知获取原生模块、JS call Native的执行线程都是JS线程。
    静态函数invokeInner,根据函数索引methodId获取到对应的函数,此时函数调用精确到对应原生模块的对应函数。
    参数值由C++动态类型转化为OC数组,并获取原生模块实例instance,接着中转。
// RCTNativeModule.mm

// 原生模块函数调用 methodId:函数索引 params:参数
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &&params, int callId) {
  __weak RCTBridge *weakBridge = m_bridge;
  __weak RCTModuleData *weakModuleData = m_moduleData;
  dispatch_block_t block = [weakBridge, weakModuleData, methodId, params=std::move(params), callId] {
    invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
  };

  // 执行函数调用,由JS线程,切换到对应线程执行
  if (m_bridge.valid) {
    dispatch_queue_t queue = m_moduleData.methodQueue;
    if (queue == RCTJSThread) {
      block();
    } else if (queue) {
      dispatch_async(queue, block);
    }
  }
}

static MethodCallResult invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic &params) {
  // 根据函数索引methodId获取函数
  id<RCTBridgeMethod> method = moduleData.methods[methodId];
  // 参数 c++动态类型 》 oc数组
  NSArray *objcParams = convertFollyDynamicToId(params);
  // 执行函数调用
  @try {
    id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
    return convertIdToFollyDynamic(result);
  } @catch (NSException *exception) { ... }
}
  • 8.RCTModuleMethod是原生模块导出函数描述对象,也是最终的执行者。
    • 1.首次调用函数会通过processMethodSignature解析导出函数信息,主要是:根据函数名生成函数消息、构造参数赋值回调argumentBlocks。
      这里有点冗长,最好看源码,并把宏还原。回调类型参数RCTResponseSenderBlock会生成一个封装了js回调操作的block([bridge enqueueCallback:json args:args];),在第二步设置为函数消息对象invocation的参数。
    • 2.接着遍历执行argumentBlocks给消息对象invocation设置函数参数值。
    • 3.最后执行函数调用[_invocation invokeWithTarget:module]
// RCTModuleMethod.mm
- (id)invokeWithBridge:(RCTBridge *)bridge
                module:(id)module
             arguments:(NSArray *)arguments
{
  // 首次执行,解析导出函数信息
  if (_argumentBlocks == nil) {
    [self processMethodSignature];
  }

  // 执行传参block,设置参数
  for (id json in arguments) {
    RCTArgumentBlock block = _argumentBlocks[index];
  }

  // 调用原生模块方法
  [_invocation invokeWithTarget:module];
}

- (void)processMethodSignature
{
  // 通过导出函数名解析函数参数、SEL
  NSArray<RCTMethodArgument *> *arguments;
  _selector = NSSelectorFromString(RCTParseMethodSignature(_methodInfo->objcName, &arguments));

  // 构造函数签名、消息
  NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
  NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  invocation.selector = _selector;
  _invocation = invocation;
  NSMutableArray *retainedObjects = [NSMutableArray array];
  _retainedObjects = retainedObjects;

// 以block形式封装函数传参逻辑,并保存到_argumentBlocks
  NSUInteger numberOfArguments = methodSignature.numberOfArguments;
  NSMutableArray<RCTArgumentBlock> *argumentBlocks =
    [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
 
  for (NSUInteger i = 2; i < numberOfArguments; i++) {
    const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
    BOOL isNullableType = NO;
    RCTMethodArgument *argument = arguments[i - 2];
    NSString *typeName = argument.type;
    SEL selector = selectorForType(typeName);
    if ([RCTConvert respondsToSelector:selector]) {
       ...
    } else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
     /*
      BLOCK_CASE((NSArray *args), {
        [bridge enqueueCallback:json args:args];
      });
     */
     
     // 宏展开后如下,可以看出带回调的参数会通过enqueueCallback执行暂存在js端的回调
      [argumentBlocks addObject:^(__unused __weak RCTBridge *bridge, NSUInteger index, id json) {
        // 参数:回调block
        id value = [^(NSArray *args) {
          // 调用JS函数  json:callbackId,args:参数
          [bridge enqueueCallback:json args:args];
        } copy];
        [invocation setArgument:&value atIndex:(index) + 2];
        if (value) {
          [retainedObjects addObject:value];
        }
        return YES;
      }];
    } else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
      ...
    }
  }
  
  _argumentBlocks = argumentBlocks;
}

发起函数调用就调用到最终的原生模块函数。如果该JS call Native是不带回调的普通函数,至此已经执行完毕

// FYRNTestManager.m
// 带回调函数
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
  RCTLogInfo(@"do something...");
  RCTLogInfo(@"thread : %@", [NSThread currentThread]);
  NSArray *events = @[@"events1", @"events2"];
  
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      callback(@[[NSNull null], events]);
  });
}
  • 9.函数调用完毕,执行回调。该回调是对[bridge enqueueCallback:json args:args];的封装,会触发C++Bridge执行js callback,把callback ID(即JS call Native的callID)传递到JS端用于获取到对应的回调函数,详见processMethodSignatureRCTResponseSenderBlock类型参数宏展开。

  • 10.RCTCxxBridge进一步把回调中转给Instance,参数由OC数组转化为C++动态类型

// RCTCxxBridge.mm
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
  __weak __typeof(self) weakSelf = self;
  [self _runAfterLoad:^(){
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf->_reactInstance) {
    // 执行JS回调
      strongSelf->_reactInstance->callJSCallback([cbID unsignedLongLongValue], convertIdToFollyDynamic(args ?: @[]));
    }
  }];
}
  • 11.Instance把回调中转给NativeToJsBridge
// Instance.cpp
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic &&params) {
  nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
  • 12.NativeToJsBridge把回调中转给JSIExecutor。此处线程切换到JS线程
// NativeToJsBridge.cpp
void NativeToJsBridge::invokeCallback(double callbackId, folly::dynamic&& arguments) {
   runOnExecutorQueue([this, callbackId, arguments = std::move(arguments),]
      executor->invokeCallback(callbackId, arguments);
    });
}
  • 13.JSIExecutor调用JS端BatchedBridge的注入函数callFunctionReturnFlushedQueue执行回调。
// JSIExecutor.cpp
void JSIExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
  Value ret;
  try {
    ret = invokeCallbackAndReturnFlushedQueue_->call(
        *runtime_, callbackId, valueFromDynamic(*runtime_, arguments));
  } catch (...) { ... }
}
  • 14.BatchedBridge根据回调id,对其进行位运算右移恢复,得到缓存key,并取出执行.
    enqueueNativeCall对callID进行位运算左移,这里是其逆运算,简洁的方式区分成功/失败回调。
// MessageQueue.js
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
    this.__guard(() => {
     // 执行Native函数对应的JS回调
      this.__invokeCallback(cbID, args);
    });
    return this.flushedQueue();
}

__invokeCallback(cbID: number, args: any[]) {
    this._lastFlush = Date.now();
    this._eventLoopStartTime = this._lastFlush;

    // 对cbID进行位运算右移,获取对应回调。 通过最后一位区分成功/失败回调
    const callID = cbID >>> 1;
    const isSuccess = cbID & 1;
    const callback = isSuccess
      ? this._successCallbacks[callID]
      : this._failureCallbacks[callID];

    delete this._successCallbacks[callID];
    delete this._failureCallbacks[callID];
    // 执行回调
    callback(...args);
  }

触发业务层回调,流程完毕

const manager = NativeModules.TestManager;
manager.findEvents((error, events) => {
  console.log("执行回调");
  if (!error) {
      this.setState({events: events});
  }
});

Reference

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

推荐阅读更多精彩内容