React-Native系列Android——Native與Javascript通信原理(一)
React-Native最核心的是Native與Javascript之間的通信,並且是雙向通信。Native層到Javascript層,Javascript層到Native層。雖說是兩個方向,但實現上大同小異,我們先從Native層入手,研究一下Native調用Javascript的過程。
1、通信模型
Android應用層的程序語言是Java。React-Native在Native端的框架實現用的也是Java語言,所以實質上是Java與Javascript兩種程序語言的調用。
事實上這個過程,在Android
所以由此,我們大概能夠猜到React-Native
2、Java層實現
之前說過。React-Native的重要設計思想是組件化,為了便於維護擴展和減少耦合,React-Native並沒有為了實現某一詳細的通信編寫代碼(比方上篇博文所講的觸摸事件傳遞),而是設計了一套標準用於組件化。
這套標準是向開發人員開放的,開發人員能夠自行編寫須要的組件用來在Native與Javascript之間通信,雖然這並非推薦的選擇。
2.1 JavaScriptModule組件
React-Native官方實現了一定數量的組件,比方觸摸事件組件。按鍵組件等。這些組件都位於CoreModulesPackage
/**
* 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中也不能有重載。
細致的讀者會發現,凝視裏有兩個單詞非常關鍵。extending和implemented 。Java層extend。Javascript層implement,也就是說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中註冊。那我們來看一下註冊的過程。
RCTEventEmitter是facebook官方定義的。組裝在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是用來催化Native和Javascript之間的反應?讓我們來瞧一瞧真面目吧。
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>() {
@Override
public 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 。字面意思就是真正連接Native和Javascript的橋梁了。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字符串,而字符串由什麽構成呢?nativeModule和jsModules,nativeModule先無論,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這種方法幹了什麽事呢?遍歷全部JavaScriptModule的public方法,然後通過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端的通信請求。
JavaScriptModuleInvocationHandler是JavaScriptModuleRegistry的一個內部類,動態代理的攔截類。
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstanceImpl mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstanceImpl catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @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裏調用了CatalystInstance的callFunction方法,主要傳入了ModuleId、MethodId和Arguments這三個重要參數(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() {
@Override
public 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通過JNI,JNI再調用WebKit不就OK了麽?
繼續前面說的ReactBridge的setGlobalVariable和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調用,而JNI層的入口是react/jni/OnLoad.cpp,和常規的javah規則不同,它是通過RegisterNatives方式註冊的,JNI_OnLoad裏面註冊了setGlobalVariable和callFunction等native本地方法。
廢話不多說。來看看c++中setGlobalVariable和callFunction的實現吧。
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_FBSYSTRACE
int systraceCookie = m_systraceCookie++;
...
#endif
#ifdef WITH_FBSYSTRACE
runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {
...
#else
runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName] (JSExecutor* executor) {
#endif
executor->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. #9317773
std::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 JSC
folly::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裏面將methodName和jsonArgs拼接成了一個apply的Javascript運行語句。最後調用jni/react/JSCHelpers.cpp的evaluateScript的來運行這個語句,完畢Bridge向Javascript的調用。(JSCHelpers對WebKit的一些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->Javascript,WebKit中提供了很多與Javascript通信的API。比方evaluateScript、JSContextGetGlobalObject、JSObjectSetProperty等。
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);
}
JSContextGetGlobalObject是WeiKit的方法。其目的是獲取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對象裏又有兩個屬性:remoteModuleConfig和localModulesConfig。
哪兒冒出來的呢?
有心的讀者能猜到這兩個屬性都是定義在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. #9317773
std::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 JSC
folly::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);
...
}
這裏就是通過moduleID和methodID來查詢兩張映射Table了。獲取到了詳細的moduleName和methodName,接著肯定要做調用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)
假設以RCTEventEmitter的receiveTouches方法調用為例。詳細語句應該是這樣:
ReactNativeEventEmitter.receiveTouches.apply(moduleMethods, eventTopLevelType, touches, changedIndices);
結束!通信完畢。大功告成!
5、總結
整個通信過程涉及到三種程序語言:Java、C++、Javascript,這還僅僅是單向的通信流程。假設是逆向則更加復雜。因為篇幅的關系。留到以後的博客裏面研究。
最後總結一下幾個關鍵點:
1、Java層
JavaScriptModule接口類定義通信方法,在ReactApplicationContext創建的時候存入註冊表類JavaScriptModuleRegistry中。同一時候通過動態代理生成代理實例,並在代理攔截類JavaScriptModuleInvocationHandler中統一處理發向Javascript的全部通信請求。
CatalystInstanceImpl類內部的ReactBridge詳細實現與Javascript的通信請求,它是調用Bridge Jni 的出口。
在ReactBridge被創建的時候會將JavaScriptModule信息表預先發給Javascript層用來生成映射表。
2、C++層
OnLoad是jni層的調用入口,註冊了全部的native方法。其內部調用又都是通過CountableBridge來完畢的,CountableBridge是Bridge的無實現子類。而在Bridge裏面JSCExecutor才是真正的運行者。
JSCExecutor將全部來自Java層的通信請求封裝成Javascript運行語句。交給WebKit內核完畢向Javascript層的調用。
3、Javascript層
BatchedBridge是Javascript層的調用入口,而其又是MessageQueue的偽裝者。MessageQueue預先註冊了全部能夠接收通信請求的組件_callableModules 。同一時候也保存著來自Java層JavaScriptModule的兩張映射表。
接收通信請求時,先通過映射表確認詳細請求信息,再確認Javascript組件能否夠被調用,最後通過apply方式完畢運行。
整個通信過程流程例如以下圖:
本博客不定期持續更新,歡迎關註和交流:
http://blog.csdn.net/megatronkings
React-Native系列Android——Native與Javascript通信原理(一)