【騰訊Bugly乾貨分享】深入源碼探索 ReactNative 通信機制

来源:http://www.cnblogs.com/bugly/archive/2016/03/11/5266250.html
-Advertisement-
Play Games

本文從源碼角度剖析 RNA 中 Java <> Js 的通信機制(基於最新的 RNA Release 20)。 對於傳統 Java<>Js 通信而言,Js 調用 Java 通不外乎 Jsbridge、onprompt、log 及 addjavascriptinterface 四種方式,在 Java...


Bugly 技術乾貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,通過日常工作經驗的總結以及感悟撰寫而成,內容均屬原創,轉載請標明出處。

本文從源碼角度剖析 RNA 中 Java <> Js 的通信機制(基於最新的 RNA Release 20)。

對於傳統 Java<>Js 通信而言,Js 調用 Java 通不外乎 Jsbridge、onprompt、log 及 addjavascriptinterface 四種方式,在 Java 調用 Js 只有 loadurl 及高版本才支持的 evaluateJavaScript 兩種。但在 RN 中沒有採用了傳統 Java 與 Js 之間的通信機制,而是藉助 MessageQueue 及模塊配置表,將調用轉化為{moduleID, methodID,callbackID,args},處理端在模塊配置表裡查找註冊的模塊與方法並調用。

一、Module Registry

在 RNA 中,在應用啟動時根據 ReactPackage 會自動生成 NativeModuleRegistry 及 JavaScriptModuleRegistry 兩份模塊配置表,包含系統及自定義模塊,Java 端與 Js 端持有相同的模塊配置表,標識為可識別為 Native 模塊或 Js 模塊都是通過實現相應介面,並將實例添加 ReactPackage 的 CreactXXModules 方法即可。

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 統一管理。

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;
  }
}

二、Java -> Js

完整通信機制流程圖:

bugly 乾貨分享

簡要說明下這5個步驟:

1.CatalystanceImpl 為 Js<>Java 通信高層封裝實現類,業務模塊通過 ReactInstanceManager 與 CatalystanceImpl 間接通信,調用Js暴露出來的API。

2.將來自Java層的調用拆分為 ModuleID,MethodID 及 Params,JavaScriptModuleInvocationHandler 通過動態代理方式交由 CatalystanceImpl 統一處理。

  1. CatalystanceImpl 進一步將 ModuleID,MethodID 及 Params 轉交給 ReactBridge JNI 處理。

  2. ReactBridge 調用 C++層的調用鏈轉發 ModuleID,MethodID 及 Params。

    5.最終通過 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

對於 JS -> Java 調用的設計相對獨特,在 React Native 的設計中, JS 是不能直接調用 Java 的介面的,而是將來自 JS 層的調用 Push 到 JS 層的一個 MessageQueue 中,在事件發生時會調用 JS 相應的模塊方法去處理,處理完這些事件後再執行 JS 想讓 Java 執行的方法,與 native 開發里事件響應機制是一致的。

完整通信機制流程圖:

Bugly 乾貨分享

簡要說明下這5個步驟:

1.JS 層調用 Java 層暴露的 API。

2.將來自 JS 層的調用拆分為 ModuleID,MethodID 及 Params 分別 push 進相應的 queue 中。

3.當事件發生時,會執行從 Java -> JS 上面這條調用鏈路。

4.在執行完 callFunctionReturnFlushedQueue 後,會調用 flushedQueue 並返回 MessageQueue,即刷新後的隊列。

5.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是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智能合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精准定位功能幫助開發同學定位到出問題的代碼行,實時上報可以在發佈後快速的瞭解應用的質量情況,適配最新的 iOS, Android 官方操作系統,鵝廠的工程師都在使用,快來加入我們吧!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • /** * 當app進入後臺時調用 */- (void)applicationDidEnterBackground:(UIApplication *)application{ /** * app的狀態 * 1.死亡狀態:沒有打開app * 2.前臺運行狀態 * 3.後臺暫停狀態:停止一切動畫、定時器
  • 阿打發十多個
  • Android 中Activitys之間的數據傳遞以及對返回結果的處理
  • 下麵是一些效果圖 下麵是代碼。有些枯燥 , 其實並不難 。 #import <UIKit/UIKit.h> @interface ViewController : UIViewController<UIPickerViewDelegate,UIPickerViewDataSource> @prope
  • 廣播都寫了一大篇 這裡不能保存,一直提示有 //竊聽的 發個連接吧 https://github.com/ln0491/AndroidStudy/blob/master/%E5%AE%89%E5%8D%93%E7%AC%AC%E5%8D%81%E4%BA%8C%E5%A4%A9%E7%AC%94%E
  • 用於Android開發的免費類庫和工具集合,按目錄分類。 Action Bars ActionBarSherlock Extended ActionBar FadingActionBar GlassActionBar v7 appcompat library 廣告 AdMob Google Mobi
  • android中的文件(圖片)上傳其實沒什麼複雜的,主要是對 multipart/form data 協議要有所瞭解。 關於 multipart/form data 協議,在 RFC文檔中有詳細的描述 [RFC 2388 Returning Values from Forms: multipart/
  • /** * 判斷當前時間是否處於某個時間段內 * * @param startTime 開始時間 * @param expireTime 結束時間 */ - (BOOL)validateWithStartTime:(NSString *)startTime withExpireTime:(NSStr
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...