React Native React

React-native 原理探究

2017-10-08  本文已影响118人  木中木

运用一个架构,总得了解一下背后的原理,本文基于react-native的最新版本(0.47.1),简要探究一下react-native背后到底做了哪些操作。

一、通信机制
RN框架最主要的就是实现了一套JAVA和 JS通信的方案,该方案可以做到比较简便的互调对方的接口。一般的JS运行环境是直接扩展JS接口,然后JS通过扩展接口发送信息到主线程。但RN的通信的实现机制是单向调用,Native线程定期向JS线程拉取数据, 然后转成JS的调用预期,最后转交给Native对应的调用模块。这样最终同样也可以达到Java和 JS 定义的Module互相调用的目的。

1.js调用Java
①现在版本是通过如下调用

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


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

上面是我们封装一个下载插件,我们去看下NativeModules,我们拿出这块代码:

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初始化,其实就是把js的信息写入到一个消息队列中(messageQueue),其中注意一下genModule这个方法,我们去看这个源码发现这里先是对js传过来的config信息做非空、命名检查Promise和sync方法检查,如果检查合格就调用genMethod方法,如下图

1-1.png

然后我们看下genMethod方法,如2-1图,这里我们看的很清楚了,这个方法根据方法类型来分别调用BatchedBridge.enqueueNativeCall这个方法,这个方法里面有三个入参,moduleID,MethodID,args和两个回调,看到这里,我们应该都能猜想到,native层应该是通过moduleID来确定是调用哪个类,MethodID确定是这个类的哪个方法,args就是参数了,回调就是正确回调和错误回调了,不信我们往下看。

2-1.png

那么我们看下enqueueNativeCall这个方法,这里我们注意到多了一个callID,其实往下看你就会知道native层就是通过这个callID进行回调,通过一些处理后,把方法调用信息push到消息队列中(_queue).

3-1.png

最后,通过nativeFlushQueueImmediate 方法理解发送到消息队列,等待native来取,至此,js层的处理已经全部完成。这里我们还要注意一点,这个方法把模块名、方法名、调用参数放到数组里存起来,如果上次调用和本次调用想着超过5ms则调用c++的nativeFlushQueueImmediate方法,如果小于5ms就直接返回了。可见js调用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通过一定条件触发,这里我们暂时不管什么时候触发,先看下native怎么调用js层的信息。从上面我们知道js最后一步是走到了调用nativeFlushQueueImmediate方法,那这个方法在native里面是怎么调用生成的呢?
nativeFlushQueueImmediate这个方法其实C++代码中注入到Js的一个全局变量,具体怎么注入的,就是在JSCExecutor.cpp中调用installGlobalFunction,installGlobalFunction的是通过JavaScriptCore的API来实现让Js可以调用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又调用了flushQueueImmediate方法,flushQueueImmediate方法中有调用了callNativeModules,那么问题来了,这个callNativeModules是从哪里来的呢?看下图5-1,这里我们已经很清楚了在JsToNativeBridge方法中调用了callNativeMethod,再看下入参,是不是跟js中的入参一样,O(∩_∩)O哈哈~。

5-1.png

这里的callNativeMethod是调用ModuleRegistry中的方法,在这个方法中,我们看到了用到反射invoke方法,那么这个invoke方法其实是定义在NativeModule .h文件中的一个虚方法,这个虚方法又是被JavaModuleWrapper.cpp实现了,在这个方法里面最后是使用反射调用了jni中对应JavaModuleWrapper.java的方法,然后我们去看下JavaModuleWrapper.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中,我们看到了这个方法,这里我们应该就可以明白了已经调到Java的方法了,那么至此js调用native到此结束。

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

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

下面是整个交互流程。

6-1.png

②Native调用JS流程
首先我们找到CatalystInstance类,这个是在JNI中,下面这两方法,第一个方法正是上面js调用完native后,回调通过这里回调回去,可以看到用到的是callbackid,这个正是上面js在messagequeue中生成的那个id;然后第二个方法是调用js方法,对应三个参数:js类,js方法,参数。

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

再去看看catalystInstance具体的实现类,如下最后调用了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);
  }

讲到这里,实际上reactContext目前并没有入口提供给我们。我们通常调用的是ReactContext.getJSModule(JSModule类名.class).方法名(params);

ReactContext调用的是CatalystInstance的同名方法。

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

那这个方法也是先收集js模块的所有信息并验证最后调用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,这个就有开始调用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);
    });
}

最后发消息发送到js的messagequeue上。

总结一下整个流程:

1.MessageQueue把Native调用的方法放到JavaScriptCore中
2.JS Module把可以调用的方法放到MessageQueue的一个对列中
3.Native从JavaScriptCore中拿到JS的调用入口,并把Module Name、Method Name、Parameters传过去
4.执行JS Module的方法

image.png
上一篇下一篇

猜你喜欢

热点阅读