可能某個(gè)大神說過源碼自有黃金屋,源碼自有顏如玉。對(duì)于react-native這樣優(yōu)秀的框架,不看看他的源碼實(shí)在是有點(diǎn)可惜,本文通過對(duì)react-native 與 java 通信部分的源碼閱讀探究react-native android的通訊機(jī)制及實(shí)現(xiàn)。
<h1>總體模型</h1>
對(duì)于傳統(tǒng) Java Js 通信而言,Js 調(diào)用 Java 通不外乎 Jsbridge、onprompt、log 及 addjavascriptinterface 四種方式。但在 RN 中沒有采用了傳統(tǒng) Java 與 Js 之間的通信機(jī)制。而是自己弄一套最新的WebKit作為React-Native的解釋器,這樣做有兩個(gè)重要好處就是兼容絕大多少設(shè)備版本和方便添加自定義功能。其總體模型如上圖
<h1>java層</h1>
先上一張圖
這是從源碼中提取的java層實(shí)現(xiàn)中關(guān)鍵的類,我們一個(gè)一個(gè)的來看
<h5>JavaScriptModule</h5>
在reactAndroid下的com.facebook.react.bridge包中我們可以看到JavaScriptModule的代碼
/**
* 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. Calling methods
* on the object returned from {@link ReactContext#getJSModule}
* or
* {@link CatalystInstance#getJSModule} will result in the methods
* with those names exported by
* that module being called in JS.
*
* NB: JavaScriptModule does not allow method name overloading
* because JS does not allow method name
* overloading.
*/
@DoNotStrip
public interface JavaScriptModule {
}
</br>
作為RN各組件的接口其描述在源碼中寫得有:
1.這個(gè)接口在js層中具有同名模塊,調(diào)用這個(gè)接口會(huì)調(diào)用js層相應(yīng)的模塊
2.所有組件必須繼承自該JavaScriptModule接口,并且注冊(cè)到CatalystInstance中
3.所有public方法都應(yīng)當(dāng)在js層同名組件中被實(shí)現(xiàn)
4.其方法不允許重載,因?yàn)閖s不允許方法重載
好了,現(xiàn)在我們知道了JavaScriptModule是在js層有同名組件的接口,沒有具體實(shí)現(xiàn)類。接下來就需要看一下源碼中所說的注冊(cè)到CatalystInstance中這一過程是怎么回事了。
<h5>JavaScriptModule 和 NativeModule的注冊(cè)過程</h5>
facebook官方定義了許多組件都組裝在<b>CoreMoudulesPackage</b>(圖中上方中間)中,這里用戶可以自定義組件被組裝在mPackages中。在源碼中尋找它們被加載到何處,就能揭開注冊(cè)的過程了吧.
</br>
我們知道RNA的啟動(dòng)是這樣的:ReactActivity的委托類ReactActivityDelegate創(chuàng)建創(chuàng)建ReactRootView作為應(yīng)用的容器->調(diào)用ReactRootView.startReactApplication()進(jìn)一步執(zhí)行應(yīng)用啟動(dòng)流程
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties) {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
try {
UiThreadUtil.assertOnUiThread();
...
Assertions.assertCondition(
mReactInstanceManager == null,
"This root view has already been attached to a catalyst instance manager");
mReactInstanceManager = reactInstanceManager;
mJSModuleName = moduleName;
mAppProperties = initialProperties;
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
</br>
這里創(chuàng)建了一個(gè)<b>ReactInstanceManager</b>,這是一個(gè)非常重要的實(shí)例,從名字上看,它管理著整個(gè)react實(shí)例。這里會(huì)調(diào)用createReactContextInBackground()來創(chuàng)建RN應(yīng)用的上下文。這個(gè)方法最終開啟異步任務(wù)ReactInstanceManager.createReactContext(),真正開始創(chuàng)建ReactContext。這個(gè)方法之后還會(huì)提到這里只截取我們感興趣的部分
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
...
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
...
try {
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
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);
}
}
...
</br>
好了這下看到<b>CoreMoudulesPackage</b>的去向了,說明方向?qū)α恕_@個(gè)這些截取部分具體做了這些事情:
1.創(chuàng)建<b> NativeModuleRegistry</b>和<b> JavaScriptModuleRegistry</b>的builder(建造者模式)
2.使用processPackage方法處理<b>CoreModulesPackage</b>
那么processPackage方法是怎么樣的呢
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);
}
}
</br>
這下明白了,循環(huán)處理我們?cè)贏pplication里注入的<b>CoreMoudulesPackage</b>和其他package,處理的過程就是把各自的Module添加到對(duì)應(yīng)的builder中。然后在ReactInstanceManager.createReactContext()中
try {
nativeModuleRegistry = nativeModuleRegistryBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
</br>
生成Java Module注冊(cè)表<b> NativeModuleRegistry </b>
誒,那么JS Module的注冊(cè)表呢,還有源碼里說的所有javaScriptModule注冊(cè)到CatalystInstance中是怎么回事,我們接著往下看
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModuleRegistry(jsModulesBuilder.build())
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
// CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
final CatalystInstance catalystInstance;
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
}
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JSC_CALLS)) {
catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
}
reactContext.initializeWithInstance(catalystInstance);
catalystInstance.runJSBundle();
</br>
在這段代碼中createReactContext()方法通過builder模式去創(chuàng)建一個(gè)<b>CatalystInstance</b>,并且注意.setJSModuleRegistry(jsModulesBuilder.build()),這里生成了JS Module的注冊(cè)表<b> JavaScriptModuleRegistry </b>。果然像源碼注釋說的一樣所有javaScriptModule注冊(cè)到CatalystInstance中。
至此,我們生成好了 NativeModuleRegistry 及 JavaScriptModuleRegistry 兩份模塊配置表,可是還有一個(gè)問題,這兩份注冊(cè)表是存在于java端的,那要怎么傳輸?shù)絁S端呢?
答案是將所有JavaScriptModule的信息生成JSON字符串(moduleID+methodID+ arguments)預(yù)先保存到<b>ReactBridge</b>中,為什么這么做呢,這與接下來的調(diào)用過程有關(guān)。至此java層的組件的注冊(cè)完成。
</br>
<h5>JavaScriptModule 的調(diào)用過程</h5>
前面在組件的注冊(cè)過程中描述了關(guān)鍵的類和方法,再回憶一下開頭的圖
調(diào)用是怎么來完成的呢?
在注冊(cè)過程的ReactInstanceManager.createReactContext()時(shí)有這么一段代碼
if (mCurrentReactContext != null) {
mCurrentReactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
...
}
字面意思很好理解,在當(dāng)前react上下文中獲取JSModule,com.facebook.react.bridge 包中有ReactContext類,來看看getJSModule這個(gè)方法
public class ReactContext extends ContextWrapper {
...
/**
* @return handle to the specified JS module for the CatalystInstance associated with this Context
*/
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
if (mCatalystInstance == null) {
throw new RuntimeException(EARLY_JS_ACCESS_EXCEPTION_MESSAGE);
}
return mCatalystInstance.getJSModule(jsInterface);
}
...
}
原來調(diào)用了<b>CatalystInstance</b>實(shí)例的getJSModule()方法,那就來看看這個(gè)方法
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
}
又調(diào)用了<b> JavaScriptModuleRegistry </b>的getJavaScriptModule()方法,
public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
CatalystInstance instance,
Class<T> moduleInterface) {
JavaScriptModule module = mModuleInstances.get(moduleInterface);
if (module != null) {
return (T) module;
}
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(instance, moduleInterface));
mModuleInstances.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
}
先看上半部分代碼,顯然,實(shí)例中緩存得有得就返回相應(yīng)的JSModule,但是沒有呢?
沒有肯定要去創(chuàng)建,可我們之前描述道javaScriptModule組件都是接口定義,在Java端是沒有實(shí)現(xiàn)類的,被注冊(cè)的都是Class類,沒有真正的實(shí)例,Java端又如何來調(diào)用呢,代碼中給出了答案——?jiǎng)討B(tài)代理,既然是動(dòng)態(tài)代理,那么委托類,中介類和代理類都是什么呢?
我們來看看<b> JavaScriptModuleInvocationHandler </b>類,它是<b> JavaScriptModuleRegistry </b>的內(nèi)部類
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance;
private final Class<? extends JavaScriptModule> mModuleInterface;
private @Nullable String mName;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance,
Class<? extends JavaScriptModule> moduleInterface) {
mCatalystInstance = catalystInstance;
mModuleInterface = moduleInterface;
if (ReactBuildConfig.DEBUG) {
Set<String> methodNames = new HashSet<>();
for (Method method : mModuleInterface.getDeclaredMethods()) {
if (!methodNames.add(method.getName())) {
throw new AssertionError(
"Method overloading is unsupported: " + mModuleInterface.getName() +
"#" + method.getName());
}
}
}
}
private String getJSModuleName() {
if (mName == null) {
// With proguard obfuscation turned on, proguard apparently (poorly) emulates inner
// classes or something because Class#getSimpleName() no longer strips the outer
// class name. We manually strip it here if necessary.
String name = mModuleInterface.getSimpleName();
int dollarSignIndex = name.lastIndexOf('$');
if (dollarSignIndex != -1) {
name = name.substring(dollarSignIndex + 1);
}
// getting the class name every call is expensive, so cache it
mName = name;
}
return mName;
}
@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;
}
}
看了invoke方法后,我們就明白了,調(diào)用了代理類<b>CatalystInstance</b>的callFunction()方法,看一下這個(gè)方法
@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);
}
</br>
這個(gè)方法最后,在 Java 層調(diào)用 JS 會(huì)調(diào)用 JNI 的 CallFunction 的方法。具體作用就是將想用調(diào)用的方法對(duì)應(yīng)的moduleId,methodId和arguments通過jni傳遞到j(luò)s端進(jìn)行調(diào)用。看到這三個(gè)參數(shù)有沒有回想到我們?cè)谧?cè)過程最后描述的,所有JavaScriptModule信息都被以(moduleID+methodID+ arguments)形式生成的JSON字符串預(yù)先存入了ReactBridge中.就是為了調(diào)用時(shí)作為一個(gè)索引表。
<h1>bridge層</h1>
接著java層的圖示,再上一張圖
前面說到Java 層調(diào)用 JS 會(huì)調(diào)用 JNI 的 CallFunction 的方法我們來看一下
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調(diào)用,其接口為react/jni中的<b>OnLoad.cpp</b>,里面涵蓋了類型操作,jsbundle 加載及全局變量操作等。看一下它的代碼
static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
NativeArray::jhybridobject args) {
auto bridge = extractRefPtr<Bridge>(env, obj);
auto arguments = cthis(wrap_alias(args));
try {
bridge->callFunction(
(double) moduleId,
(double) methodId,
std::move(arguments->array)
);
} catch (...) {
translatePendingCppExceptionToJavaException();
}
又調(diào)用了<b>Bridge</b>,實(shí)際上是繼承自它的<b>CountableBridge</b>,再看其代碼中的callFunction()
void Bridge::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
if (*m_destroyed) {
return;
}
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.callFunction");
#endif
auto returnedJSON = m_jsExecutor->callFunction(moduleId, methodId, arguments);
m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */);
}
回調(diào)轉(zhuǎn)接到<b>JSExector.cpp </b>,來執(zhí)行js,看看它的代碼
std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
// TODO: Make this a first class function instead of evaling. #9317773
std::vector<folly::dynamic> call{
(double) moduleId,
(double) methodId,
std::move(arguments),
};
return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
}
這里還有一個(gè)地方需要注意前面Java層構(gòu)造的JavaScriptModule信息JSON串在這里得到處理,作為映射表供callFunction()調(diào)用
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);
}
</br>
最后將調(diào)用轉(zhuǎn)發(fā)到 <b>JSCHelper.cpp</b> 中執(zhí)行evaluateScript 的函數(shù),從而執(zhí)行 JS 的調(diào)用。<b>JSCHelpers</b>對(duì)WebKit的一些API做了封裝,負(fù)責(zé)最終調(diào)用WebKit.
td::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
// TODO: Make this a first class function instead of evaling. #9317773
std::vector<folly::dynamic> call{
(double) moduleId,
(double) methodId,
std::move(arguments),
};
return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
}
JSCHelpers.cpp
JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source, const char *cachePath) {
JSValueRef exn, result;
#if WITH_FBJSCEXTENSIONS
if (source){
// If evaluating an application script, send it through `JSEvaluateScriptWithCache()`
// to add cache support.
result = JSEvaluateScriptWithCache(context, script, NULL, source, 0, &exn, cachePath);
} else {
result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
}
#else
result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
#endif
if (result == nullptr) {
Value exception = Value(context, exn);
std::string exceptionText = exception.toString().str();
FBLOGE("Got JS Exception: %s", exceptionText.c_str());
auto line = exception.asObject().getProperty("line");
std::ostringstream locationInfo;
std::string file = source != nullptr ? String::adopt(source).str() : "";
locationInfo << "(" << (file.length() ? file : "<unknown file>");
if (line != nullptr && line.isNumber()) {
locationInfo << ":" << line.asInteger();
}
locationInfo << ")";
throwJSExecutionException("%s %s", exceptionText.c_str(), locationInfo.str().c_str());
}
return result;
}
這樣java層到bridge層的調(diào)用就完成了,接下來就開始Bridge向Javascript的調(diào)用。
<h1>js層</h1>
與前面兩層相同,先上圖
在react-native\Libraries\BatchedBridge中有<b> BatchedBridge .js</b>和<b>MessageQueue.js</b>作為bridge層調(diào)用傳遞的接口,
BatchedBridge的代碼是這樣
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue();
只是簡單的new了一個(gè)<b>MessageQueue</b>對(duì)象,看來<b>MessageQueue</b>才是真的接口,果然在這里我們看到了__callFunction()方法
__callFunction(module: string, method: string, args: Array<any>) {
this._lastFlush = new Date().getTime();
this._eventLoopStartTime = this._lastFlush;
Systrace.beginEvent(`${module}.${method}()`);
if (this.__spy) {
this.__spy({ type: TO_JS, module, method, args});
}
const moduleMethods = this._getCallableModule(module);
invariant(
!!moduleMethods,
'Module %s is not a registered callable module (calling %s)',
module, method
);
invariant(
!!moduleMethods[method],
'Method %s does not exist on module %s',
method, module
);
const result = moduleMethods[method].apply(moduleMethods, args);
Systrace.endEvent();
return result;
}
而這一方法是在callFunctionReturnFlushedQueue()方法中調(diào)用的,它的代碼是
callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
this.__guard(() => {
this.__callFunction(module, method, args);
});
return this.flushedQueue();
}
在調(diào)用 CallFunction 執(zhí)行 Js 后,會(huì)調(diào)用 flushedQueue 更新隊(duì)列。在__callFunction()方法中有這樣一段代碼
const result = moduleMethods[method].apply(moduleMethods, args);
真是通過apply的方法最終將調(diào)用傳遞到j(luò)s層的對(duì)應(yīng)組件的對(duì)應(yīng)方法上。這就是從<b>java層到j(luò)s層</b>的整個(gè)調(diào)用過程。
</br>
其中還有一個(gè)地方需要注意的是在<b>MessageQueue</b>對(duì)象中有一個(gè)屬性_callableModules數(shù)組,它就是用來存放哪些Javascript組件是可以被調(diào)用的,正常情況下_callableModules的數(shù)據(jù)和JavaScriptModules的數(shù)據(jù)是完全對(duì)應(yīng)的,也就是前文中從java層傳遞下來的JavaScriptModule信息JSON串所包含的信息。<b>MessageQueue</b>對(duì)象通過
const moduleMethods = this._getCallableModule(module);
來對(duì)照映射獲取這相應(yīng)模塊信息。
<h1>總結(jié)</h1>
前文描述了java->js調(diào)用各層的組成原理,和調(diào)用過程。在這里總結(jié)一下調(diào)用過程過程
<h5>java</h5>
1.<b>Catalystance</b>通過<b> JavaScriptModuleRegistry </b>調(diào)用對(duì)應(yīng)于js層組件的java層<b> JavaScriptModule </b>。
2.如果實(shí)例中緩存得有就返回相應(yīng)的JSModule,沒有就由<b> JavaScriptModuleInvocationHandler </b>通過動(dòng)態(tài)代理統(tǒng)一處理發(fā)向Javascript的所有調(diào)用。
3.<b>CatalystanceImpl</b> 進(jìn)一步將 調(diào)用拆分為moduleID,methodID 及 arguments 的形式轉(zhuǎn)交給 <b>ReactBridge</b> JNI 處理。
</br>
<h5>bridge</h5>
1.<b>OnLoad</b>接入<b>ReactBridge</b>傳入的調(diào)用,并通過<b> Bridge </b>,<b> JSExector</b>將傳入的的調(diào)用裝成Javascript執(zhí)行語句。
2.<b>JSCHelper</b>將調(diào)用交給<b>WebKit</b>內(nèi)核 通過evaluateScript 的方法將 moduleID,methodID 及 arguments 借助 JSC 傳遞給 Js 層。
</br>
<h5>js</h5>
1.調(diào)用通過<b> BatchedBridge </b>傳入<b> MessageQueue </b>它預(yù)先注冊(cè)了所有能夠接收通信請(qǐng)求的組件_callableModules ,同時(shí)也保存著來自Java層JavaScriptModule的兩張映射表。
2.<b> MessageQueue </b>接收到調(diào)用后,通過映射表確認(rèn)具體請(qǐng)求信息,然后通過apply方式調(diào)用js層相應(yīng)組件。
最后再回頭看各層的配圖,應(yīng)該對(duì)整個(gè)過程有更加清晰的理解了。這里只描述的java->js調(diào)用的過程,但RNA的通信機(jī)制是雙向的,js->java的調(diào)用及其各層的關(guān)鍵組成將在之后的學(xué)習(xí)探究中描述。