React-Native最核心的是NativeJavascript之间的通信,并且是双向通信。Native层到Javascript层,Javascript层到Native层。虽说是两个方向,但实现上大同小异,我们先从Native层入手,研究一下Native调用Javascript的过程。


1、通信模型

Android应用层的程序语言是JavaReact-NativeNative端的框架实现用的也是Java语言,所以实质上是JavaJavascript两种程序语言的调用。

事实上这个过程,在Android系统上已经有了实现。就是WebView。熟悉WebView的都知道底层实现是WebKit,虽然在Android 4.4系统上切换成了Chromium,但归根结底还是WebKit的变种,仅仅是加了谷歌自己的一些东西。然而React-NativeWebView并没有一点关系,并且后者的WebKit内核也不支持ES6特性(React语法大多基于ES6),那怎么办?仅仅能自己弄一套最新的WebKit作为React-Native的解释器了,这个从安卓projectlib文件夹以下的libjsc.so动态链接库文件能够印证,这样做还有两个重要优点就是兼容绝大多少设备版本号和方便加入自己定义功能。

所以由此,我们大概能够猜到React-Native的通信原理,画一张图来简单地描写叙述一下:


2、Java层实现

之前说过。React-Native的重要设计思想是组件化,为了便于维护扩展和减少耦合,React-Native并没有为了实现某一详细的通信编写代码(比方上篇博文所讲的触摸事件传递),而是设计了一套标准用于组件化。

这套标准是向开发人员开放的,开发人员能够自行编写须要的组件用来在NativeJavascript之间通信,虽然这并非推荐的选择。

2.1 JavaScriptModule组件

React-Native官方实现了一定数量的组件,比方触摸事件组件。按键组件等。这些组件都位于CoreModulesPackage中,属于默认载入的。全部的组件都必须继承JavaScriptModule接口标准。JavaScriptModule位于com.facebook.react.bridge包以下:

/*** Interface denoting that a class is the interface to a module with the same name in JS. Calling* functions on this interface will result in corresponding methods in JS being called.** When extending JavaScriptModule and registering it with a CatalystInstance, all public methods* are assumed to be implemented on a JS module with the same name as this class. ** NB: JavaScriptModule does not allow method name overloading because JS does not allow method name* overloading.*/
@DoNotStrip
public interface JavaScriptModule {
}

阅读一下凝视,主要有三点信息:
1、全部组件必须继承JavaScriptModule,并注冊在CatalystInstance中。
2、全部public方法与Javascript层保持同名并由后者详细实现。

3、因为Javascript不支持重载。所以Java中也不能有重载。

细致的读者会发现,凝视里有两个单词非常关键。extendingimplementedJavaextendJavascriptimplement,也就是说Java层仅仅做接口定义。而实现由Javascript完毕。所以。搜索一下JavaScriptModule的子类会发现它们都是接口。没有详细实现类。

有点晦涩但事实上非常好理解,举个简单的样例。去餐馆吃饭,顾客(Java)仅仅要定义(interface)好想吃什么菜,然后和餐馆(Bridge)说。餐馆会通知自己的厨师(Javascript)把详细的菜做好。这就是一个简单的通信过程Java->Bridge->Javascript

2.2 JavaScriptModule组件的注冊

上一篇文章讲的触摸事件的处理。里面提到一个RCTEventEmitter的类,用来将每一个触摸事件都传递给Javascript层,这个组件就是继承于JavaScriptModule

public interface RCTEventEmitter extends JavaScriptModule {public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);public void receiveTouches(String eventName,WritableArray touches,WritableArray changedIndices);
}

先前凝视中第1点,全部JavaScriptModule组件都必须在CatalystInstance中注冊。那我们来看一下注冊的过程。

RCTEventEmitterfacebook官方定义的。组装在CoreModulesPackage中。而全部的package都是在com.facebook.react.ReactInstanceManagerImpl中处理的,看一下代码:

class ReactInstanceManagerImpl extends ReactInstanceManager {...private ReactApplicationContext createReactContext(JavaScriptExecutor jsExecutor,JSBundleLoader jsBundleLoader) {...try {CoreModulesPackage coreModulesPackage =new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);} finally {...}for (ReactPackage reactPackage : mPackages) {...try {processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);} finally {...}}}private void processPackage(ReactPackage reactPackage, ReactApplicationContext reactContext, NativeModuleRegistry.Builder nativeRegistryBuilder, JavaScriptModulesConfig.Builder jsModulesBuilder) {...for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()){jsModulesBuilder.add(jsModuleClass);}}...}

能够看到CoreModulesPackage和开发人员扩展自己定义的mPackages都是通过processPackage方法里加入到JavaScriptModulesConfig里注冊的。

简单的建造者模式,我们直接看一下JavaScriptModulesConfig类,位于包com.facebook.react.bridge下。

public class JavaScriptModulesConfig {private final List<JavaScriptModuleRegistration> mModules;private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {mModules = modules;}/*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {return mModules;}...
}

JavaScriptModule明显是通过构造函数传入,然后又通过一个getter方法提供出去了,看样子JavaScriptModulesConfig仅仅起到了一个中间者的作用,并非真正的注冊类。

回看一下之前的ReactInstanceManagerImpl类代码,createReactContext中另一段。例如以下:

private ReactApplicationContext createReactContext(JavaScriptExecutor jsExecutor,JSBundleLoader jsBundleLoader)...JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();JavaScriptModulesConfig javaScriptModulesConfig;try {javaScriptModulesConfig = jsModulesBuilder.build();} finally {...}...CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder().setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()).setJSExecutor(jsExecutor).setRegistry(nativeModuleRegistry).setJSModulesConfig(javaScriptModulesConfig).setJSBundleLoader(jsBundleLoader).setNativeModuleCallExceptionHandler(exceptionHandler);...CatalystInstance catalystInstance;try {catalystInstance = catalystInstanceBuilder.build();} finally {...}...
}

看来终于javaScriptModulesConfig是用来构建CatalystInstance的,正如凝视所讲。果然没有骗我。

CatalystInstance仅仅是一个接口。实现类是CatalystInstanceImpl。相同位于包com.facebook.react.bridge下。Catalyst单词的中文意思是催化剂,化学中是用来促进化学物之间的反应,难道说CatalystInstance是用来催化NativeJavascript之间的反应?让我们来瞧一瞧真面目吧。

public class CatalystInstanceImpl implements CatalystInstance {...private CatalystInstanceImpl(final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,final JavaScriptExecutor jsExecutor,final NativeModuleRegistry registry,final JavaScriptModulesConfig jsModulesConfig,final JSBundleLoader jsBundleLoader,NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {...mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);...try {mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue(new Callable<ReactBridge>() {@Overridepublic ReactBridge call() throws Exception {...try {return initializeBridge(jsExecutor, jsModulesConfig);} finally {...}}}).get();} catch (Exception t) {throw new RuntimeException("Failed to initialize bridge", t);}}private ReactBridge initializeBridge(JavaScriptExecutor jsExecutor,JavaScriptModulesConfig jsModulesConfig) {...ReactBridge bridge;try {bridge = new ReactBridge(jsExecutor,new NativeModulesReactCallback(),mReactQueueConfiguration.getNativeModulesQueueThread());} finally {...}...try {bridge.setGlobalVariable("__fbBatchedBridgeConfig",buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));bridge.setGlobalVariable("__RCTProfileIsProfiling",Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

"true" : "false"); } finally { ... } return bridge; } ... }

CatalystInstanceImpl构造方法里,jsModulesConfig又被用来初始化JavaScriptModuleRegistry,字面意思是JavaScriptModule注冊表。看样子终于找到注冊类了。

先不着急。继续往下看CatalystInstanceImpl中还初始化了ReactBridge 。字面意思就是真正连接NativeJavascript的桥梁了。ReactBridge干了什么呢?调用了setGlobalVariable方法,參数里面的buildModulesConfigJSONProperty方法又用到了JavaScriptModulesConfig,顺便来看看。

  private String buildModulesConfigJSONProperty(NativeModuleRegistry nativeModuleRegistry,JavaScriptModulesConfig jsModulesConfig) {JsonFactory jsonFactory = new JsonFactory();StringWriter writer = new StringWriter();try {JsonGenerator jg = jsonFactory.createGenerator(writer);jg.writeStartObject();jg.writeFieldName("remoteModuleConfig");nativeModuleRegistry.writeModuleDescriptions(jg);jg.writeFieldName("localModulesConfig");jsModulesConfig.writeModuleDescriptions(jg);jg.writeEndObject();jg.close();} catch (IOException ioe) {throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);}return writer.getBuffer().toString();}

这种方法终于的目的是生成一个JSON字符串,而字符串由什么构成呢?nativeModulejsModulesnativeModule先无论,jsModulesConfig调用了writeModuleDescriptions

回头看看刚才讲的JavaScriptModulesConfig这个中间类。

public class JavaScriptModulesConfig {...void writeModuleDescriptions(JsonGenerator jg) throws IOException {jg.writeStartObject();for (JavaScriptModuleRegistration registration : mModules) {jg.writeObjectFieldStart(registration.getName());appendJSModuleToJSONObject(jg, registration);jg.writeEndObject();}jg.writeEndObject();}private void appendJSModuleToJSONObject(JsonGenerator jg,JavaScriptModuleRegistration registration) throws IOException {jg.writeObjectField("moduleID", registration.getModuleId());jg.writeObjectFieldStart("methods");for (Method method : registration.getMethods()) {jg.writeObjectFieldStart(method.getName());jg.writeObjectField("methodID", registration.getMethodId(method));jg.writeEndObject();}jg.writeEndObject();}...
}

writeModuleDescriptions这种方法干了什么事呢?遍历全部JavaScriptModulepublic方法,然后通过methodID标识作为key存入JSON生成器中,用来终于生成JSON字符串。

这里略微梳理一下。从initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions。整个过程作用是将全部JavaScriptModule的信息生成JSON字符串预先保存到Bridge中。至于为什么这么做。先挖个坑,研究到后面自然就明确了。

2.3 JavaScriptModule组件的调用

继续之前说到的NativeModuleRegistry注冊表类。位于包com.facebook.react.bridge中。

/*package*/ class JavaScriptModuleRegistry {private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;public JavaScriptModuleRegistry(CatalystInstanceImpl instance, JavaScriptModulesConfig config) {mModuleInstances = new HashMap<>();for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(moduleInterface.getClassLoader(),new Class[]{moduleInterface},new JavaScriptModuleInvocationHandler(instance, registration));mModuleInstances.put(moduleInterface, interfaceProxy);}}...
}

当每次看到这段代码的时候,都有一种惊艳的感觉。前面说过JavaScriptModule组件都是接口定义。在Java端是没有实现类的,被注冊的都是Class类。没有真正的实例,Java端又怎样来调用呢?答案是:动态代理

这里使用动态代理除了创建JavaScriptModule组件的实例化类外。另一个关键的数据,即JavaScriptModule全部的方法调用都会被invoke拦截,这样就能够统一处理全部从Java端向Javascript端的通信请求。

JavaScriptModuleInvocationHandlerJavaScriptModuleRegistry的一个内部类,动态代理的拦截类。

  private static class JavaScriptModuleInvocationHandler implements InvocationHandler {private final CatalystInstanceImpl mCatalystInstance;private final JavaScriptModuleRegistration mModuleRegistration;public JavaScriptModuleInvocationHandler(CatalystInstanceImpl catalystInstance,JavaScriptModuleRegistration moduleRegistration) {mCatalystInstance = catalystInstance;mModuleRegistration = moduleRegistration;}@Overridepublic @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String tracingName = mModuleRegistration.getTracingName(method);mCatalystInstance.callFunction(mModuleRegistration.getModuleId(),mModuleRegistration.getMethodId(method),Arguments.fromJavaArgs(args),tracingName);return null;}}

JavaScriptModule方法拦截invoke里调用了CatalystInstancecallFunction方法,主要传入了ModuleIdMethodIdArguments这三个重要參数(tracingName忽略)。

public class CatalystInstanceImpl implements CatalystInstance {...private final ReactBridge mBridge;void callFunction(final int moduleId,final int methodId,final NativeArray arguments,final String tracingName) {...mReactQueueConfiguration.getJSQueueThread().runOnQueue(new Runnable() {@Overridepublic void run() {...try {  Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId,arguments);} finally {...}}});}...}

分析这里终于豁然开朗了,原来全部Java层向Javascript层的通信请求都是走的ReactBridge.callFunction

又有了一个问题,Javascript层详细怎么知道Java层的调用信息呢?

还是之前举的餐馆吃饭的样例,顾客(Java)把菜名告诉餐馆(Bridge),餐馆再通知厨师(Javascript)。厨师自然就知道该做什么菜了。当然前提是要约定一个菜单了,菜单包括全部的菜名。

还记得之前挖的一个坑吗?就是CatalystInstanceImpl中初始化ReactBridge的时候,全部JavaScriptModule信息都被以moduleID+methodID形式生成的JSON字符串预先存入了ReactBridge中,这事实上就是一个菜单索引表了。餐馆(Bridge)知道了菜名(moduleID+methodID)就能告诉厨师(Javascript)顾客(Java)想吃什么了,当然有时还少不了不放辣这样的需求了(arguments)。

所以callFunction中有了moduleId + methodId + arguments,就能够调用到Javascript中的实现了。


3、Bridge层实现

通信模型图中要调用WebKit的实现,少不了Bridge这个桥梁。因为Java是不能直接调用WebKit,可是假设Java通过JNIJNI再调用WebKit不就OK了么?

继续前面说的ReactBridgesetGlobalVariablecallFunction方法。

public class ReactBridge extends Countable {static {SoLoader.loadLibrary(REACT_NATIVE_LIB);}public native void callFunction(int moduleId, int methodId, NativeArray arguments);public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
}

果然是JNI调用,而JNI层的入口是react/jni/OnLoad.cpp,和常规的javah规则不同,它是通过RegisterNatives方式注冊的,JNI_OnLoad里面注冊了setGlobalVariablecallFunctionnative本地方法。

废话不多说。来看看c++setGlobalVariablecallFunction的实现吧。

namespace bridge {static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {auto bridge = extractRefPtr<CountableBridge>(env, obj);bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));}static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint moduleId, jint methodId,NativeArray::jhybridobject args, jstring tracingName) {auto bridge = extractRefPtr<CountableBridge>(env, obj);auto arguments = cthis(wrap_alias(args));try {bridge->callFunction(cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),folly::to<std::string>(moduleId),folly::to<std::string>(methodId),std::move(arguments->array),fromJString(env, tracingName));} catch (...) {translatePendingCppExceptionToJavaException();}}}
struct CountableBridge : Bridge, Countable {using Bridge::Bridge;
};

OnLoad仅仅是一个调用入口。终于走的还是CountableBridge,而CountableBridge继承的是Bridge。仅仅是加了一个计数功能。实现代码在react/Bridge.cpp中。

void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {executor->setGlobalVariable(propName, jsonValue);});
}
void Bridge::callFunction(ExecutorToken executorToken,const std::string& moduleId,const std::string& methodId,const folly::dynamic& arguments,const std::string& tracingName) {#ifdef WITH_FBSYSTRACEint systraceCookie = m_systraceCookie++;...#endif#ifdef WITH_FBSYSTRACErunOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {...#elserunOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName] (JSExecutor* executor) {#endifexecutor->callFunction(moduleId, methodId, arguments);});
}

两个方法调用的过程几乎相同,都是塞进runOnExecutorQueue运行队列里面等待调用,回调都是走的JSExecutor。所以还是要看JSExecutor了。

这边提一下,Bridge类构造的时候会初始化ExecutorQueue,通过JSCExecutorFactory创建JSExecutor,而JSExecutor的真正实现类是JSCExecutor

通过jni/react/JSCExecutor.h头文件能够验证这一点,此处略过不细讲。

绕来绕去,略微有点晕。最后又跑到JSCExecutor.cpp里面了。

void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {auto globalObject = JSContextGetGlobalObject(m_context);String jsPropertyName(propName.c_str());String jsValueJSON(jsonValue.c_str());auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}

finally哈。前面Java层构造的JavaScriptModule信息JSON串,终于在这里被处理了,不用想也知道肯定是解析后存为一张映射表,然后等callFunction的时候映射调用。接下来看callFunction的处理。

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {// TODO:  Make this a first class function instead of evaling. #9317773std::vector<folly::dynamic> call{moduleId,methodId,std::move(arguments),};std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(JSGlobalContextRef ctx,const std::string& methodName,const std::vector<folly::dynamic>& arguments) {...// Evaluate script with JSCfolly::dynamic jsonArgs(arguments.begin(), arguments.end());auto js = folly::to<folly::fbstring>("__fbBatchedBridge.", methodName, ".apply(null, ",folly::toJson(jsonArgs), ")");auto result = evaluateScript(ctx, String(js.c_str()), nullptr);return Value(ctx, result).toJSONString();
}

callFunction里面运行的是executeJSCallWithJSC。而executeJSCallWithJSC里面将methodNamejsonArgs拼接成了一个applyJavascript运行语句。最后调用jni/react/JSCHelpers.cppevaluateScript的来运行这个语句,完毕BridgeJavascript的调用。(JSCHelpersWebKit的一些API做了封装,暂不深究,仅仅要知道它负责终于调用WebKit即可了)

当然JSCExecutor::callFunction方法最后另一个Bridge.cpp类的callNativeModules反向通信,意图是将Javascript语句运行结果通知回Native,这个过程留在以后的文章中慢慢研究,先行略过。

最后,总结一下Bridge层的调用过程: OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->JSCHelpers.cpp->WebKit


4、Javascript层实现

Javascript的通信,实质上是Weikit运行Javascript语句,调用流程是Bridge->WebKit->JavascriptWebKit中提供了很多与Javascript通信的API。比方evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

4.1、JavaScriptModule映射表

前面说过,全部JavaScriptModule信息是调用的setGlobalVariable方法生成一张映射表,这张映射表终于肯定是要保存在Javascript层的,回头细致分析下jni/react/JSCExecutor.cpp的代码。

void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {auto globalObject = JSContextGetGlobalObject(m_context);String jsPropertyName(propName.c_str());String jsValueJSON(jsonValue.c_str());auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}

JSContextGetGlobalObjectWeiKit的方法。其目的是获取Global全局对象。jsPropertyName方法字面意思就是Javascript对象的属性名,參数propName是从Java层传递过来的,在CatalystInstanceImpl.java类中能够印证这一点,详细值有两个:__fbBatchedBridgeConfig__RCTProfileIsProfiling

bridge.setGlobalVariable("__fbBatchedBridgeConfig",buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));bridge.setGlobalVariable("__RCTProfileIsProfiling",Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

"true" : "false");

我们所关注的是__fbBatchedBridgeConfig。这个值被传递到刚刚说的JSCExecutor::setGlobalVariable生成jsPropertyName对象,而jsonValue相同被JSValueMakeFromJSONString处理成一个jsValue对象,这样一来property-value就全都有了。最后JSObjectSetProperty方法,顾名思义,就是设置属性,使用的是Global全局对象,假设翻译成Javascript代码,大概应该是这样:

global.__fbBatchedBridgeConfig = jsonValue;

或者

Object.defineProperty(global, '__fbBatchedBridgeConfig', { value: jsonValue});

作用事实上是一样的。

既然javascript接收到了关于JavaScriptModule的信息,那就要生成一张映射表了。

我们来看node_modules\react-native\Libraries\BatchedBridge\BatchedBridge.js的代码。

const MessageQueue = require('MessageQueue');const BatchedBridge = new MessageQueue(__fbBatchedBridgeConfig.remoteModuleConfig,__fbBatchedBridgeConfig.localModulesConfig,
);

因为__fbBatchedBridgeConfig对象是被直接定义成Global全局对象的属性,就能够直接调用了,相似于window对象。__fbBatchedBridgeConfig对象里又有两个属性:remoteModuleConfiglocalModulesConfig

哪儿冒出来的呢?

有心的读者能猜到这两个属性都是定义在jsonValue里面的,为了验证这一点。我们再回头搜索下生成JSON串的地方,代码在CatalystInstanceImpl.java里面。

  private String buildModulesConfigJSONProperty(NativeModuleRegistry nativeModuleRegistry,JavaScriptModulesConfig jsModulesConfig) {JsonFactory jsonFactory = new JsonFactory();StringWriter writer = new StringWriter();try {JsonGenerator jg = jsonFactory.createGenerator(writer);jg.writeStartObject();jg.writeFieldName("remoteModuleConfig");nativeModuleRegistry.writeModuleDescriptions(jg);jg.writeFieldName("localModulesConfig");jsModulesConfig.writeModuleDescriptions(jg);jg.writeEndObject();jg.close();} catch (IOException ioe) {throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);}return writer.getBuffer().toString();}

这段代码分析过,localModulesConfig里面存的就是JavaScriptModule的信息,果然没错!

再来看刚刚的BatchedBridge.js

const MessageQueue = require('MessageQueue');const BatchedBridge = new MessageQueue(__fbBatchedBridgeConfig.remoteModuleConfig,__fbBatchedBridgeConfig.localModulesConfig,
);

JavaScriptModule的信息。又被传入MessageQueue的构造函数里面了,继续往MessageQueue里面看,代码在node_modules\react-native\Libraries\Utilities\MessageQueue.js

class MessageQueue {constructor(remoteModules, localModules) {...localModules && this._genLookupTables(this._genModulesConfig(localModules),this._moduleTable, this._methodTable);
}

localModules參数就是JavaScriptModule信息了,又被传进了_genLookupTables的方法里,同一时候还有两个參数_moduleTable_methodTable。推測一下,应该就是我们找的映射表了。一张module映射表,一张method映射表。

_genLookupTables(modulesConfig, moduleTable, methodTable) {modulesConfig.forEach((config, moduleID) => {this._genLookup(config, moduleID, moduleTable, methodTable);});}_genLookup(config, moduleID, moduleTable, methodTable) {if (!config) {return;}let moduleName, methods;if (moduleHasConstants(config)) {[moduleName, , methods] = config;} else {[moduleName, methods] = config;}moduleTable[moduleID] = moduleName;methodTable[moduleID] = Object.assign({}, methods);}

哈哈,和推測的一样,生成了两张映射表,存放在了MessageQueue类里面。

4.2、callFunction的调用

回想一下JSCExecutor.cpp中的终于callFunction调用过程。

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {// TODO:  Make this a first class function instead of evaling. #9317773std::vector<folly::dynamic> call{moduleId,methodId,std::move(arguments),};std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(JSGlobalContextRef ctx,const std::string& methodName,const std::vector<folly::dynamic>& arguments) {...// Evaluate script with JSCfolly::dynamic jsonArgs(arguments.begin(), arguments.end());auto js = folly::to<folly::fbstring>("__fbBatchedBridge.", methodName, ".apply(null, ",folly::toJson(jsonArgs), ")");auto result = evaluateScript(ctx, String(js.c_str()), nullptr);return Value(ctx, result).toJSONString();
}

executeJSCallWithJSC中有个生成语句的代码,methodName的值为callFunctionReturnFlushedQueue,所以拼装成的Javascript语句是:

__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

首先,在Javascript的运行环境下,当前作用域条件下__fbBatchedBridge能被直接调用。必须是Global全局对象的属性。

4.1__fbBatchedBridgeConfig不同的是,jni并没有手动设置__fbBatchedBridge为全局对象的属性,那唯一的可能就是在Javascript里面通过Object.defineProperty来设置了。

搜索一下。在BatchedBridge.js中找到例如以下代码:

const MessageQueue = require('MessageQueue');const BatchedBridge = new MessageQueue(__fbBatchedBridgeConfig.remoteModuleConfig,__fbBatchedBridgeConfig.localModulesConfig,
);...Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });module.exports = BatchedBridge;

这段代码等价于

global.__fbBatchedBridge = new MessageQueue(...args);

再次替换一下,callFuction调用的是:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

Arguments參数再详细一下。就变成了:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);

又回到MessageQueue.js了,前面才分析到它里面存放了两张映射表,如今第一件事当然是作匹配查找了,看MessageQueue.callFunctionReturnFlushedQueue的详细调用吧。

callFunctionReturnFlushedQueue(module, method, args) {guard(() => {this.__callFunction(module, method, args);this.__callImmediates();});return this.flushedQueue();}var guard = (fn) => {try {fn();} catch (error) {ErrorUtils.reportFatalError(error);}
};

Lambda+闭包,代码非常简洁,但阅读起来比較吃力。而React里面都是这样的。强烈吐槽一下。

定义guard目的是为了统一捕获错误异常,忽略这一步,以上代码等价于:

callFunctionReturnFlushedQueue(module, method, args) {this.__callFunction(module, method, args);this.__callImmediates();return this.flushedQueue();}

this指的是当前MessageQueue 对象。所以找到MessageQueue.__callFunction方法:

__callFunction(module, method, args) {...if (isFinite(module)) {method = this._methodTable[module][method];module = this._moduleTable[module];}...var moduleMethods = this._callableModules[module];invariant(!!moduleMethods,'Module %s is not a registered callable module.',module);moduleMethods[method].apply(moduleMethods, args);...}

这里就是通过moduleIDmethodID来查询两张映射Table了。获取到了详细的moduleNamemethodName,接着肯定要做调用Javascript相应组件了。

假设在餐馆吃饭的样例中,场景应该是这样的:顾客点完菜。餐馆服务人员也已经把菜名通知到厨师了,厨师该做菜了吧。等等。当中还漏了一步,就是这个厨师会不会做这道菜。假设让川菜师傅去做粤菜肯定是不行的,所以厨师的能力里还应该有一张技能清单,做菜前厨师须要推断下自己的技能单子里面有没有这道菜。

代码同理。MessageQueue里面有一个_callableModules数组。它就是用来存放哪些Javascript组件是能够被调用的。正常情况下_callableModules的数据和JavaScriptModules的数据(包括方法名和參数)理应是全然相应的。

我们来瞧瞧_callableModules数据初始化的过程,相同是在MessageQueue.js中:

registerCallableModule(name, methods) {this._callableModules[name] = methods;
}

全部的Javascript组件都是通过registerCallableModule来注冊的,比方触摸事件RCTEventEmitter.java相应的组件RCTEventEmitter.js,代码路径是
node_modules\react-native\Libraries\BatchedBridge\BatchedBridgedModules\RCTEventEmitter.js

var BatchedBridge = require('BatchedBridge');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');BatchedBridge.registerCallableModule('RCTEventEmitter',ReactNativeEventEmitter
);// Completely locally implemented - no native hooks.
module.exports = ReactNativeEventEmitter;

BatchedBridge能够看成是MessageQueue。被注冊的组件是ReactNativeEventEmitter。代码位于node_modules\react-native\Libraries\ReactNative\ReactNativeEventEmitter.js

receiveEvent: function(tag: number, topLevelType: string, nativeEventParam: Object) {...
},receiveTouches: function(eventTopLevelType: string,  touches:Array<Object>, changedIndices: Array<number>) {...
}

细致对比RCTEventEmitter .java比較,是不是全然一致,哈哈

public interface RCTEventEmitter extends JavaScriptModule {public void receiveEvent(int targetTag, String eventName,  WritableMap event);public void receiveTouches(String eventName, WritableArray touches, WritableArray changedIndices);}

继续__callFunction方法代码的最后一步

moduleMethods[method].apply(moduleMethods, args)

假设以RCTEventEmitterreceiveTouches方法调用为例。详细语句应该是这样:

ReactNativeEventEmitter.receiveTouches.apply(moduleMethods, eventTopLevelType, touches, changedIndices);

结束!通信完毕。大功告成!

5、总结

整个通信过程涉及到三种程序语言:JavaC++Javascript,这还仅仅是单向的通信流程。假设是逆向则更加复杂。因为篇幅的关系。留到以后的博客里面研究。

最后总结一下几个关键点:

1、Java层

JavaScriptModule接口类定义通信方法,在ReactApplicationContext创建的时候存入注冊表类JavaScriptModuleRegistry中。同一时候通过动态代理生成代理实例,并在代理拦截类JavaScriptModuleInvocationHandler中统一处理发向Javascript的全部通信请求。

CatalystInstanceImpl类内部的ReactBridge详细实现与Javascript的通信请求,它是调用Bridge Jni 的出口。

ReactBridge被创建的时候会将JavaScriptModule信息表预先发给Javascript层用来生成映射表。

2、C++层

OnLoadjni层的调用入口,注冊了全部的native方法。其内部调用又都是通过CountableBridge来完毕的,CountableBridgeBridge的无实现子类。而在Bridge里面JSCExecutor才是真正的运行者。

JSCExecutor将全部来自Java层的通信请求封装成Javascript运行语句。交给WebKit内核完毕向Javascript层的调用。

3、Javascript层

BatchedBridgeJavascript层的调用入口,而其又是MessageQueue的伪装者。MessageQueue预先注冊了全部能够接收通信请求的组件_callableModules 。同一时候也保存着来自JavaJavaScriptModule的两张映射表。

接收通信请求时,先通过映射表确认详细请求信息,再确认Javascript组件能否够被调用,最后通过apply方式完毕运行。

整个通信过程流程例如以下图:


本博客不定期持续更新,欢迎关注和交流:

http://blog.csdn.net/megatronkings

转载于:https://www.cnblogs.com/llguanli/p/8442598.html

React-Native系列Android——Native与Javascript通信原理(一)相关推荐

  1. android native.js,Android Native与JS通信互调

    写在最前: 看Android最新技术总结,关注公众号: 最近因为App与H5交互逻辑太乱,所以抽空梳理了下:对目前App与H5的各种交互通信做个总结,自取适合自己的交互方式. 一.H5调用原生的nat ...

  2. 再谈Android Binder跨进程通信原理

    在谈Android的跨进程通信问题上时,总会问到Android的IPC机制,是指两个进程之间进行数据交换的过程.按操作系统的中的描述,线程是CPU调度最小的单元,同时线程是一种有限的系统资源,而进程是 ...

  3. android之IM即时通信原理

    即时通讯(Instant Messenger,简称IM)软件多是基于TCP/IP和UDP进行通讯的,TCP/IP和UDP都是建立在更低层的IP协议上的两种通讯传输协议.前 者是以数据流的形式,将传输数 ...

  4. ReactNative与iOS通信原理解析-通信篇

    文章首发个人博客: ReactNative与iOS通信原理解析-通信篇 导语:其实原本是想编写一篇  react-native (下文简称 rn) 在  iOS 中如何实现  jsbridge 的文章 ...

  5. nacis服务注册原理_HwServiceManager篇Android10.0 HwBinder通信原理(五)

    阅读本文大约需要花费34分钟. 原创不易,如果您觉得有点用,希望可以随手转发或者点击右下角的 "在看".""分享"",拜谢! <And ...

  6. 《深入浅出通信原理》学习笔记(目录)

    本系列文章整理<深入浅出通信原理>的各个帖子,并附上我个人的一些学习心得.鉴于原帖比较零碎,我将按照各知识点的连贯性,将几个帖子(5个左右)放在一篇文章中介绍. 原帖链接: 1,总链接   ...

  7. 【REACT NATIVE 系列教程之十二】REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信

    一用到跨平台的引擎必然要有引擎与各平台原生进行交互通信的需要.那么Himi先讲解React Native与iOS之间的通信交互. 本篇主要分为两部分讲解:(关于其中讲解的OC语法等不介绍,不懂的请自行 ...

  8. 【React Native】深入理解Native与RN通信原理

    在使用 React Native 开发应用程序的时候,有时候需要使用 JavaScript 中默认不可用的 IOS 或 Android 的原生 API. 也许你想复用一些现有的 OC.Swift.Ja ...

  9. 【REACT NATIVE 系列教程之十三】利用LISTVIEW与TEXTINPUT制作聊天/对话框获取组件实例常用的两种方式...

    本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2346.html ...

最新文章

  1. NGUI-制作位图字体以及图文混排
  2. 2018年下半年软件设计师考试上午真题(参考答案)
  3. DL之NIN:Network in Network算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  4. 沟通管理计划3个过程及重点
  5. Java_IO流(精讲)包含练习题及答案
  6. 怎样和客户一起搞定需求
  7. mysql更改root用户密码
  8. 程序员除了会 CRUD 之外,还应该知道什么叫 CQRS!
  9. Bzoj 3932: [CQOI2015]任务查询系统(主席树)
  10. 如何查看有没有django及版本
  11. dcs world f15c教学_视频教学知识归纳 | 广东中考必备英语:中考语法知识 冠词
  12. 基于SpringBoot和Vue的OA办公管理系统
  13. 支付宝支付返回resultStatus:4000(系统繁忙,请稍后再试)
  14. qt删除键值_Qt QMap键值对基本用法(键值对)
  15. Go语言判断接口的具体类型并进行类型转换
  16. Matlab作图后的各种调整方法——线条、坐标、标题、图例
  17. Qt/C++ 压缩/解压缩库—QuaZip
  18. iOS开发——使用Charles进行https网络抓包详解
  19. [网络安全自学篇] 七十三.WannaCry勒索病毒复现及分析(四)蠕虫传播机制全网源码详细解读
  20. [BZOJ2246][SDOI2011]迷宫探险(状压概率DP)

热门文章

  1. Scala 闭包详解
  2. 【收藏】webpack configuration.module has an unknown property ‘loaders‘. These properties arevalid: 解决办法
  3. Linux rpm软件包管理工具常用命令示例
  4. Java float浮点数精度丢失问题
  5. Scala概述及学习Scala的优势
  6. JVM 的内存模型及jstat命令的使用
  7. SQL语句--INSERT INTO SELECT 语句用法示例
  8. redis命令操作(1)
  9. 浅析Java线程的三种实现
  10. messenger android 4.,AndroidIPC机制(4)-Messenger