React-native 原理探究

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

推荐阅读更多精彩内容