RN中Js如何调用Native的代码

js调用java.png

react-native.js 其实就是声明了ReactNative提供的可以在js中使用的各种模块。

var ReactNative = Object.assign(Object.create(require('React')), {
  ...
  ToastAndroid: require('ToastAndroid'),
});
module.exports = ReactNative;

ToastAndroid.android.js show方法其实就是调用了RCTToastAndroid.show(message, duration)

var RCTToastAndroid = require('NativeModules').ToastAndroid;
var ToastAndroid = {
  SHORT: RCTToastAndroid.SHORT,
  LONG: RCTToastAndroid.LONG,
  show: function (
     message: string,
     duration: number
  ): void {
     RCTToastAndroid.show(message, duration);
  },
};

NativeModules.js RCTToastAndroid是定义在NativeModules中的

var NativeModules = require('BatchedBridge').RemoteModules;
var nativeModulePrefixNormalizer = require('nativeModulePrefixNormalizer');
nativeModulePrefixNormalizer(NativeModules);
module.exports = NativeModules;

BatchedBridge.js 最终,所有的模块都是来自BatchedBridge,它做的事情就是构造一个MessageQueue对象。

let MessageQueue = require('MessageQueue');
let BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);
module.exports = BatchedBridge;

ToastAndroid.show方法其实最终调用的是MessageQueue.ToastAndroid.show方法。


__fbBatchedBridgeConfig 是一个全局js变量,它是在CatalystInstance.java中声明赋值的,通过调用ReactBridge.setGlobalVariable方法。setGlobalVariable是在Jni中声明的方法,最终会调用JavaScriptCore,把Java中定义的JSON字符串,赋值给js的全局对象__fbBatchedBridgeConfig,这个对象会有两个属性remoteModuleConfig和localModulesConfig。

__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig,

CatalystInstanceImpl.java

private native void initializeBridge(
    ReactCallback callback,
    JavaScriptExecutor jsExecutor,
    MessageQueueThread jsQueue,
    MessageQueueThread moduleQueue,
    Collection<JavaModuleWrapper> javaModules,
    Collection<ModuleHolder> cxxModules);
private void initializeBridge(
    JavaScriptExecutor jsExecutor,
    NativeModuleRegistry registry,
    JavaScriptModulesConfig jsModulesConfig,
    JSBundleLoader jsBundleLoader) {
  mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
  Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
  mBridge = new ReactBridge(
      jsExecutor,
      new NativeModulesReactCallback(),
      mCatalystQueueConfiguration.getNativeModulesQueueThread());
  mBridge.setGlobalVariable(
      "__fbBatchedBridgeConfig",
      buildModulesConfigJSONProperty(registry, jsModulesConfig));
  jsBundleLoader.loadScript(mBridge);
}

__fbBatchedBridgeConfig.remoteModuleConfig 代表的是Java中定义的一些模块,这些模块可以在js中被调用。格式如下:

{
  "remoteModuleConfig": {
    "Logger": {
      "constants": { /* If we had exported constants... */ },
      "moduleID": 1,
      "methods": {
        "requestPermissions": {
          "type": "remote",
          "methodID": 1
        }
      }
    }
  }
}
{
  'ToastAndroid': {
    moduleId: 0,
    methods: {
      'show': {
        methodID: 0
      }
    },
    constants: {
      'SHORT': '0',
      'LONG': '1'
    }
  },
  'moduleB': {
    moduleId: 0,
    methods: {
      'method1': {
        methodID: 0
      }
    },
    'key1': 'value1',
    'key2': 'value2'
  }
}

MessageQueue.js 构造函数中首先定义了一些实例变量,注释里面的js module指的是只在js中定义的模块,native module指的是在native(这里就是Java)层定义的模块,这些模块都可以在js中使用。

this.RemoteModules = {};//存储最终生成的各个模块信息,包含模块名,模块中的方法,常量等信息
this._require = customRequire || require;//用于加载模块的函数
this._queue = [[],[],[]];//队列,用于存放调用的模块,方法和参数信息,分别存储在第一二三个数组中
this._moduleTable = {};//moduleId查找moduleName的map,用于js module
this._methodTable = {};//methodId查找methodName的map,用于js module
this._callbacks = [];//回调函数数组,和queue一一对应,每个queue中调用的方法,如果有回调函数,那么就在这个数组的对应坐标上
this._callbackID = 0;//回调函数的id,自增

this._genModules(remoteModules);
localModules && this._genLookupTables(
  localModules, this._moduleTable, this._methodTable);

this._debugInfo = {};//放置一些debug相关的信息,主要是调用模块,函数,参数的信息
this._remoteModuleTable = {};//moduleId查找moduleName的map,用于native module
this._remoteMethodTable = {};//methodId查找methodName的map,用于native module

this._genLookupTables(
  remoteModules, this._remoteModuleTable, this._remoteMethodTable);

_genModules(remoteModules) 遍历传过来的remoteModules所有的key,得到moduleName,然后针对每个module调用_genModule方法

_genModules(remoteModules) {
  let moduleNames = Object.keys(remoteModules);
  for (var i = 0, l = moduleNames.length; i < l; i++) {
    let moduleName = moduleNames[i];
    let moduleConfig = remoteModules[moduleName];
    this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
  }
}

_genModule(module, moduleConfig) _genModule方法和_genModules方法类似,遍历module下面的所有的方法,对每个方法,调用_genMethod方法。

_genModule(module, moduleConfig) {
  let methodNames = Object.keys(moduleConfig.methods);
  for (var i = 0, l = methodNames.length; i < l; i++) {
    let methodName = methodNames[i];
    let methodConfig = moduleConfig.methods[methodName];
    module[methodName] = this._genMethod(
      moduleConfig.moduleID, methodConfig.methodID, methodConfig.type);
  }
  Object.assign(module, moduleConfig.constants);
  return module;
}
_genMethod(module, method, type) {
  ...
  fn = function(...args) {
    let lastArg = args.length > 0 ? args[args.length - 1] : null;
    let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
    let hasSuccCB = typeof lastArg === 'function';
    let hasErrorCB = typeof secondLastArg === 'function';
    hasErrorCB && invariant(
      hasSuccCB,
      'Cannot have a non-function arg after a function arg.'
    );
    let numCBs = hasSuccCB + hasErrorCB;
    let onSucc = hasSuccCB ? lastArg : null;
    let onFail = hasErrorCB ? secondLastArg : null;
    args = args.slice(0, args.length - numCBs);
    return self.__nativeCall(module, method, args, onFail, onSucc);
  };
}

__nativeCall(module, method, params, onFail, onSucc)

  • 方法首先检查是否有onFail和onSucc,如果有的话就压入_callbacks栈中,同时把_callbackID存入参数中。
  • 接着可以看到,_queue其实被当做了三个栈来使用,分别压入模块名,方法名和参数信息。
  • 到这里MessageQueue的构造函数就分析的差不多了,那么我们最开始的ToastAndroid.show(message, duration);方法调用,其实就是往_queue栈中压入了一些信息而已,那么最终是怎么在Java层调用到Android原生的Toast模块的呢?
__nativeCall(module, method, params, onFail, onSucc) {
  if (onFail || onSucc) {
    // eventually delete old debug info
    (this._callbackID > (1 << 5)) &&
      (this._debugInfo[this._callbackID >> 5] = null);

    this._debugInfo[this._callbackID >> 1] = [module, method];
    onFail && params.push(this._callbackID);
    this._callbacks[this._callbackID++] = onFail;
    onSucc && params.push(this._callbackID);
    this._callbacks[this._callbackID++] = onSucc;
  }
  this._queue[MODULE_IDS].push(module);
  this._queue[METHOD_IDS].push(method);
  this._queue[PARAMS].push(params);

  var now = new Date().getTime();
  if (global.nativeFlushQueueImmediate &&
      now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
    global.nativeFlushQueueImmediate(this._queue);
    this._queue = [[],[],[]];
    this._lastFlush = now;
  }

  if (__DEV__ && SPY_MODE && isFinite(module)) {
    console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
      this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
  }
}

  • 这里的关键就是__nativeCall中调用的nativeFlushQueueImmediate方法,这个方法其实C++代码中注入到Js的一个全局变量,具体怎么注入的,就是在JSCExecutor.cpp中调用installGlobalFunction,installGlobalFunction的是通过JavaScriptCore的API来实现让Js可以调用C++代码的。
  • nativeFlushQueueImmediate其实又调用了JSCExecutor.cpp中的flushQueueImmediate方法。
  • 其中,m_flushImmediateCallback是在JSCExecutor的构造函数中初始化。
  • 那么JSCExecutor对象又是在哪了被创建出来的呢?RN中是通过JSCExecutorFactory这个工厂的createJSExecutor方法来创建JSCExecutor对象的,而这个方法的实现刚好就在JSCExecutor.cpp中。

JSCExecutor.cpp

JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
    m_flushImmediateCallback(cb) {}

installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook);
installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow);

void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
  m_flushImmediateCallback(queueJSON);
}

std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
  return std::unique_ptr<JSExecutor>(new JSCExecutor(cb));
}

  • 接下来问题又来了,又是谁调用了JSCExecutorFactory.createJSExecutor呢?答案就是Bridge.cpp,
  • Bridge.cpp是RN的Jni层的入口,Java层大部分调用的Jni函数都是在这个文件中定义的。

Bridge.cpp

Bridge::Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback) :
  m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback)));

JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
    m_callback(callback)
  {
    m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) {
      m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
    });
  }

ReactBridge.java回去调用jni中注册的initialize方法。RN所有jni中注册的方法,都在OnLoad.cpp。

OnLoad.cpp 可以看到initialize方法其实就是OnLoad.cpp中的bridge这个namespace下得create方法

registerNatives("com/facebook/react/bridge/ReactBridge", {
    makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/JavaScriptExecutor;Lcom/facebook/react/bridge/ReactCallback;Lcom/facebook/react/bridge/queue/MessageQueueThread;)V", bridge::create),
    makeNativeMethod(
      "loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
      bridge::loadScriptFromAssets),
    makeNativeMethod("loadScriptFromFile", bridge::loadScriptFromFile),
    makeNativeMethod("callFunction", bridge::callFunction),
    makeNativeMethod("invokeCallback", bridge::invokeCallback),
    makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
    makeNativeMethod("supportsProfiling", bridge::supportsProfiling),
    makeNativeMethod("startProfiler", bridge::startProfiler),
    makeNativeMethod("stopProfiler", bridge::stopProfiler),
    makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate),
    makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical),
});
static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback,
                   jobject callbackQueueThread) {
  auto weakCallback = createNew<WeakReference>(callback);
  auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
  auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector<MethodCall> calls, bool isEndOfBatch) {
    dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls), isEndOfBatch);
  };
  auto nativeExecutorFactory = extractRefPtr<JSExecutorFactory>(env, executor);
  auto bridge = createNew<Bridge>(nativeExecutorFactory, bridgeCallback);
  setCountableForJava(env, obj, std::move(bridge));
}

这里调用了Bridge类的构造函数,而Bridge的构造函数中,回去构造一个JSThreadState对象,Bridge所有的API调用都会委托给这个创建的JSThreadState对象。

Bridge::Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback) :
  m_callback(callback),
  m_destroyed(std::shared_ptr<bool>(new bool(false)))
{
  auto destroyed = m_destroyed;
  auto proxyCallback = [this, destroyed] (std::vector<MethodCall> calls, bool isEndOfBatch) {
    if (*destroyed) {
      return;
    }
    m_callback(std::move(calls), isEndOfBatch);
  };
  m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback)));
}
  • JSThreadState的构造函数中,会去创建一个JSCExecutor类的对象
  • 这里createJSExecutor方法中,传递的是一个C++的函数,这个函数会被赋值给m_flushImmediateCallback成员变量。
JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
    m_callback(callback)
{
  m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) {
    m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
  });
}

找到了创建JSCExecutor对象的地方了,回到刚才,我们说,所有的Js调用Native Module的API的时候,都会调用flushQueueImmediate方法,而flushQueueImmediate方法中会去调用m_flushImmediateCallback函数。

JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
    m_flushImmediateCallback(cb) {
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
  return std::unique_ptr<JSExecutor>(new JSCExecutor(cb));
}

也就是说,Js层所有的API调用,都会走到这个m_flushImmediateCallback函数的调用中,这个函数是实现Js和Native通讯的核心。而这里的m_flushImmediateCallback在CPP层,最终是在OnLoad.cpp的bridge::create方法中传入的,这个create方法又是由Java层来调用的,Bridge.java的构造函数中会调用这个create方法,所以说,Js层调用Native API的时候,最终就是调用了Bridge.java中传递过来的ReactCallback对象

public ReactBridge(
      JavaScriptExecutor jsExecutor,
      ReactCallback callback,
      MessageQueueThread nativeModulesQueueThread) {
        .....
}
  • ReactBridge对象的创建,是在CatalystInstanceImpl.java中
  • 这里传递的是ReactCallback类的子类NativeModulesReactCallback,最终调用的是call方法,而call方法又调用了mJavaRegistry.call方法。看到mJavaRegistry,开发过RN应用的同学应该感到很熟悉了,对的,这里就是对应我们在应用启动的时候注册的NativeModule对象。
private ReactBridge initializeBridge
  bridge = new ReactBridge(
            jsExecutor,
            new NativeModulesReactCallback(),
            mCatalystQueueConfiguration.getNativeModulesQueueThread());

private class NativeModulesReactCallback implements ReactCallback {
      public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
        mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
    }
}

NativeModuleRegistry.Java

/* package */ void call(
    CatalystInstance catalystInstance,
    int moduleId,
    int methodId,
    ReadableNativeArray parameters) {
  ModuleDefinition definition = mModuleTable.get(moduleId);
  if (definition == null) {
    throw new RuntimeException("Call to unknown module: " + moduleId);
  }
  definition.call(catalystInstance, methodId, parameters);
}

我们在RN中注册NativeModule是通过add一个ReactPackage对象来实现,家下来我们看一下,RN是如何把我们注册的各个module添加到NativeModuleRegistry中的。

mReactInstanceManager = ReactInstanceManager.builder()
                .addPackage(new MainReactPackage())
                .build();

ReactInstanceManager.Builder的build方法,会new一个ReactInstanceManagerImpl对象,把我们的ReactPackage对象传递过去。ReactInstanceManagerImpl类的核心就是createReactContext方法,createReactContext会首先遍历所有注册的ReactPackage,对所有的NativeModule,构造一个ModuleDefinition对象,保存到nativeModuleRegistry对象的mModuleTable中。最后,createReactContext会通过CatalystInstanceImpl.Builder构造一个CatalystInstance对象,并把包含各个NativeModule信息的nativeModuleRegistry对象传递过去。具体的模块的注册过程,我后面再写一篇单独的博客介绍,此处就不啰嗦了。

private ReactApplicationContext createReactContext(JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
    ...
    for (ReactPackage reactPackage : mPackages) {
      processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    }
    ...
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
      .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
      .setJSExecutor(jsExecutor)
      .setRegistry(nativeModuleRegistry)
      .setJSModulesConfig(javaScriptModulesConfig)
      .setJSBundleLoader(jsBundleLoader)
      .setNativeModuleCallExceptionHandler(exceptionHandler);
}

回到我们最开始的例子中,我们要在Js中使用Toast,那么我们的MainReactPackage中,就需要构造一个ToastModule,来注册给RN,这样才可以给Js调用。

public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Arrays.<NativeModule>asList(
      new ToastModule(reactContext));
  }

需要注意的是,所有要给Js中使用的模块,都需要在Native这边注册一个对应的Module


在Js中调用ToastAndroid这个模块的流程就是,Js调用会调用到C++中m_flushImmediateCallback函数,参数就是Js函数的参数加上调用的模块名构成的一个JSON字符串。C++中,有一个parseMethodCalls方法,会从Js传递的JSON中,解析出moduleName,functionName,参数等一系列信息,然后C++层会调用Java层的ReactCallback类,Java代码中,会根据传递来的moduleName,functionName找到对应的模块中的方法,然后通过反射执行这些方法,并把参数传递过去。这样就完成了Js对Native代码的调用。

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 8,618评论 0 3
  • 小伙伴们都知道在Android开发中实现Java和JS的通信可以通过WebView来实现,包括注册JSBridge...
    juexingzhe阅读 2,517评论 2 11
  • 碱性备孕,认准益生碱 备孕妈妈的最佳选择,上过电视的碱性营养食品,得到315诚信品牌的口碑营养食品,全国仅此一家❗...
    博帕不怕阅读 79评论 0 0
  • 读书:只为取悦自己 读书,仿佛一直是一件严肃的事情。我们一直是为了一些目的性而读书,取悦别人而读书。其实,如何在生...
    袁小圆儿阅读 671评论 8 7