React-native 原理探究

運(yùn)用一個(gè)架構(gòu),總得了解一下背后的原理,本文基于react-native的最新版本(0.47.1),簡要探究一下react-native背后到底做了哪些操作。

一、通信機(jī)制
RN框架最主要的就是實(shí)現(xiàn)了一套JAVA和 JS通信的方案,該方案可以做到比較簡便的互調(diào)對方的接口。一般的JS運(yùn)行環(huán)境是直接擴(kuò)展JS接口,然后JS通過擴(kuò)展接口發(fā)送信息到主線程。但RN的通信的實(shí)現(xiàn)機(jī)制是單向調(diào)用,Native線程定期向JS線程拉取數(shù)據(jù), 然后轉(zhuǎn)成JS的調(diào)用預(yù)期,最后轉(zhuǎn)交給Native對應(yīng)的調(diào)用模塊。這樣最終同樣也可以達(dá)到Java和 JS 定義的Module互相調(diào)用的目的。

1.js調(diào)用Java
①現(xiàn)在版本是通過如下調(diào)用

import { NativeModules,  DeviceEventEmitter} from 'react-native';


export const  download=(opt) =>{
NativeModules.DownloadFileManager.download(opt);
}

上面是我們封裝一個(gè)下載插件,我們?nèi)タ聪翹ativeModules,我們拿出這塊代碼:

let NativeModules : {[moduleName: string]: Object} = {};
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)
      });
    }
  });
}

這里我們簡要看一下,大體意思就是nativeModule初始化,其實(shí)就是把js的信息寫入到一個(gè)消息隊(duì)列中(messageQueue),其中注意一下genModule這個(gè)方法,我們?nèi)タ催@個(gè)源碼發(fā)現(xiàn)這里先是對js傳過來的config信息做非空、命名檢查Promise和sync方法檢查,如果檢查合格就調(diào)用genMethod方法,如下圖

1-1.png

然后我們看下genMethod方法,如2-1圖,這里我們看的很清楚了,這個(gè)方法根據(jù)方法類型來分別調(diào)用BatchedBridge.enqueueNativeCall這個(gè)方法,這個(gè)方法里面有三個(gè)入?yún)?,moduleID,MethodID,args和兩個(gè)回調(diào),看到這里,我們應(yīng)該都能猜想到,native層應(yīng)該是通過moduleID來確定是調(diào)用哪個(gè)類,MethodID確定是這個(gè)類的哪個(gè)方法,args就是參數(shù)了,回調(diào)就是正確回調(diào)和錯(cuò)誤回調(diào)了,不信我們往下看。

2-1.png

那么我們看下enqueueNativeCall這個(gè)方法,這里我們注意到多了一個(gè)callID,其實(shí)往下看你就會知道native層就是通過這個(gè)callID進(jìn)行回調(diào),通過一些處理后,把方法調(diào)用信息push到消息隊(duì)列中(_queue).

3-1.png

最后,通過nativeFlushQueueImmediate 方法理解發(fā)送到消息隊(duì)列,等待native來取,至此,js層的處理已經(jīng)全部完成。這里我們還要注意一點(diǎn),這個(gè)方法把模塊名、方法名、調(diào)用參數(shù)放到數(shù)組里存起來,如果上次調(diào)用和本次調(diào)用想著超過5ms則調(diào)用c++的nativeFlushQueueImmediate方法,如果小于5ms就直接返回了。可見js調(diào)用native總是有開銷的。

    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);
    }

native通過一定條件觸發(fā),這里我們暫時(shí)不管什么時(shí)候觸發(fā),先看下native怎么調(diào)用js層的信息。從上面我們知道js最后一步是走到了調(diào)用nativeFlushQueueImmediate方法,那這個(gè)方法在native里面是怎么調(diào)用生成的呢?
nativeFlushQueueImmediate這個(gè)方法其實(shí)C++代碼中注入到Js的一個(gè)全局變量,具體怎么注入的,就是在JSCExecutor.cpp中調(diào)用installGlobalFunction,installGlobalFunction的是通過JavaScriptCore的API來實(shí)現(xiàn)讓Js可以調(diào)用C++代碼的。

  installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
  installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");

  installGlobalFunction(m_context, "nativeLoggingHook", JSCNativeHooks::loggingHook);
  installGlobalFunction(m_context, "nativePerformanceNow", JSCNativeHooks::nowHook);

  #if DEBUG
  installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
  #endif

  ...

JSValueRef JSCExecutor::nativeFlushQueueImmediate(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if (argumentCount != 1) {
    throw std::invalid_argument("Got wrong number of args");
  }

  flushQueueImmediate(Value(m_context, arguments[0]));
  return Value::makeUndefined(m_context);
}
...
void JSCExecutor::flushQueueImmediate(Value&& queue) {
  auto queueStr = queue.toJSONString();
  m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false);
}

這里nativeFlushQueueImmediate又調(diào)用了flushQueueImmediate方法,flushQueueImmediate方法中有調(diào)用了callNativeModules,那么問題來了,這個(gè)callNativeModules是從哪里來的呢?看下圖5-1,這里我們已經(jīng)很清楚了在JsToNativeBridge方法中調(diào)用了callNativeMethod,再看下入?yún)?,是不是跟js中的入?yún)⒁粯樱琌(∩_∩)O哈哈~。

5-1.png

這里的callNativeMethod是調(diào)用ModuleRegistry中的方法,在這個(gè)方法中,我們看到了用到反射invoke方法,那么這個(gè)invoke方法其實(shí)是定義在NativeModule .h文件中的一個(gè)虛方法,這個(gè)虛方法又是被JavaModuleWrapper.cpp實(shí)現(xiàn)了,在這個(gè)方法里面最后是使用反射調(diào)用了jni中對應(yīng)JavaModuleWrapper.java的方法,然后我們?nèi)タ聪翵avaModuleWrapper.java方法

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);
}
....
class NativeModule {
 public:
  virtual ~NativeModule() {}
  virtual std::string getName() = 0;
  virtual std::vector<MethodDescriptor> getMethods() = 0;
  virtual folly::dynamic getConstants() = 0;
  virtual void invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) = 0;
  virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic&& args) = 0;
};

}
}
...
void NewJavaNativeModule::invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) {
  if (reactMethodId >= methods_.size()) {
    throw std::invalid_argument(
      folly::to<std::string>("methodId ", reactMethodId, " out of range [0..", methods_.size(), "]"));
  }
  CHECK(!methods_[reactMethodId].isSyncHook()) << "Trying to invoke a synchronous hook asynchronously";
  messageQueueThread_->runOnQueue([this, reactMethodId, params=std::move(params), callId] () mutable {
    #ifdef WITH_FBSYSTRACE
    if (callId != -1) {
      fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
    }
    #endif
    invokeInner(reactMethodId, std::move(params));
  });
}
MethodCallResult NewJavaNativeModule::invokeInner(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {
  return methods_[reactMethodId].invoke(instance_, module_.get(), token, params);
}

在JavaModuleWrapper.java中,我們看到了這個(gè)方法,這里我們應(yīng)該就可以明白了已經(jīng)調(diào)到Java的方法了,那么至此js調(diào)用native到此結(jié)束。

  @DoNotStrip
  public void invoke(int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
      return;
    }

    mMethods.get(methodId).invoke(mJSInstance, parameters);
  }

下面是整個(gè)交互流程。

6-1.png

②Native調(diào)用JS流程
首先我們找到CatalystInstance類,這個(gè)是在JNI中,下面這兩方法,第一個(gè)方法正是上面js調(diào)用完native后,回調(diào)通過這里回調(diào)回去,可以看到用到的是callbackid,這個(gè)正是上面js在messagequeue中生成的那個(gè)id;然后第二個(gè)方法是調(diào)用js方法,對應(yīng)三個(gè)參數(shù):js類,js方法,參數(shù)。

  @Override @DoNotStrip
  void invokeCallback(
      int callbackID,
      NativeArray arguments);
  @DoNotStrip
  void callFunction(
      String module,
      String method,
      NativeArray arguments);

再去看看catalystInstance具體的實(shí)現(xiàn)類,如下最后調(diào)用了jniCallJSFunction

  @Override
  public void callFunction(
      final String module,
      final String method,
      final NativeArray arguments) {
    if (mDestroyed) {
      final String call = module + "." + method + "(" + arguments.toString() + ")";
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed: " + call);
      return;
    }
    if (!mAcceptCalls) {
      // Most of the time the instance is initialized and we don't need to acquire the lock
      synchronized (mJSCallsPendingInitLock) {
        if (!mAcceptCalls) {
          mJSCallsPendingInit.add(new PendingJSCall(module, method, arguments));
          return;
        }
      }
    }

    jniCallJSFunction(module, method, arguments);
  }

講到這里,實(shí)際上reactContext目前并沒有入口提供給我們。我們通常調(diào)用的是ReactContext.getJSModule(JSModule類名.class).方法名(params);

ReactContext調(diào)用的是CatalystInstance的同名方法。

  @Override
  public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
    return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
  }

那這個(gè)方法也是先收集js模塊的所有信息并驗(yàn)證最后調(diào)用InvocationHandler的invoke()方法。

    @Override
    public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
      NativeArray jsArgs = args != null
        ? Arguments.fromJavaArgs(args)
        : new WritableNativeArray();
      mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs);
      return null;
    }

這里我們看到了上面說到的callFunction 方法了,然后我們看下jniCallJSFunction,這個(gè)就有開始調(diào)用C++

  private native void jniCallJSFunction(
    String module,
    String method,
    NativeArray arguments);

在nativeToBridge.cpp中,我們看到了跟Java同名的方法,就是它了


void NativeToJsBridge::callFunction(
    std::string&& module,
    std::string&& method,
    folly::dynamic&& arguments) {
  int systraceCookie = -1;
  #ifdef WITH_FBSYSTRACE
  systraceCookie = m_systraceCookie++;
  FbSystraceAsyncFlow::begin(
      TRACE_TAG_REACT_CXX_BRIDGE,
      "JSCall",
      systraceCookie);
  #endif

  runOnExecutorQueue([module = std::move(module), method = std::move(method), arguments = std::move(arguments), systraceCookie]
    (JSExecutor* executor) {
      #ifdef WITH_FBSYSTRACE
      FbSystraceAsyncFlow::end(
          TRACE_TAG_REACT_CXX_BRIDGE,
          "JSCall",
          systraceCookie);
      SystraceSection s("NativeToJsBridge::callFunction", "module", module, "method", method);
      #endif
      // This is safe because we are running on the executor's thread: it won't
      // destruct until after it's been unregistered (which we check above) and
      // that will happen on this thread
      executor->callFunction(module, method, arguments);
    });
}

最后發(fā)消息發(fā)送到j(luò)s的messagequeue上。

總結(jié)一下整個(gè)流程:

1.MessageQueue把Native調(diào)用的方法放到JavaScriptCore中
2.JS Module把可以調(diào)用的方法放到MessageQueue的一個(gè)對列中
3.Native從JavaScriptCore中拿到JS的調(diào)用入口,并把Module Name、Method Name、Parameters傳過去
4.執(zhí)行JS Module的方法

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容