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系統上已經有了實現。就是WebView。熟悉WebView的都知道底層實現是WebKit,雖然在Android 4.4系統上切換成了Chromium,但歸根結底還是WebKit的變種,僅僅是加了谷歌自己的一些東西。然而React-Native與WebView并沒有一點關系,并且后者的WebKit內核也不支持ES6特性(React語法大多基于ES6),那怎么辦?僅僅能自己弄一套最新的WebKit作為React-Native的解釋器了,這個從安卓projectlib文件夾以下的libjsc.so動態鏈接庫文件能夠印證,這樣做還有兩個重要優點就是兼容絕大多少設備版本號和方便加入自己定義功能。
所以由此,我們大概能夠猜到React-Native的通信原理,畫一張圖來簡單地描寫敘述一下:
2、Java層實現
之前說過。React-Native的重要設計思想是組件化,為了便于維護擴展和減少耦合,React-Native并沒有為了實現某一詳細的通信編寫代碼(比方上篇博文所講的觸摸事件傳遞),而是設計了一套標準用于組件化。
這套標準是向開發人員開放的,開發人員能夠自行編寫須要的組件用來在Native與Javascript之間通信,雖然這并非推薦的選擇。
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中也不能有重載。
細致的讀者會發現,凝視里有兩個單詞非常關鍵。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>() {@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注冊表??礃幼咏K于找到注冊類了。
先不著急。繼續往下看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;}@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里調用了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() {@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通過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_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里面將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. #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);...}這里就是通過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
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
轉載于:https://www.cnblogs.com/llguanli/p/8442598.html
總結
以上是生活随笔為你收集整理的React-Native系列Android——Native与Javascript通信原理(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Python中操作文件之truncat
- 下一篇: js 一/二维数组排序