Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> React-Native系列Android——Native與Javascript通信原理(三)

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

編輯:關於Android編程

前面兩篇博客,詳細分析了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獲取到存儲的回調方法,然後執行。

流程圖表示如下:
\


 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved