React-Native系列Android——Native與Javascript通訊原理(三)

來源:互聯網
上載者:User

React-Native系列Android——Native與Javascript通訊原理(三)

前面兩篇部落格,詳細分析了NativeJavascript通訊的過程,可以滿足絕大部分情境下NativeJavascript的相互調用,但是仍然有不健全的情況。

比如Javascript層要即時擷取Native的一些狀態,就需要Native被動地向Javascript層通訊了。這個過程區別於通訊第一篇中Native主動向Javascript層通訊,本篇部落格就來研究下這樣一個被動回調的過程!

首先,從一個常用的情境開始分析。

假設前端開發人員在Javascript的代碼中想要擷取APP的狀態,比如APP是否是處於前台(active),還是後台(background)。大概有兩種實現方式:
1、NativeAPP每次狀態切換的時候,調用callFunction將最新的狀態傳給Javascript層,然後由Javascript緩衝起來,這樣開發人員想要擷取狀態可以能直接使用這個緩衝的值。
2、前端開發人員在Javascript中想要擷取狀態時,先向Native端發起通訊請求,表示想擷取狀態,然後由Native端把這個狀態作為通訊應答返給Javascript層。

這兩種方案都有各自的使用性情境,並且在React-Native都有相應實現。第一種實現對開發人員來說相對簡單,直接取緩衝值,是一個完全同步的過程。第二種實現向Native發起通訊請求,需要等待Native的應答,是一個非同步過程。

第一種方案實現原理在React-Native系列Android——Native與Javascript通訊原理(一)中已經詳細分析過了,不再贅述,本篇博文重點來分析下第二種方案的實現原理。

1、JavaScript的請求

NativeJavaScript的通訊,都是由Native主動發起,然後由JavaScript應答,但是JavaScript是無法向Native主動發起通訊的。那麼,JavaScript如何才能向Native發起通訊請求呢?

上一篇博文中講過,JavaScript應答Native是通過將應答資料封裝成JSON格式,然後在flushedQueue() 返給Bridge再返給Native的。如果JavaScript在這個應答資訊加入通訊請求的標識,那麼Native在解析應答資訊時發現了其中包含JavaScript的通訊標識,然後Native來應答這個請求,這樣不就完成了一次JavaScript請求Native的過程嗎?

第一步:在返給Native的應答資訊中加入JavaScript的通訊請求

同樣以在Javascript中擷取APP目前狀態為例,示範代碼如下:

var NativeModules = require('NativeModules');var RCTAppState = NativeModules.AppState;var logError = require('logError');RCTAppState.getCurrentAppState(  (appStateData) => {     console.log('dev', 'current state: ' + appStateData.app_state);  },  logError);

前一篇博文中分析過NativeModules的前世今生,它是一個動態初始化的類(具體請看前篇Native與Javascript通訊原理(二),這裡略過),RCTAppState.getCurrentAppState實際上是調用的是MessageQueue.js的下面這段代碼:

function(...args) {   let lastArg = args.length > 0 ? args[args.length - 1] : null;   let secondLastArg = args.length > 1 ? args[args.length - 2] : null;   let hasSuccCB = typeof lastArg === 'function';   let hasErrorCB = typeof secondLastArg === 'function';   hasErrorCB && invariant(hasSuccCB, 'Cannot have a non-function arg after a function arg.');   let numCBs = hasSuccCB + hasErrorCB;   let onSucc = hasSuccCB ? lastArg : null;   let onFail = hasErrorCB ? secondLastArg : null;   args = args.slice(0, args.length - numCBs);   return self.__nativeCall(module, method, args, onFail, onSucc);};

這裡面參數args具體化有兩個,一個是lambda運算式回呼函數,一個是logError,都是function類型。解析的時候lastArg變數指logErrorsecondLastArg變數指回呼函數。

所以調用__nativeCall函數時候傳遞的兩個參數onFailonSucc,就分別指回呼函數和logError。這裡明顯是React-Native的命名bug了,差點以為是兩個變數解析顛倒了,不過不影響整個流程(原因是Native代碼中解析參數時預設是onSucc在前面,又顛倒回來了,後面會分析到)。

接下來看__nativeCall

__nativeCall(module, method, params, onFail, onSucc) {    if (onFail || onSucc) {      ...      onFail && params.push(this._callbackID);      this._callbacks[this._callbackID++] = onFail;      onSucc && params.push(this._callbackID);      this._callbacks[this._callbackID++] = onSucc;    }    ...    this._queue[MODULE_IDS].push(module);    this._queue[METHOD_IDS].push(method);    this._queue[PARAMS].push(params);    ...  }

this._queue的作用上篇分析過,是用來儲存應答Native的資料的,這裡主要來看if裡面的判斷邏輯。

this._callbackID是作為this._callbacks集合的索引來標識回呼函數的,同時這個索引會放到params裡面傳遞給Native端,Native端應答的時候會將這個索引傳回到Javascript端,這樣Javascript端就能通過索引找到事先存放在this._callbacks集合裡的回呼函數了。所以,this._callbackID就是Javascript請求Native的標識了。

第二步:Native如何應答Javascript端

中間還有一步flushedQueue()Bridge層的傳遞過程,參考前文即可,這裡跳過。

前篇博文中分析過Native處理來自Javascript應答資訊,都是通過moduleID+methodID映射到具體NativeModule組件的方法,然後解析參數,最後通過invoke反射方式完成調用的。

例子中,擷取APP目前狀態的組件在Native端對應的NativeModule類是AppStateModule。被映射到的方法是getCurrentAppState,它有兩個Callback類型的參數。

來看看NativeModule解析Callback型別參數時的代碼,位於其父類com.facebook.react.bridge.BaseJavaModule.java

  static final private ArgumentExtractor ARGUMENT_EXTRACTOR_CALLBACK =      new ArgumentExtractor() {        @Override        public @Nullable Callback extractArgument(            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {          if (jsArguments.isNull(atIndex)) {            return null;          } else {            int id = (int) jsArguments.getDouble(atIndex);            return new CallbackImpl(catalystInstance, id);          }        }      };
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {      ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];      for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {        Class argumentClass = paramTypes[i];        ...        if (argumentClass == Callback.class) {          argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;        }        ...      }      return argumentExtractors;    }

對於Callback型別參數,使用的參數提取器是ARGUMENT_EXTRACTOR_CALLBACK,在其extractArgument方法裡面提取出由Javascript端傳來的callbackID,構造進CallbackImpl對象裡面。而這個構造出來的CallbackImpl對象,就是invoke反射getCurrentAppState方法裡的參數了。

下面來看一下被反射的getCurrentAppState方法,位於com.facebook.react.modules.appstate.AppStateModule.java

public class AppStateModule extends ReactContextBaseJavaModule        implements LifecycleEventListener {  public static final String APP_STATE_ACTIVE = "active";  public static final String APP_STATE_BACKGROUND = "background";  private String mAppState = "uninitialized";  public AppStateModule(ReactApplicationContext reactContext) {    super(reactContext);  }  @Override  public String getName() {    return "AppState";  }  @Override  public void initialize() {    getReactApplicationContext().addLifecycleEventListener(this);  }  @ReactMethod  public void getCurrentAppState(Callback success, Callback error) {    success.invoke(createAppStateEventMap());  }  @Override  public void onHostResume() {    mAppState = APP_STATE_ACTIVE;    sendAppStateChangeEvent();  }  @Override  public void onHostPause() {    mAppState = APP_STATE_BACKGROUND;    sendAppStateChangeEvent();  }  ...  private WritableMap createAppStateEventMap() {    WritableMap appState = Arguments.createMap();    appState.putString("app_state", mAppState);    return appState;  }  ...}

Activity生命週期變化的時候,會更新狀態到mAppStatecreateAppStateEventMap()mAppState封裝在用於Native-Bridge間傳遞的WritableMap對象中。然後調用了success.invoke(),而這個Callback類型的 success參數就是前面ARGUMENT_EXTRACTOR_CALLBACK構造出來的CallbackImpl對象了,它記憶體儲存著用於回調的標識callbackID

所以,來看CallbackImplinvoke方法,代碼在com.facebook.react.bridge.CallbackImpl.java

public final class CallbackImpl implements Callback {  private final CatalystInstance mCatalystInstance;  private final int mCallbackId;  public CallbackImpl(CatalystInstance bridge, int callbackId) {    mCatalystInstance = bridge;    mCallbackId = callbackId;  }  @Override  public void invoke(Object... args) {    mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));  }}

invoke方法裡面又調用了CatalystInstance.invokeCallback,通過前面兩篇博文我們知道CatalystInstanceNativeJavascript通訊的入口,那麼這裡很明顯其CatalystInstance.invokeCallback就是NativeJavascript的應答了。裡麵包含了標識callbackID和內容資料mAppState

CatalystInstance的實作類別CatalystInstanceImpl內部,又是通過ReactBridge調用JNI的,這一點同
React-Native系列Android——Native與Javascript通訊原理(一)中的callFunction原理完全一樣。

public class CatalystInstanceImpl implements CatalystInstance {   ...   public void invokeCallback(final int callbackID, final NativeArray arguments) {       ...       Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);       ...   }   ...}

第三步:Bridge的中轉

上一步中通過JNI調用了invokeCallback方法,裡面有兩個參數:callbackIDargumentscallbackID是來自Javascript端的通訊回調標識,argumentsNative應答Javascript請求的內容。Bridge的作用就是將這兩個參數中轉到Javascript端。

Bridge層的調用入口是react\jni\OnLoad.cpp,先來瞧瞧invokeCallback方法

static void invokeCallback(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint callbackId,                           NativeArray::jhybridobject args) {  auto bridge = extractRefPtr(env, obj);  auto arguments = cthis(wrap_alias(args));  try {    bridge->invokeCallback(      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),      (double) callbackId,      std::move(arguments->array)    );  } catch (...) {    translatePendingCppExceptionToJavaException();  }}

調用的又是CountableBridgeBridge對象的invokeCallback方法,代碼在react\Bridge.cpp

void Bridge::invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& arguments) {  ...  auto executorMessageQueueThread = getMessageQueueThread(executorToken);  if (executorMessageQueueThread == nullptr) {    ...    return;  }  std::shared_ptr isDestroyed = m_destroyed;  executorMessageQueueThread->runOnQueue([=] () {    ...    JSExecutor *executor = getExecutor(executorToken);    if (executor == nullptr) {      ...      return;    }    ...    executor->invokeCallback(callbackId, arguments);  });}

executorMessageQueueThread隊列線程裡面,執行的是JSExecutorinvokeCallback方法。

繼續來看react\JSCExecutor.cpp

void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {  std::vector call{    (double) callbackId,    std::move(arguments)  };  std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));  m_bridge->callNativeModules(*this, calls, true);}static std::string executeJSCallWithJSC(    JSGlobalContextRef ctx,    const std::string& methodName,    const std::vector& arguments) {  ...  // Evaluate script with JSC  folly::dynamic jsonArgs(arguments.begin(), arguments.end());  auto js = folly::to(          "__fbBatchedBridge.", methodName, ".apply(null, ",      folly::toJson(jsonArgs), ")");  auto result = evaluateScript(ctx, String(js.c_str()), nullptr);  return Value(ctx, result).toJSONString();}

這段代碼和callFunction非常相似,只不過executeJSCallWithJSC裡面第二個參數換成了invokeCallbackAndReturnFlushedQueue

這一段產生的Javascript執行語句是

__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);

jsonArgs中包含callbackIDargumentsWebkit執行這段Javascript語句達到串連到Javascript端的目的。

當然,執行完Javascript語句後也有一個result返回,用來調用callNativeModules,作為後續的通訊請求,流程和前篇完全一致!

第四步:Javascript接收Native的應答

參考React-Native系列Android——Native與Javascript通訊原理(一),上一步中Bridge建立的Javascript執行語句

__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);

其實等同於

MessageQueue.invokeCallbackAndReturnFlushedQueue.apply(null, callbackID, args);

所以執行的是MessageQueue.jsinvokeCallbackAndReturnFlushedQueue方法。

  invokeCallbackAndReturnFlushedQueue(cbID, args) {    guard(() => {      this.__invokeCallback(cbID, args);      this.__callImmediates();    });    return this.flushedQueue();  }

這裡的cbID其實就是callbackID了,也就是第一步裡面的this._callbackID。這個值是由Javascript傳給Native的,現在又從Native傳回來了,完璧歸趙啊!

下面調用的是this.__invokeCallback

  __invokeCallback(cbID, args) {    ...    let callback = this._callbacks[cbID];    ...    this._callbacks[cbID & ~1] = null;    this._callbacks[cbID |  1] = null;    callback.apply(null, args);    ...  }

this._callbacks集合裡面以callbackID為索引儲存著回呼函數callback,這裡就可以通過cbID這個索引為key取出來了。這樣執行callback.apply(null, args)就等於執行回呼函數了。

同時,還要清除this._callbacks集合裡面儲存的回呼函數。由於__nativeCall中封裝回呼函數時,先後儲存了兩個回呼函數onFail(索引為偶數)和onSucc(索引為奇數,比前者+1),而取出來的callback並不確定是onFail還是onSucc。所以,cbID & ~1最低位置0cbID | 1最低位置1,這樣無論cbID標識是onFail還是onSucc的索引,都能保證兩者完全清除。

擷取APP狀態例子中的回呼函數是

function(appStateData){   console.log('dev', 'current state: ' + appStateData.app_state);}

app_state變數的值就是當前APP的狀態了,與AppStateModule中的值的封裝恰好呼應

private WritableMap createAppStateEventMap() {    WritableMap appState = Arguments.createMap();    appState.putString("app_state", mAppState);    return appState;}

這樣,整個通訊流程差不多就到此完整了。

總結

Javascript請求Native再回調到Javascript中,一共經曆了如下流程:

一共Javascript->Bridge->Native->Bridge->Javascript五個步驟,callbackID是整個流程的關鍵點。

Javascript請求Native,需要先產生callbackID,並以callbackID為唯一鍵儲存回呼函數。callbackID作為上一次通訊請求的應答內容傳到Native端,Native接收到後通過反射NativeModule的處理方法,然後將callbackID及處理結果返給Javascript端,Javascript使用callbackID擷取到儲存的回調方法,然後執行。

流程圖表示如下:

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.