Bugly 技術乾貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,經過平常工做經驗的總結以及感悟撰寫而成,內容均屬原創,轉載請標明出處。
本文從源碼角度剖析 RNA 中 Java <> Js 的通訊機制(基於最新的 RNA Release 20)。javascript
對於傳統 Java<>Js 通訊而言,Js 調用 Java 通不外乎 Jsbridge、onprompt、log 及 addjavascriptinterface 四種方式,在 Java 調用 Js 只有 loadurl 及高版本才支持的 evaluateJavaScript 兩種。但在 RN 中沒有采用了傳統 Java 與 Js 之間的通訊機制,而是藉助 MessageQueue 及模塊配置表,將調用轉化爲{moduleID, methodID,callbackID,args},處理端在模塊配置表裏查找註冊的模塊與方法並調用。java
在 RNA 中,在應用啓動時根據 ReactPackage 會自動生成 NativeModuleRegistry 及 JavaScriptModuleRegistry 兩份模塊配置表,包含系統及自定義模塊,Java 端與 Js 端持有相同的模塊配置表,標識爲可識別爲 Native 模塊或 Js 模塊都是經過實現相應接口,並將實例添加 ReactPackage 的 CreactXXModules 方法便可。react
CoreModulesPackage.java @Override public List<NativeModule> createNativeModules( ReactApplicationContext catalystApplicationContext) { return Arrays.<NativeModule>asList( new AndroidInfoModule(), new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler), new DebugComponentOwnershipModule(catalystApplicationContext)); } @Override public List<Class<? extends JavaScriptModule>> createJSModules() { return Arrays.asList( DeviceEventManagerModule.RCTDeviceEventEmitter.class, JSTimersExecution.class, RCTEventEmitter.class, RCTNativeAppEventEmitter.class, AppRegistry.class } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return new ArrayList<>(0); }
Js 模塊 extends 自 JavascriptModule,映射在 Js 相對應 Js 模塊,經過動態代理實現調用 Js 模塊。下例 AppRegistry.java 爲在加載完 Jsbundle 後,Native 去啓動 React Application 的總入口,appkey 爲應用的 ID。映射每一個 JavascriptModule 的信息保存在 JavaScriptModuleRegistration 中,統一由 JavaScriptModuleRegistry 統一管理。json
AppRegistry.java public interface AppRegistry extends JavaScriptModule { void runApplication(String appKey, WritableMap appParameters); void unmountApplicationComponentAtRootTag(int rootNodeTag); }
Java 模塊 extends 自 BaseJavaModule,在 Js 層存在同名文件識別爲可調用的 Native。重寫 getName 識別爲 Js 的模塊名,重寫 getConstants 識別爲 Js 可訪問的常量,方法經過註解 @ReactMethod 可識別供 Js 調用的 API 接口,全部 Java 層提供的模塊接口統一由 NativeModuleRegistry 統一暴露。微信
AndroidInfoModule.java public class AndroidInfoModule extends BaseJavaModule { @Override public String getName() { return "AndroidConstants"; } @Override public @Nullable Map<String, Object> getConstants() { HashMap<String, Object> constants = new HashMap<String, Object>(); constants.put("Version", Build.VERSION.SDK_INT); return constants; } }
完整通訊機制流程圖:app
簡要說明下這5個步驟:ide
CatalystanceImpl 爲 Js<>Java 通訊高層封裝實現類,業務模塊經過 ReactInstanceManager 與 CatalystanceImpl 間接通訊,調用Js暴露出來的API。函數
未來自Java層的調用拆分爲 ModuleID,MethodID 及 Params,JavaScriptModuleInvocationHandler 經過動態代理方式交由 CatalystanceImpl 統一處理。工具
CatalystanceImpl 進一步將 ModuleID,MethodID 及 Params 轉交給 ReactBridge JNI 處理。oop
ReactBridge 調用 C++層的調用鏈轉發 ModuleID,MethodID 及 Params。
最終經過 JSCHelper 的 evaluateScript 的方法將 ModuleID,MethodID 及 Params 藉助 JSC 傳遞給 Js 層。
總體調用關係比較清晰,下面分別藉助源碼說明上面整個流程。
在 Java 層 implements JavaScriptModule 這個 interface 被識別爲 Js 層暴露的公共 Module,(由JS不容許的方法名稱重載,因此繼承自 JavaScriptModule 一樣不容許方法重載)。JavaScriptModuleRegistry 負責管理全部的 JavaScriptModule,持有對 JavaScriptModuleInvocationHandler 的引用,經過 invoke 的方式,統一調度從 Java -> Js 的調用。
JavaScriptModuleInvocationHandler.java 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; } }
CatalystInstance 爲 Java 與 Js 以前通訊的高層接口,已被抽離成接口,CatalystInstanceImpl 爲其基礎實現類,業務側在 ReactInstanceManager Create ReactContext 時經過 Builder 構建實例化,業務通常不直接持有 CatalystInstance 的引用,通常經過 Framework 層的 ReactInstanceManager 的實現類進行訪問。持有對 JavaScriptModuleRegistry& RativeModuleRegistry 的引用。
CatalystInstanceImpl.java @Override public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) { return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface); }
在 CatalystInstance 初始化時會調用 initializeBridge 初始化私有成員 ReactBridge,ReactBridge 作爲 JNI 層的通訊橋接對象,負責 Java<>JCS 之間的通訊。在 Java 層調用 JS 會調用 JNI 的 CallFunction 的方法,經過 JSC 轉接到 JS 層的模塊。
CatalystInstanceImpl.java private ReactBridge initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { mReactQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor"); ReactBridge bridge; try { bridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mReactQueueConfiguration.getNativeModulesQueueThread()); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig"); try { bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); bridge.setGlobalVariable( "__RCTProfileIsProfiling", Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false"); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } return bridge; } ReactBridge.java /** * All native functions are not thread safe and appropriate queues should be used */ public native void loadScriptFromAssets(AssetManager assetManager, String assetName); public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL); public native void callFunction(int moduleId, int methodId, NativeArray arguments); public native void invokeCallback(int callbackID, NativeArray arguments); public native void setGlobalVariable(String propertyName, String jsonEncodedArgument); public native boolean supportsProfiling(); public native void startProfiler(String title); public native void stopProfiler(String title, String filename); private native void handleMemoryPressureModerate(); private native void handleMemoryPressureCritical();
Onload.cpp 爲 C++ 層主要入口,涵蓋類型操做,jsbundle 加載及全局變量操做等。經過 bridge.cpp 的轉接到 JSExector.cpp 執行 JS。JSExector.cpp 最終將調用轉發到 JSCHelper.cpp 中執行evaluateScript 的函數,從而執行 JS 的調用。
OnLoad.cpp 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(); } Bridge.cpp 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 */); } JSCExectutor.cpp 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)); } 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 -> C++ 層調用鏈結束,JSC 將執行 JS 調用,在 JS Framewrok 層接收來自 C++的調用爲 MessageQueue.js 的 callFunctionReturnFlushedQueue。在調用 CallFunction 執行 Js 後,會調用 flushedQueue 更新隊列。
MessageQueue.js callFunctionReturnFlushedQueue(module, method, args) { guard(() => { this.__callFunction(module, method, args); this.__callImmediates(); }); return this.flushedQueue(); } MessageQueue.js __callFunction(module, method, args) { this._lastFlush = new Date().getTime(); this._eventLoopStartTime = this._lastFlush; if (isFinite(module)) { method = this._methodTable[module][method]; module = this._moduleTable[module]; } Systrace.beginEvent(`${module}.${method}()`); if (__DEV__ && SPY_MODE) { console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); } var moduleMethods = this._callableModules[module]; invariant( !!moduleMethods, 'Module %s is not a registered callable module.', module ); moduleMethods[method].apply(moduleMethods, args); Systrace.endEvent(); }
對於 JS -> Java 調用的設計相對獨特,在 React Native 的設計中, JS 是不能直接調用 Java 的接口的,而是未來自 JS 層的調用 Push 到 JS 層的一個 MessageQueue 中,在事件發生時會調用 JS 相應的模塊方法去處理,處理完這些事件後再執行 JS 想讓 Java 執行的方法,與 native 開發裏事件響應機制是一致的。
完整通訊機制流程圖:
簡要說明下這5個步驟:
JS 層調用 Java 層暴露的 API。
未來自 JS 層的調用拆分爲 ModuleID,MethodID 及 Params 分別 push 進相應的 queue 中。
當事件發生時,會執行從 Java -> JS 上面這條調用鏈路。
在執行完 callFunctionReturnFlushedQueue 後,會調用 flushedQueue 並返回 MessageQueue,即刷新後的隊列。
Java 層的 JavaRegistry 根據模塊配置表調用相應模塊執行。
下面分別藉助源碼說明上面整個流程。
MessageQueue.js __nativeCall(module, method, params, onFail, onSucc) { if (onFail || onSucc) { // eventually delete old debug info (this._callbackID > (1 << 5)) && (this._debugInfo[this._callbackID >> 5] = null); this._debugInfo[this._callbackID >> 1] = [module, method]; onFail && params.push(this._callbackID); this._callbacks[this._callbackID++] = onFail; onSucc && params.push(this._callbackID); this._callbacks[this._callbackID++] = onSucc; } global.nativeTraceBeginAsyncFlow && global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID); this._callID++; 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) + ')'); } } MessageQueue.js callFunctionReturnFlushedQueue(module, method, args) { guard(() => { this.__callFunction(module, method, args); this.__callImmediates(); }); return this.flushedQueue(); } invokeCallbackAndReturnFlushedQueue(cbID, args) { guard(() => { this.__invokeCallback(cbID, args); this.__callImmediates(); }); return this.flushedQueue(); } flushedQueue() { this.__callImmediates(); let queue = this._queue; this._queue = [[], [], [], this._callID]; return queue[0].length ? queue : null; }
Js 層經過調用__nativeCall 將 ModuleID,MethodID 及 Params 放入不一樣隊列。當 Java 層事件發生後會調用 Java -> Js 整個調用鏈,最終到 flushedQueue 並返回 MessageQueue。
Java 層收到來自 MessageQueue 的調用信息,查詢 Java 層模塊配置表,調用相應模塊相應接口。
CatalystInstanceImpl$NativeModulesReactCallback.java private class NativeModulesReactCallback implements ReactCallback { @Override public void call(int moduleId, int methodId, ReadableNativeArray parameters) { mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); // Suppress any callbacks if destroyed - will only lead to sadness. if (mDestroyed) { return; } mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters); } }
查詢到相應 Java 模塊,經過反射調用相應接口。
BaseJavaModule.java @Override public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod"); try { mMethod.invoke(BaseJavaModule.this, mArguments); } catch (IllegalArgumentException ie) { } } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } }
更多精彩內容歡迎關注騰訊Bugly微信公衆帳號:
騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!