ReactNative 通信機制_js端源碼分析

ReactNative: 源碼分析系列

ReactNative 啟動過程源碼分析
ReactNative 通信機制_java端源碼分析
ReactNative 通信機制_c++端源碼分析
ReactNative 通信機制_js端源碼分析

我們已經分析過了java端,c++端調用流程,只剩下最后一個js端的過程。

BatchedBridge.js

'use strict';

const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue();

Object.defineProperty(global, '__fbBatchedBridge', {
  configurable: true,
  value: BatchedBridge,
});

module.exports = BatchedBridge;

創建一個MessageQueue實例,并將它定義到全局變量中,以便給JSCExecutor.cpp中獲取到。

MessageQueue.js

有三個作用:1.注冊所有的JavaScriptModule,2.提供方法供c++端調用,3.分發js端NativeModule所有異步方法的調用(同步方法會直接調用c++端代碼)。

constructor() {
    this._lazyCallableModules = {};
    this._queue = [[], [], [], 0];
    this._successCallbacks = [];
    this._failureCallbacks = [];
    this._callID = 0;
    this._lastFlush = 0;
    this._eventLoopStartTime = new Date().getTime();

    if (__DEV__) {
      this._debugInfo = {};
      this._remoteModuleTable = {};
      this._remoteMethodTable = {};
    }

    (this: any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(
      this,
    );
    (this: any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(
      this,
    );
    (this: any).flushedQueue = this.flushedQueue.bind(this);
    (this: any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(
      this,
    );
  }
  1. _lazyCallableModules:一個Object,儲存了所有JavaScriptModule。
  2. _queue:儲存js端調用NativeModule的命令。

它是一個長度為4的Array,第一個也是Array,儲存NativeModule名字的索引數組(它對應著c++的ModuleRegistry.cpp中modules_列表的下標),第二個也是Array,儲存NativeModule的method對應索引數組(表示這個方法在NativeModule的methed方法列表中的下表,對應的是JavaModuleWrapper.java的mMethods集合的下表),第三個也是Array,存儲傳遞參數列表數組。第四個是一個number類型,表示_callID。

  1. _successCallbacks:是一個方法的集合Array。

作用是當我們進行NativeModule調用時,可能要接受java端返回數據,那么怎么將這個數據準確賦值給js端調用的地方。這個集合就儲存js端成功回調的方法列表。

  1. _failureCallbacks:和上個參數作用相同,儲存js端失敗回調的方法列表。
  2. _callID:是_successCallbacks和_failureCallbacks的下標,來確定回調對應的方法。
  3. _inCall: 主動通知c++端的標志,當它的值是0,且這個時候js端觸發NativeModule方法,那么就可能會直接調用c++方法,在enqueueNativeCall方法中進行分析。
  4. _lastFlush和_eventLoopStartTime:記錄時間。
  5. 然后調用bind(this)方法,綁定this變量。
__guard(fn: () => void) {
    this._inCall++;
    try {
      fn();
    } catch (error) {
      ErrorUtils.reportFatalError(error);
    } finally {
      this._inCall--;
    }
  }

這個方法是一個監控方法,它四個地方調用,就是c++直接調用js端的四個方法。

_inCall自增,表示正在有c++端調用js端代碼,我們都知道c++端調用js端代碼之后,都會調用自己的callNativeModules方法,所以不用js端主動觸發這個方法了

flushedQueue() {
    this.__guard(() => {
      this.__callImmediates();
    });
    const queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  } 

這個方法主要得到this._queue數據,并將它重置。this.__callImmediates()是開啟開啟一個定時器方法,具體可以看JSTimers.js中具體實現(它也是一個JavaScriptModule)

callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
    this.__guard(() => {
      this.__callFunction(module, method, args);
    });
    return this.flushedQueue();
  }

java端調用JavaScriptModule方法,最終就會調用到這個方法。__callFunction調用js端對應JavaScriptModule方法,最后返回js端當前發起的NativeModule異步方法請求列表。

invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
    this.__guard(() => {
      this.__invokeCallback(cbID, args);
    });
    return this.flushedQueue();
  }

NativeModule方法調用,java端的結果值就是通過這個方法,回調給js端。

callFunctionReturnResultAndFlushedQueue(
    module: string,
    method: string,
    args: any[],
  ) {
    let result;
    this.__guard(() => {
      result = this.__callFunction(module, method, args);
    });
    return [result, this.flushedQueue()];
  }

這個方法和callFunctionReturnFlushedQueue大體相同,但是它多返回了js端調用JavaScriptModule方法的結果值。

原則上它可以實現java端調用JavaScriptModule方法并得到js端的結果值。可惜這個方法還沒有完全實現,在Instance.h中的callFunctionSync方法會調用到js的這個方法,但是這個callFunctionSync方法,卻沒有任何地方調用。

下面我們來分析JavaScriptModule的注冊:

registerCallableModule(name: string, module: Object) {
    this._lazyCallableModules[name] = () => module;
  }

向這個_lazyCallableModules中添加這個module。

例如在AppRegistry.js中就調用了這個方法,BatchedBridge.registerCallableModule('AppRegistry', AppRegistry); BatchedBridge是MessageQueue的一個實例。

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添加JavaScriptModule,它在InitializeCore.js中有多處調用。

getCallableModule(name: string) {
    const getValue = this._lazyCallableModules[name];
    return getValue ? getValue() : null;
  }

通過JavaScriptModule的名字,得到這個JavaScriptModule。

__callFunction(module: string, method: string, args: any[]): any {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    Systrace.beginEvent(`${module}.${method}()`);
    if (this.__spy) {
      this.__spy({type: TO_JS, module, method, args});
    }
    const moduleMethods = this.getCallableModule(module);
    invariant(
      !!moduleMethods,
      'Module %s is not a registered callable module (calling %s)',
      module,
      method,
    );
    invariant(
      !!moduleMethods[method],
      'Method %s does not exist on module %s',
      method,
      module,
    );
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }

先通過this.getCallableModule(module)獲取對應的JavaScriptModule,在使用moduleMethods[method].apply(moduleMethods, args);調用對應的方法,最后返回方法調用的結果值.


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

    // The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
    // eslint-disable-next-line no-bitwise
    const callID = cbID >>> 1;
    // eslint-disable-next-line no-bitwise
    const isSuccess = cbID & 1;
    const callback = isSuccess
      ? this._successCallbacks[callID]
      : this._failureCallbacks[callID];

    if (__DEV__) {
      const debug = this._debugInfo[callID];
      const module = debug && this._remoteModuleTable[debug[0]];
      const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
      if (!callback) {
        let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`;
        if (method) {
          errorMessage =
            `The callback ${method}() exists in module ${module}, ` +
            'but only one callback may be registered to a function in a native module.';
        }
        invariant(callback, errorMessage);
      }
      const profileName = debug
        ? '<callback for ' + module + '.' + method + '>'
        : cbID;
      if (callback && this.__spy) {
        this.__spy({type: TO_JS, module: null, method: profileName, args});
      }
      Systrace.beginEvent(
        `MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
      );
    }

    if (!callback) {
      return;
    }

    this._successCallbacks[callID] = this._failureCallbacks[callID] = null;
    callback(...args);

    if (__DEV__) {
      Systrace.endEvent();
    }
  }

根據cbID,從this._successCallbacks或this._failureCallbacks獲取對應的方法callback,然后通過 callback(...args)將java端返回的結果值,回調給js端調用的地方。

要理解cbID的作用。NativeModule的調用結果有兩個,一個是成功,一個是失敗。它們對應的callID(即在_successCallbacks和_failureCallbacks下標)是同一個。失敗的cbID對應的是this._callID << 1(即將_callID乘以2,而且它的二進制形式最后一位一定是0),成功的cbID對應的是(this._callID << 1) | 1(即將_callID乘以2再加1,那么它的二進制形式最后一位一定是1),這些操作都是在enqueueNativeCall方法中進行的。這樣我們雖然是相同的callID,但是我們也可以區分成功還是失敗的回調了。

 enqueueNativeCall(
    moduleID: number,
    methodID: number,
    params: any[],
    onFail: ?Function,
    onSucc: ?Function,
  ) {
    if (onFail || onSucc) {
      if (__DEV__) {
        this._debugInfo[this._callID] = [moduleID, methodID];
        if (this._callID > DEBUG_INFO_LIMIT) {
          delete this._debugInfo[this._callID - DEBUG_INFO_LIMIT];
        }
      }
      // Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
      // to indicate fail (0) or success (1)
      // eslint-disable-next-line no-bitwise
      onFail && params.push(this._callID << 1);
      // eslint-disable-next-line no-bitwise
      onSucc && params.push((this._callID << 1) | 1);
      this._successCallbacks[this._callID] = onSucc;
      this._failureCallbacks[this._callID] = onFail;
    }

    if (__DEV__) {
      global.nativeTraceBeginAsyncFlow &&
        global.nativeTraceBeginAsyncFlow(
          TRACE_TAG_REACT_APPS,
          'native',
          this._callID,
        );
    }
    this._callID++;

    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);

    if (__DEV__) {
      // Any params sent over the bridge should be encodable as JSON
      JSON.stringify(params);

      // The params object should not be mutated after being queued
      deepFreezeAndThrowOnMutationInDev((params: any));
    }
    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)) {
      this.__spy({
        type: TO_NATIVE,
        module: this._remoteModuleTable[moduleID],
        method: this._remoteMethodTable[moduleID][methodID],
        args: params,
      });
    } else if (this.__spy) {
      this.__spy({
        type: TO_NATIVE,
        module: moduleID + '',
        method: methodID,
        args: params,
      });
    }
  }

所有js端調用NativeModule的異步方法都會走這里(同步方法直接調用c++方法)。

  1. moduleID:對應的ModuleRegistry.cpp中modules_集合的下標。
  2. methodID:對應的是JavaModuleWrapper.java中mMethods集合的下標。
  3. params: 傳遞給NativeModule方法的調用參數。
  4. onFail 和 onSucc:接受java端結果值的回調。

首先當onFail或onSucc有值時,那么就向params數組中多添加cbID。還記得JavaMethodWrapper.java中ARGUMENT_EXTRACTOR_CALLBACK屬性中獲取的id就是這里的cbID,最終它會回傳給__invokeCallback方法。然后就是向this._queue中添加這些moduleID,methodID,params數據。

NativeModules.js

作用是將c++端傳遞的NativeModules轉換成js的NativeModule的對象集合。

if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else {
  const bridgeConfig = global.__fbBatchedBridgeConfig;
  invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules');

  const defineLazyObjectProperty = require('defineLazyObjectProperty');
  (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {
    // Initially this config will only contain the module name when running in JSC. The actual
    // configuration of the module will be lazily loaded.
    const info = genModule(config, moduleID);
    if (!info) {
      return;
    }

    if (info.module) {
      NativeModules[info.name] = info.module;
    }
    // If there's no module config, define a lazy getter
    else {
      defineLazyObjectProperty(NativeModules, info.name, {
        get: () => loadModule(info.name, moduleID)
      });
    }
  });
}
  1. 如果全局變量中存在nativeModuleProxy變量,那么就把它當成NativeModules。
  2. 如果沒有,那么獲取global.__fbBatchedBridgeConfig變量,它是由c++端構建,是一個Object,有一個remoteModuleConfig屬性的數組,這個數組儲存了所有的NativeModule的信息。

每個NativeModule的格式
["ImageLoader",{},["abortRequest","getSize","prefetchImage","queryCache"],[1,2,3]] 它是一個數組:

  1. 表示NativeModule的模板名。
  2. 表示這個NativeModule所有常量的對象(相當于map)。
  3. 表示NativeModule所有方法名的數組。
  4. 是一個儲存下標的數組,表示這個NativeModule那些方法是promise類型。
  5. 可能還有第五個參數,也是一個儲存下標的數組,表示個NativeModule那些方法是sync類型的,即同步等待java端返回結果值得。

3.遍歷這個remoteModuleConfig數組,通過genModule方法生成對應的js端NativeModule,注冊到NativeModules中。

function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} {
  if (!config) {
    return null;
  }

  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  invariant(!moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
    'Module name prefixes should\'ve been stripped by the native side ' +
    'but wasn\'t for ' + moduleName);

  if (!constants && !methods) {
    // Module contents will be filled in lazily later
    return { name: moduleName };
  }

  const module = {};
  methods && methods.forEach((methodName, methodID) => {
    const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
    const isSync = syncMethods && arrayContains(syncMethods, methodID);
    invariant(!isPromise || !isSync, 'Cannot have a method that is both async and a sync hook');
    const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
    module[methodName] = genMethod(moduleID, methodID, methodType);
  });
  Object.assign(module, constants);

  if (__DEV__) {
    BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
  }

  return { name: moduleName, module };
}

生成js端的NativeModule。

  1. const [moduleName, constants, methods, promiseMethods, syncMethods] = config;解析出來參數在上一個方法中已經分析過了。
  2. 遍歷這個methods數組,通過genMethod方法,生成Method方法,賦值到module對象中。

注意方法有三種類型,promise:通過Promise方式接收返回值的異步方法,async:通過Callback方式接收返回值得異步方法,sync:等待接收返回值的同步方法。

3.再將constants對象的屬性全部賦值到module對象中,所以我們可以在js端的通過NativeModule實例,直接使用Java端NativeModule定義的常量值。

function 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>) {
      if (__DEV__) {
        invariant(global.nativeCallSyncHook, 'Calling synchronous methods on native ' +
          'modules is not supported in Chrome.\n\n Consider providing alternative ' +
          'methods to expose this method in debug mode, e.g. by exposing constants ' +
          'ahead-of-time.');
      }
      return global.nativeCallSyncHook(moduleID, methodID, args);
    };
  } else {
    fn = function(...args: Array<any>) {
      const lastArg = args.length > 0 ? args[args.length - 1] : null;
      const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
      const hasSuccessCallback = typeof lastArg === 'function';
      const hasErrorCallback = typeof secondLastArg === 'function';
      hasErrorCallback && invariant(
        hasSuccessCallback,
        'Cannot have a non-function arg after a function arg.'
      );
      const onSuccess = hasSuccessCallback ? lastArg : null;
      const onFail = hasErrorCallback ? secondLastArg : null;
      const callbackCount = hasSuccessCallback + hasErrorCallback;
      args = args.slice(0, args.length - callbackCount);
      BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
    };
  }
  fn.type = type;
  return fn;
}

分三種情況:

  1. promise:返回一個方法,這個方法的返回值是Promise。

調用BatchedBridge.enqueueNativeCall方法,然后等待BatchedBridge的回調。

  1. sync:返回一個方法,這個方法的返回值就是java端調用的結果值。

它調用global.nativeCallSyncHook方法,這個方法是在JSCExecutor.cpp中注冊到全局變量global中的,所以會調用JSCExecutor.cpp中的nativeCallSyncHook,然后調用到java端,得到結果值,返回到js端,整個過程都在同步等待。

  1. async:返回一個方法,這個方法沒有返回值,

但是調用這個方法時,可以最后一個參數傳一個方法,通過這個方法回傳java端返回的結果值。對應的java端NativeModule方法定義的時候也要定義這個Callback參數。這種方式沒有promise好,所以請盡量使用promise方法。最后也是使用BatchedBridge.enqueueNativeCall發送事件,等待回調。

下節預覽

我們分析完了ReactNative的通信機制,那么還剩下一個重要點,就是怎么根據js端代碼生成android端view的,又是怎么相互操作的。那么就要分析UIManagerModule這個類,我們在下一節進行分析。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • ReactNative: 源碼分析系列 ReactNative 啟動過程源碼分析[https://www.jian...
    wo883721閱讀 1,257評論 0 0
  • ReactNative: 源碼分析系列 ReactNative 啟動過程源碼分析ReactNative 通信機制_...
    wo883721閱讀 1,241評論 0 0
  • 昨天陪老公出差桂林,結果他出去應酬,我等得睡著了。就又忘記反思。 早上專欄留言。三個鐘頭到桂林。下午和老公去象山公...
    甘露yuer閱讀 129評論 1 0
  • 要轉化你的內心,在一大早時許下承諾:“今天無論遇到什么困難,我絕對不會讓自己有生氣、嫉妒、怨恨等情緒。”與此同時,...
    吳蕙蘭閱讀 314評論 0 0