近段时间来Android上最火的框架非react native莫属了,这里我不去评价这个框架的好坏,毕竟只有用过的人才会有深刻的体会。但是我个人有一个习惯,在使用一个开源库之前,一定要看过它的源码,不说百分百搞懂吧,至少得弄清楚它的工作原理,所以在使用RN之前我就看了看它的源码。不看不知道,一看吓一跳,它其中最核心的那一部分——java和js的通信写的确实是非常的精妙,把整个流程搞懂以后让我受益无穷。

这里插一句题外话,阿里的weex也马上就要开源了,我身边的小伙伴也有已经拿到源码投身于其中的,等到它开源的时候我也会去看看它的源码,了解下它和RN的区别到底是什么。

废话也不说了,让我们好好看看RN的通信机制是怎样的吧。

前言

在看这篇文章之前,你得确保你有一定的RN开发基础,如果是零基础的同学,建议大家先去看看这个系列的文章,里面非常清楚的介绍了如何使用RN来进行开发。当然如果你不满足仅仅是[了解]的话,也可以去网上查阅关于RN的一些资料,其实里面一些东西是非常值得一看的,比如virtual DOM,diff机制等等。

通信方式

我们所说的[通信],指的是RN中Java和js的通信,也就是js部分中的那些jsx代码是如何转化成一个java层真实的view和事件的,java层又是如何调用js来找出它所需要的那些view和事件的。

简单的说,RN的两端通信靠的是一张配置表,java端和js端持有同一张表,通信的时候就是靠这张表的各个条目的对应来进行的。

大致的就是和上面这张图一样,两端各持有一份相同的config,config中有一些已经注册的模块,两端的通信就是通过传输这样的“A”,“B”或者”C”来实现的。这个config对应到RN的代码是NativeModuleRegistry和JavaScriptModuleRegistry。如果大家想象不出来的话我可以给大家打个比喻,java端和js端的通信就好比一个中国人和一个美国人在对话,而这个config,也就是注册表就相当于两个翻译,有了翻译两个语言不通的人才能正常交流。那这两张表是如何生成的呢?还是让我们从代码中寻找答案吧。

首先我们知道在使用RN的时候,我们对应的activity要继承自ReactActivity并且重写一个叫做getPackages的方法。

1
2
3
4
5
6
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}

这个MainReactPackage是RN帮我们生成好的,其中定义了一些基础的组件和事件,具体就不说了,大家可以自己去看一下源码。如果你想要自定义一些组件或者事件的话必须要自己去写一个package,至于怎么写大家看我前面提到的那一系列文章就知道了。而我们前面提到的那两个注册表——NativeModuleRegistry和JavaScriptModuleRegistry就是通过这样的package去生成的,具体方法我们看下去就知道了。

既然我们的activity继承自了ReactActivity,那我们就去看看ReactActivity里面做了什么。第一个要看的当然是onCreate函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
// Get permission to show redbox in dev builds.
if (!Settings.canDrawOverlays(this)) {
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(serviceIntent);
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
}
} mReactInstanceManager = createReactInstanceManager();
ReactRootView mReactRootView = createRootView();
mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
setContentView(mReactRootView);
}

可以看到我们创建了一个ReactInstanceManager,看看是怎么创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setInitialLifecycleState(mLifecycleState); for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
} String jsBundleFile = getJSBundleFile(); if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(getBundleAssetName());
} return builder.build();
}

中间有一段这样的代码

1
2
3
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}

通过builder模式把我们的package注入到了builder中并且最后调用build方法创建出一个ReactInstanceManagerImpl实例。

我们回过头来看onCreate函数,在这之后我们创建一个ReactRootView作为我们的根视图,并且调用它的startReactApplication函数,从函数名字就可以看出来,这个函数的作用非常重要,从这儿开始,算是启动了我们的RN程序。

在startReactApplication函数中我们调用了ReactInstanceManager的createReactContextInBackground()方法去构造属于RN程序的上下文。在这个方法中会去判断是否是开发模式,大家可以在自己的activity中重写getUseDeveloperSupport()去更改模式。模式不同的主要区别在于获取JSBundle的方式不同。如果你是开发模式的,就会从server端获取,如果不是,则是从文件中获取。这里我们只关注前者。最终会走到onJSBundleLoadedFromServer方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void onJSBundleLoadedFromServer() {
recreateReactContextInBackground(
new JSCJavaScriptExecutor.Factory(),
JSBundleLoader.createCachedBundleFromNetworkLoader(
mDevSupportManager.getSourceUrl(),
mDevSupportManager.getDownloadedJSBundleFile()));
} private void recreateReactContextInBackground(
JavaScriptExecutor.Factory jsExecutorFactory,
JSBundleLoader jsBundleLoader) {
.......... mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
mReactContextInitAsyncTask.execute(initParams);
}

在该方法中我们调用JSBundleLoader的createCachedBundleFromNetworkLoader方法去创建了一个JSBundleLoader。它的主要作用是去加载JSBundle。大家可以去看看JSBundleLoader这个类,其中还有两种创建loader的方式,如果我们不是开发模式,调用的是createFileLoader,也就是说release的情况下我们需要用gradle生成了JSBundle之后将其放在assets目录上或者文件中。

下面让我们看看之后的recreateReactContextInBackground方法。

它会调用了一个叫做mReactContextInitAsyncTask的AsyncTask去执行异步任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
try {
JavaScriptExecutor jsExecutor =
params[0].getJsExecutorFactory().create(
mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap());
return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
} catch (Exception e) {
// Pass exception to onPostExecute() so it can be handled on the main thread
return Result.of(e);
}
}

我们可以看到它的doInBackground方法调用了createReactContext()方法去创建上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
FLog.i(ReactConstants.TAG, "Creating react context.");
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
mSourceUrl = jsBundleLoader.getSourceUrl();
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder(); ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
if (mUseDeveloperSupport) {
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
} ReactMarker.logMarker(PROCESS_PACKAGES_START);
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCoreModulesPackage");
try {
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
} // TODO(6818138): Solve use-case of native/js modules overriding
for (ReactPackage reactPackage : mPackages) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCustomReactPackage");
try {
processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
ReactMarker.logMarker(PROCESS_PACKAGES_END); ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
NativeModuleRegistry nativeModuleRegistry;
try {
nativeModuleRegistry = nativeRegistryBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
} ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_START);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig");
JavaScriptModulesConfig javaScriptModulesConfig;
try {
javaScriptModulesConfig = jsModulesBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_END);
} NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModulesConfig(javaScriptModulesConfig)
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
// CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
CatalystInstance catalystInstance;
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
} if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
} reactContext.initializeWithInstance(catalystInstance); ReactMarker.logMarker(RUN_JS_BUNDLE_START);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
try {
catalystInstance.runJSBundle();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(RUN_JS_BUNDLE_END);
} return reactContext;
}

这个方法的代码就比较多了,但是我们现在只看我们所关注的。大家应该还记得我们的关注点吧?[两个注册表NativeModuleRegistry和JavaScriptModuleRegistry是如何生成的]。这里给出了答案。

1
2
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();

首先创建出两个builder。

1
2
3
4
5
6
7
try {
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}

然后会去new一个CoreModulesPackage并且使用了processPackage方法去处理它,这个CoreModulesPackage里面定义了RN框架核心的一些Java和JS的module。

下面让我们看看processPackage方法。

1
2
3
4
5
6
7
8
9
10
11
12
private void processPackage(
ReactPackage reactPackage,
ReactApplicationContext reactContext,
NativeModuleRegistry.Builder nativeRegistryBuilder,
JavaScriptModulesConfig.Builder jsModulesBuilder) {
for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
nativeRegistryBuilder.add(nativeModule);
}
for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
jsModulesBuilder.add(jsModuleClass);
}
}

很简单,拿到具体的native和JS的module把它们添加到对应的builder中。

再处理完了CoreModulesPackage之后,程序又会去处理我们在activity中注入的那些package。

1
2
3
4
5
6
7
8
9
10
for (ReactPackage reactPackage : mPackages) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCustomReactPackage");
try {
processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}

接下去就是生成注册表了。

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
nativeModuleRegistry = nativeRegistryBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
} try {
javaScriptModulesConfig = jsModulesBuilder.build();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_END);
}

至此,我们就把所有的packages,包括RN核心的CoreModulesPackage和我们activity自己注入的package里面的各个modules全部写到了对应Registry的builder中。

现在这两份注册表是存在于java端的,那要怎么传输到JS端呢?我们继续看下去。

再创建完了对应的注册表之后,ReactInstanceManagerImpl会通过builder模式去创建一个CatalystInstance的实例CatalystInstanceImpl。在CatalystInstanceImpl的构造函数中会去创建一个ReactBridge。并且会调用initializeBridge(jsExecutor, jsModulesConfig)这个方法。在这个方法中会去通过ReactBridge向C层传递一些数据,其中有这么一段:

1
2
3
bridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));

调用了bridge的setGlobalVariable方法,这是一个native的方法。

1
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);

这个方法的作用就是先把JavaRegistry格式化成json,并且调用C层的代码传输到js端。由于本人C层学艺不精,能看懂个大概,怕会有细节讲错,就不带大家进行分析了。

总结

到这里,真相就水落石出了,我们来总结一下吧。

(1) 在程序启动的时候,也就是ReactActivity的onCreate函数中,我们会去创建一个ReactInstanceManagerImpl对象

(2) 通过ReactRootView的startReactApplication方法开启整个RN世界的大门

(3) 在这个方法中,我们会通过一个AsyncTask去创建ReactContext

(4) 在创建ReactContext过程中,我们把我们自己注入(MainReactPackage)的和系统生成(CoreModulesPackage)的package通过processPackage方法将其中的各个modules注入到了对应的Registry中

(5) 最后通过CatalystInstanceImpl中的ReactBridge将java的注册表通过jni传输到了JS层。

这样,js层就获取到了java层的所有接口和方法,相当于一个美国人身边有了以为中文翻译。而js层的注册表本来就是由java层生成的,所以就相当于一个中国人身边有了一个英文翻译,从此他们就可以愉快的交流了。

涉及到的重要的类:

ReactInstanceManagerImpl,ReactContext,CatalystInstanceImpl,ReactBridge。

Java->js

前面我们讲了两端通信的方式和注册表是如何从Java端发送到js端的,下面让我们讲讲这样的准备工作完成以后,java是如何调用js的方法的。

首先,经过上面的学习,其实这个问题已经有了一个初步的答案了,因为[将JavaRegistry从java端传输到js端]这个过程就是一个java向js通信的过程,具体的过程在上一节的总结中已经说了,让我们回想一下,之前所有的事情都是在ReactInstanceManagerImpl中ReactContextInitAsyncTask的doInBackground方法中完成的,那这个方法完成之后又做了什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void onPostExecute(Result<ReactApplicationContext> result) {
try {
setupReactContext(result.get());
} catch (Exception e) {
mDevSupportManager.handleException(e);
} finally {
mReactContextInitAsyncTask = null;
} // Handle enqueued request to re-initialize react context.
if (mPendingReactContextInitParams != null) {
recreateReactContextInBackground(
mPendingReactContextInitParams.getJsExecutorFactory(),
mPendingReactContextInitParams.getJsBundleLoader());
mPendingReactContextInitParams = null;
}
}

可以看到,在onPostExecute方法中调用了setupReactContext方法,在这个方法中会去调用attachMeasuredRootViewToInstance方法。

1
2
3
4
5
6
7
8
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
.......
....... catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}

这个方法的最后用了我们的CatalystInstanceImpl的getJSModule方法,它会去调用JavaScriptModuleRegistry的getJSModule方法,获取对应的JavaScriptModule,也就是从注册表中获取对应的模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public synchronized  <T extends JavaScriptModule> T getJavaScriptModule(ExecutorToken executorToken, Class<T> moduleInterface) {
HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
mModuleInstances.get(executorToken);
if (instancesForContext == null) {
instancesForContext = new HashMap<>();
mModuleInstances.put(executorToken, instancesForContext);
} JavaScriptModule module = instancesForContext.get(moduleInterface);
if (module != null) {
return (T) module;
} JavaScriptModuleRegistration registration =
Assertions.assertNotNull(
mModuleRegistrations.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(executorToken, mCatalystInstance, registration));
instancesForContext.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
}

这个方法就比较神奇了,首先去缓存中找,如果找到就返回,没找到就去创建,怎么创建的呢,用的是动态代理!

1
2
3
4
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(executorToken, mCatalystInstance, registration));

这里大家必须要对动态代理有所了解,可以自己去找相关的知识。让我们看看JavaScriptModuleInvocationHandler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ExecutorToken executorToken = mExecutorToken.get();
if (executorToken == null) {
FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
return null;
}
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
executorToken,
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
return null;
}

我们看它最核心的invoke方法。里面获取了调用方法的moduleId,methodId和参数,然后调用了CatalystInstanceImpl的callFunction去执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void callFunction(
ExecutorToken executorToken,
int moduleId,
int methodId,
NativeArray arguments,
String tracingName) {
synchronized (mJavaToJSCallsTeardownLock) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
return;
} incrementPendingJSCalls(); Assertions.assertNotNull(mBridge).callFunction(executorToken, moduleId, methodId, arguments, tracingName);
}
}

直接调用了ReactBridge的同名函数。

1
public native void callFunction(ExecutorToken executorToken, int moduleId, int methodId, NativeArray arguments, String tracingName);

可以看到又是一个native函数,具体作用就是将想用调用的方法对应的moduleId,methodId和arguments通过jni传递到js端进行调用。而我们这边调用的是AppRegistry的runApplication方法,这个方法在js端的作用就是开始运行整个js程序,从而将我们的RN程序真正的跑起来。

总结

首先,对于我们java端要调用的js端的类和方法,我们都必须要注册到js的注册表中,这个过程在上一部分的分析中已经带大家走过了。当真正要调用的时候,步骤是这样的:

(1) 调用CatalystInstanceImpl这个类的getJSModule方法去得到对应的JSModule

(2) CatalysInstanceImpl实际上是调用JavaScriptModuleRegistry的getJSModule方法去获取注册在其中的module

(3) 然后通过动态代理拿到方法的各种参数,包括moduleId,methodId和params

(4) 通过ReactBridge调用jni传递到C层

(5) 通过C层再传递到js层。

涉及到的重要的类:

CatalystInstanceImpl,JavaScriptModuleRegistry,JavaScriptModuleInvocationHandler,ReactBridge。

通过这个图配合上代码应该能比较好的理解了。

js->Java

RN的js调java的流程可以说是让我觉得最新颖的地方,具体就是js不是直接通过注册接口去调用java方法的,而是将对应的的参数(moduleId和methodId)push到一个messageQueue中,等待java层的事件来驱动它,当java层的事件传递过来以后,js层把messageQueue中所有的数据返回给java层,再通过注册表JavaRegistry去调用方法。

首先,我们说了js层是把对应的参数push到messageQueue中,具体的方法是MessageQueue.js的__nativeCall方法。

1
2
3
4
5
6
7
8
9
10
__nativeCall(module, method, params, onFail, onSucc) {

.........

this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params); ...........
}

可以看到它把对应的module,method和params push到了队列里面,然后就是等待java层的事件驱动。

java层的事件驱动其实也可以看成java层向js层的通信,最终会走到MessageQueue.js的callFunctionReturnFlushedQueue方法和invokeCallbackAndReturnFlushedQueue方法。

1
2
3
4
5
6
7
8
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
}); return this.flushedQueue();
}

调用了flushedQueue将MessageQueue中的所有数据通过C层发往java层。

到了java层以后,会回调到NativeModulesReactCallback类执行call方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private class NativeModulesReactCallback implements ReactCallback {

  @Override
public void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) {
mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); synchronized (mJSToJavaCallsTeardownLock) {
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
}
}
.....
}

可以看到里面通过JavaRegistry调用了它的call方法。

1
2
3
4
5
6
7
8
9
10
11
12
/* package */ void call(
CatalystInstance catalystInstance,
ExecutorToken executorToken,
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, executorToken, methodId, parameters);
}

拿到对应的module,调用call方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void call(
CatalystInstance catalystInstance,
ExecutorToken executorToken,
int methodId,
ReadableNativeArray parameters) {
MethodRegistration method = this.methods.get(methodId);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, method.tracingName);
try {
this.methods.get(methodId).method.invoke(catalystInstance, executorToken, parameters);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}

其中根据methodId拿到对应module的方法,执行invoke。

最终执行的是BaseJavaModule的invoke方法。

1
2
3
4
5
6
7
@Override
public void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters) { .........
mMethod.invoke(BaseJavaModule.this, mArguments);
.........
}

通过反射调用最终的方法。

总结

这里我们重点要了解的就是js向java的通信靠的是事件驱动模式。

(1) JS将方法的对应参数push到MessageQueue中,等java端事件传递

(2) Java端事件触发以后,JS层将MessageQueue中的数据通过C层传递给java层

(3) C层调用一开始注册在其中的NativeModulesReactCallback

(4 然后通过JavaRegistry这个Java注册表拿到对应的module和method

(5) 通过反射执行方法。

涉及到的重要的类:

MessageQueue(JS层),NativeModulesReactCallback,JavaRegistry。

老规矩,通过图配合代码来理解流程。

重要类的作用

最后我们通过总结一下前面提到的几个重要的类的作用来加深印象。

ReactInstanceManager:它的作用是创建出ReactContext,CatalystInstance等类,解析package生成注册表,并且配合ReactRootView管理View的创建,生命周期等功能。

ReactContext:继承自ContextWrapper,是RN程序自己的上下文,我们可以通过getContext()去获得,里面有CatalystInstance实例,可以去获得Java和JS的module。

ReactRootView:RN程序的根视图,startReactApplication方法开启RN世界的大门。

CatalystInstance:Java端通信的管理类,提供通信的环境,方法和回调方法,内部通过ReactBridge进行通信。

ReactBridge:通信的核心类,通过jni的方式进行通信。

NativeModuleRegistry:Java接口的注册表。

JavascriptModuleRegistry:JS接口的注册表。

CoreModulePackage:RN核心框架的package,包括Java接口和js接口,前文提到的AppResgitry就是在这里面注册的。

MainReactPackage:RN帮我们封装的一些通用的Java组件和事件。

JsBundleLoader:用于加载JSBundle的类,其中会根据应用的情况创建不同的loader。

JSBundle:存有js核心逻辑,在release环境下要通过gradle任务去创建并且放在对应的目录下。

勘误

不好意思各位!!这里由于本人粗心的原因,代码没看全,有一个地方出错了,这里写一个勘误,如果造成了大家的困扰我再次说一声不好意思!

前面我说过,JS调Java的机制是[JS把对应的moduleId,methodId和params push到queue中,等待native调用js,然后把MessageQueue中的数据发送到C层再通过jni转到java层]。但是今天公司里的大神在和我们讨论RN的时候纠正了我的这个错误。

我们一起看一下MessageQueue.js的__nativeCall方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__nativeCall(module, method, params, onFail, 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._callID];
this._lastFlush = now;
}
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
if (__DEV__ && SPY_MODE && isFinite(module)) {
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
}
}

之前的文章只分析了前面的三个queue.push,但是大家可以看一下后面的一段:

1
2
3
4
5
6
7
var now = new Date().getTime();
if (global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
global.nativeFlushQueueImmediate(this._queue);
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
}

它会去判断两次调用的时差,如果大于MIN_TIME_BETWEEN_FLUSHES_MS(5ms)的话,会调用global.nativeFlushQueueImmediate(this._queue);这个方法去主动把数据传递到C层的。也就是说,如果你两次的通信时间很短,小于5ms了,那就和我之前讲的一样,但是如果大于5ms,RN在JS端会主动传递数据的,这里一定要注意了!!

官方文档:

    http://wiki.jikexueyuan.com/project/react-native/

其实没那么复杂!探究react-native通信机制的更多相关文章

  1. React Native通信机制详解

    React Native是facebook刚开源的框架,可以用javascript直接开发原生APP,先不说这个框架后续是否能得到大众认可,单从源码来说,这个框架源码里有非常多的设计思想和实现方式值得 ...

  2. react native 刷新机制----通知

    在项目中,不知道大家有没有遇到这样的一个问题,比如说有两个页面A,B.A页面中有某个按钮点击后可以跳转到B页面,现在有一个需求就是,我在B页面中做了某些操作,然后点击回退按钮,回到A页面,A页面中的数 ...

  3. [React Native]Promise机制

    React Native中经常会看到Promise机制. Promise机制代表着在JavaScript程序中下一个伟大的范式.可以把一些复杂的代码轻松撸成一个串,和Android中的rxjava非常 ...

  4. 总结react native 事件机制

    React 事件机制 一个组件的所有事件会使用统一的事件监听器,绑定到组件的最外层,那么如何使用? bind方法,绑定并且可以传递参数 <TouchableOpacity onPress={th ...

  5. React Native初探

    前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...

  6. iOS、swift、React Native学习常用的社区、论坛

    <!----iOS> <!----Swift>*IOS开发常用社区:http://code4app.com/ *IOS开发常用社区:http://www.cocoachina. ...

  7. react native 学习资料整理

    入门教程 深入浅出 React Native:使用 JavaScript 构建原生应用 http://www.appcoda.com/react-native-introduction/  中文版 h ...

  8. React Native 从入门到原理

    React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却寥寥无几. 本文分为两个部分:上半部分用通 ...

  9. iOS 写给iOS开发者的React Native学习路线(转)

    我是一名iOS开发者,断断续续一年前开始接触React Native,最近由于工作需要,专职学习React Native也有一个多月了.网络上知识资源非常的多,但能让人豁然开朗.迅速学习的还是少数,我 ...

  10. 关于React Native 火热的话题,从入门到原理

    本文授权转载,作者:bestswifter(简书) React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原 ...

随机推荐

  1. Intel Edison的那些事:修改Edison的HTTP服务的页面

    Intel Edison配置好之后,按住PWR键2-7秒(4秒恰到好处),就可以进入AP热点模式(此时,Arduino扩展板上的灯不停闪烁),可以将笔记本接入Edison的热点,然后在浏览器中访问“h ...

  2. 《WPF程序设计指南》读书笔记——第2章 基本画刷

    1.Color结构 using System; using System.Windows; using System.Windows.Input; using System.Windows.Media ...

  3. 1011. World Cup Betting (20)(最大值)

    With the 2010 FIFA World Cup running, football fans the world over were becoming increasingly excite ...

  4. NEV_SDK开发环境部署手册

    根据项目开发需求,要在MEC服务器上部署如下内容:Nginx.Nginx push stream module.Jason CPP.Spawn-fcgi.libfcgi.Redis.Hiredis.B ...

  5. ASP.NET 页面传值得9种方式

    1. Get(即使用QueryString显式传递)     方式:在url后面跟参数.     特点:简单.方便.     缺点:字符串长度最长为255个字符:数据泄漏在url中.     适用数据 ...

  6. owa Your request can't be completed right now. Please try again later.

    Your request can't be completed right now. Please try again later.

  7. 写给 iOS 开发者的 Hopper + lldb 简介

    最近,关于 @Steipete 在Radar发布的帖子,笔者看到很多人在问「你是怎么理解那个伪代码的」.笔者想写博客已经有一段时间了,现在正好就此发表第一篇博文.笔者在一个叫 Hopper 的工具上花 ...

  8. 使用 Spark 进行微服务的实时性能分析

    [编者按]当开发者从微服务架构获得敏捷时,观测整个系统的运行情况成为最大的痛点.在本文,IBM Research 展示了如何用 Spark 对微服务性能进行分析和统计,由 OneAPM 工程师编译整理 ...

  9. C++开源跨平台类库集

    在如下的库支持下,开发的系统可以很方便移植到当前大部分平台上运行而无需改动,只需在对应的平台下 用你喜欢的编译器 重新编译即可 经典的C++库   STLport-------SGI STL库的跨平台 ...

  10. CentOS SSH安装与配置

    SSH 为 Secure Shell 的缩写,由 IETF 的网络工作小组(Network Working Group)所制定:SSH 为建立在应用层和传输层基础上的安全协议. 传 统的网络服务程序, ...