美文网首页React Native源码分析JavaScript
ReactNative源码分析 - 通信机制

ReactNative源码分析 - 通信机制

作者: 浮游lb | 来源:发表于2019-12-15 17:02 被阅读0次

    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

    相关文章

      网友评论

        本文标题:ReactNative源码分析 - 通信机制

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