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

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

編輯:關於android開發

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


前一篇博客分析了Native端向Javascript端通信的全流程,這次來研究下Javascript端向Native端通信的全流程,與前篇恰好構成一個基本完整的通信機制。

本篇博客內容與前篇聯系較大,有些分析過的東西這次就直接拿來用了,不再贅述,所以希望閱讀這篇文章之前先熟悉下前篇:
React-Native系列Android——Native與Javascript通信原理(一)
 

引用下前篇中的通信模型:
\

NativeJavascript之間的雙向通信其實是你來我往的一個循環過程,就好像是兩個人在對話,你一句我一句然後你再一句我再一句。那麼總有一個會話的發起者吧?當然是Native了,因為所有的行為都是從Native端發起的,用戶操作直接面向的也是Native。所以這個通信模型又可以看成是Native發起會話,然後Javascript進行應答。

所以,今天的博文重點就是分析Javascript是如何應答Native,同時Native又是如何處理來自Javascript的應答的。


1、Javascript的應答

還記得前篇Bridge層章節中JSCExecutor::callFunction最後有一個callNativeModules的調用嗎?忘記了不要緊,再來回顧下jni/react/JSCExecutor.cpp中的這段代碼吧!

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. #9317773
  std::vector 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& 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();
}

executeJSCallWithJSC方法執行Javascript最終腳本後返回了一個名為callsJSON串,這個字符串又被塞進了Bridge.cppcallNativeModules方法,這個方法大家都能根據字面意思猜測到是調用Native端組件的,那麼這個JSON串內容就是來自Javascript的應答內容了。

callNativeModules方法及裡面的細節我們暫時放一放,先來看看這個Javascript的應答內容裡面到底是些神馬東西!

前篇裡面,被WebKit庫執行的Javascript語句,大家應該還記得吧,最終如下:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);

再次看一下MessageQueue.jscallFunctionReturnFlushedQueue方法內容:

callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }
  flushedQueue() {
    this.__callImmediates();

    let queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

返回的是flushedQueue()方法,而flushedQueue()返回的是this._queue數組或者null

這裡有個小細節,flushedQueue()並不是直接返回this._queue的,而是新定義了一個局部變量queue,先將this._queue的值賦給queue用於返回,然後又清空數組內容。這種處理方式,說明了this._queue數組是專門存放應答Native端內容的,每次應答之後都會置空然後等待下一次的會話到來。

那麼,問題來了!應答Native端的內容是如何被放進this._queue數組裡面的呢?

有點抽象,也有點玄乎了,我們不妨結合一下場景分析:假設用戶點擊了文本,然後手機彈出一個Toast,這個Toast其實就是來自Javascript的應答,如果用React-Native代碼寫出來應該是這樣:

var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);

當然,彈這個Toast效果是需要Native端來做了,但是‘Awesome, Clicking!’文案和SHORT,兩個參數是來自Javascript端的,Javascript端會告訴Native端彈一個內容‘Awesome, Clicking!’時長SHORT的Toast,其實就是對用戶點擊這個會話的應答了。

那我麼就以ToastAndroid為例,來分析下Javascript的應答,也就是如何把Awesome, Clicking!’和SHORT兩個參數塞進this._queue數組的。

先來研究一下ToastAndroid的代碼,位於\node_modules\react-native\Libraries\Components\ToastAndroid\ToastAndroid.android.js

'use strict';

var RCTToastAndroid = require('NativeModules').ToastAndroid;

var ToastAndroid = {

  SHORT: RCTToastAndroid.SHORT,
  LONG: RCTToastAndroid.LONG,

  show: function (
    message: string,
    duration: number
  ): void {
    RCTToastAndroid.show(message, duration);
  },

};

module.exports = ToastAndroid;

裡面使用的是RCTToastAndroid,而RCTToastAndroid又是NativeModules裡的一個屬性。所以,這一段Toast的調用代碼等價於:

NativeModules.RCTToastAndroid.show(message, duration);

下面來看看NativeModules吧,代碼位於\node_modules\react-native\Libraries\BatchedBridge\BatchedBridgedModules\NativeModules.js

const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;
...
const NativeModules = {};
...
module.exports = NativeModules;

乍一看,NativeModules對象裡面是空的,並沒有所謂的RCTToastAndroid屬性,但是不要忘了,Javascript是可以通過Object.defineProperty方式定義對象屬性的,也算是其獨門絕技了,仔細閱讀一下NativeModules的代碼,果然找到了一些蛛絲馬跡,我們來看看:

const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    enumerable: true,
    get: () => {
      let module = RemoteModules[moduleName];
      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
        const json = global.nativeRequireModuleConfig(moduleName);
        const config = json && JSON.parse(json);
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      return module;
    },
  });
});

這段代碼的意思是遍歷RemoteModules對象,將其屬性名定義成NativeModules的屬性名,而屬性值通過get方法返回,同時這裡還有一個if語句判斷,作用是如果當前moduleName對象未加載,將初始化一個並存入RemoteModules中供下一次調用。總結一下,這段代碼代碼的作用可以看成是NativeModulesRemoteModules對象的一次拷貝。

那麼,再次還原一下,Toast的調用變成了

RemoteModules.RCTToastAndroid.show(message, duration);

下面,來看看RemoteModules對象裡面具體是由哪些內容。這裡的RemoteModules引用的是BatchedBridge.RemoteModules,也就是MessageQueue.RemoteModules

class MessageQueue {

  constructor(remoteModules, localModules) {
    this.RemoteModules = {};

    ...

    let modulesConfig = this._genModulesConfig(remoteModules);
    this._genModules(modulesConfig);

    ...

  }

RemoteModules是在MessageQueue的構造函數裡面通過_genModules方法初始化數據的。

前篇中我們研究過構造函數中localModules參數的來源,同樣的remoteModules參數也是一個來自Native端的JSON對象,裡面存放著所有NativeModule組件信息,格式如下圖所示:

這裡寫圖片描述

先來看一下_genModulesConfig<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPre9t6g8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> _genModulesConfig(modules /* array or object */) { if (Array.isArray(modules)) { return modules; } else { let moduleArray = []; let moduleNames = Object.keys(modules); for (var i = 0, l = moduleNames.length; i < l; i++) { let moduleName = moduleNames[i]; let moduleConfig = modules[moduleName]; let module = [moduleName]; if (moduleConfig.constants) { module.push(moduleConfig.constants); } let methodsConfig = moduleConfig.methods; if (methodsConfig) { let methods = []; let asyncMethods = []; let methodNames = Object.keys(methodsConfig); for (var j = 0, ll = methodNames.length; j < ll; j++) { let methodName = methodNames[j]; let methodConfig = methodsConfig[methodName]; methods[methodConfig.methodID] = methodName; if (methodConfig.type === MethodTypes.remoteAsync) { asyncMethods.push(methodConfig.methodID); } } if (methods.length) { module.push(methods); if (asyncMethods.length) { module.push(asyncMethods); } } } moduleArray[moduleConfig.moduleID] = module; } return moduleArray; } }

這一段代碼是對remoteModules這個JSON格式對象的處理,生成一個以moduleID為鍵,module數組為值的集合moduleArraymodule數組中按順序存放著:

[moduleName,constants,methods,asyncMethods]

這個moduleArray又被交給_genModules方法做進一步處理:

  _genModules(remoteModules) {
    remoteModules.forEach((config, moduleID) => {
      this._genModule(config, moduleID);
    });
  }
_genModule(config, moduleID) {
    if (!config) {
      return;
    }

    let moduleName, constants, methods, asyncMethods;
    if (moduleHasConstants(config)) {
      [moduleName, constants, methods, asyncMethods] = config;
    } else {
      [moduleName, methods, asyncMethods] = config;
    }

    let module = {};
    methods && methods.forEach((methodName, methodID) => {
      const methodType =
        asyncMethods && arrayContains(asyncMethods, methodID) ?
          MethodTypes.remoteAsync : MethodTypes.remote;
      module[methodName] = this._genMethod(moduleID, methodID, methodType);
    });
    Object.assign(module, constants);

    if (!constants && !methods && !asyncMethods) {
      module.moduleID = moduleID;
    }

    this.RemoteModules[moduleName] = module;
    return module;
  }

代碼比較長,但是不難,遍歷moduleArray集合,生成一個新的module對象,然後賦值給this.RemoteModules[moduleName],最終結果等價於:

methods.forEach((methodName, methodID){
   methods[methodName] = this._genMethod(moduleID, methodID, methodType);
}

this.RemoteModules[moduleName] = {moduleID, constants, methods};

methods裡面每個methodName都通過_genMethod方法被賦值成一個function

  _genMethod(module, method, type) {
    let fn = null;
    let self = this;
    if (type === MethodTypes.remoteAsync) {
      fn = function(...args) {
        return new Promise((resolve, reject) => {
          self.__nativeCall(...);
        });
      };
    } else {
      fn = function(...args) {
         ...
        return self.__nativeCall(...);
      };
    }
    fn.type = type;
    return fn;
  }

通過這個方法,this.RemoteModules再具體化一下,將出現

this.RemoteModules[moduleName][methodName] = __nativeCall(...);

this.RemoteModules.moduleName.methodName = __nativeCall(...);

如果是調用Toast組件,moduleName = RCTToastAndroid,methodName =show,具體化一下,變成了

this.RemoteModules.RCTToastAndroid.show = __nativeCall(...);

在剛剛分析NativeModules的時候,說過Toast調用的執行語句是

RemoteModules.RCTToastAndroid.show(message, duration);

比較上面兩個Javascript語句,show方法說明實際調用的是__nativeCall,由此證明了一個觀點:所有NativeModules組件的調用,最終都是調用的MessageQueue .__nativeCall

仔細想想,這樣一個過程,是不是和前篇所分析的JavascriptModules調用最終是在JavaScriptModuleInvocationHandler裡面統一調callFunction無比相似呢?

現在可以揭開this._queue數組內容之謎了!我們來看__nativeCall

__nativeCall(module, method, params, onFail, onSucc) {

    ...

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

onFailonSucc兩個參數是用於Javascript端接收Native端回調的,如果不需要回調的話這兩個參數都是null,可以忽略(這個回調過程先挖個坑,將會是下一篇博客的內容^-^)。

由於global.nativeFlushQueueImmediate的值是undefined,所以if語句也不會走到,所以__nativeCall中真正有用的代碼就四行,再結合flushedQueue方法分析一下:

__nativeCall(module, method, params, onFail, onSucc) {  
    ...

    this._callID++;

    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(params);
    ...
  }
  flushedQueue() {   
    ...

    let queue = this._queue;
    this._queue = [[], [], [], this._callID];
    return queue[0].length ? queue : null;
  }

_callID自增計數,在flushedQueue中向Native應答後清空this._queue數組時_callID會預先放置到其中,計數下一次會話的應答。

this._queue數組中添加數據時,使用的是push,而不是賦值,說明了this._queue可以接受多個NativeModules的調用數據,然後在一次應答Native的通信中全部傳遞給Native端。

舉個例子,我點擊文本,可以同時執行多個RCTToastAndroid調用,但這個請求會一次性發給Native端,這樣可以提高通信效率。

var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Awesome, Clicking!', ToastAndroid.SHORT);

再回到之前Toast的場景,執行完上面的Javascript語句後,this._queue數組中應該有以下內容了(假設moduleID=0,methodID=0),後面來分析Bridge層的調用。

this._queue = [[0], [0], ['Awesome, Clicking!','SHORT'], this._callID];

2、Bridge層的轉發

this._queue數組被轉成JSON字符串,作為Javascript端的應答被返回給了Bridge層。

回到jni/react/JSCExecutor.cpp中的callFunction方法:

void JSCExecutor::callFunction(...) {
  ...
  std::string calls = executeJSCallWithJSC(...);
  m_bridge->callNativeModules(*this, calls, true);
}

裡面執行的是jni/react/Bridge.cppcallNativeModules:

void Bridge::callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch) {
  if (*m_destroyed) {
    return;
  }
  m_callback->onCallNativeModules(getTokenForExecutor(executor), parseMethodCalls(callJSON), isEndOfBatch);
}

再裡面又是調的m_callbackonCallNativeModules,同時又先將callJSON做了解析。先來看看callJSONparseMethodCalls方法解析之後的樣子吧,代碼位於jni/react/MethodCall.cpp

#define REQUEST_MODULE_IDS 0
#define REQUEST_METHOD_IDS 1
#define REQUEST_PARAMSS 2
#define REQUEST_CALLID 3

std::vector parseMethodCalls(const std::string& json) {

  folly::dynamic jsonData = folly::parseJson(json);

  ...

  auto moduleIds = jsonData[REQUEST_MODULE_IDS];
  auto methodIds = jsonData[REQUEST_METHOD_IDS];
  auto params = jsonData[REQUEST_PARAMSS];
  int  callId = -1;

  ...

  if (jsonData.size() > REQUEST_CALLID) {
    if (!jsonData[REQUEST_CALLID].isInt()) {
      ...
    } else {
      callId = jsonData[REQUEST_CALLID].getInt();
    }
  }

  std::vector methodCalls;
  for (size_t i = 0; i < moduleIds.size(); i++) {
    auto paramsValue = params[i];
    ...
  }

    methodCalls.emplace_back(moduleIds[i].getInt(), methodIds[i].getInt(), std::move(params[i]), callId);

    callId += (callId != -1) ? 1 : 0;
  }

  return methodCalls;
}

代碼和Javascriptthis._queue數組存儲的時候非常相似,只不過這邊是讀取,然後封裝成一個MethodCall對象。

回到m_callback->onCallNativeModules那段代碼中,Bridge.cppm_callback對象是BridgeCallback的實例,在其構造函數中傳入。而Bridge是在OnLoad.cpp中實例化的。

static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback, jobject callbackQueueThread) {
  auto weakCallback = createNew(callback);
  auto weakCallbackQueueThread = createNew(callbackQueueThread);
  auto bridgeCallback = folly::make_unique(weakCallback, weakCallbackQueueThread);
  auto nativeExecutorFactory = extractRefPtr(env, executor);
  auto executorTokenFactory = folly::make_unique();
  auto bridge = createNew(nativeExecutorFactory.get(), std::move(executorTokenFactory), std::move(bridgeCallback));
  setCountableForJava(env, obj, std::move(bridge));
}

這裡m_callback真正的引用是PlatformBridgeCallback,它是BridgeCallback的子類,所以來看PlatformBridgeCallbackonCallNativeModules方法。

class PlatformBridgeCallback : public BridgeCallback {
public:
  PlatformBridgeCallback(
      RefPtr weakCallback_,
      RefPtr weakCallbackQueueThread_) :
    weakCallback_(std::move(weakCallback_)),
    weakCallbackQueueThread_(std::move(weakCallbackQueueThread_)) {}

  ...

  virtual void onCallNativeModules(ExecutorToken executorToken, std::vector&& calls, bool isEndOfBatch) override {
    executeCallbackOnCallbackQueueThread([executorToken, calls, isEndOfBatch] (ResolvedWeakReference& callback) {
      JNIEnv* env = Environment::current();
      for (auto& call : calls) {
        makeJavaCall(env, executorToken, callback, call);
        if (env->ExceptionCheck()) {
          return;
        }
      }
      if (isEndOfBatch) {
        signalBatchComplete(env, callback);
      }
    });
  }
  ...
private:
  RefPtr weakCallback_;
  RefPtr weakCallbackQueueThread_;
};

onCallNativeModules方法調用的是executeCallbackOnCallbackQueueThread,字面意思是在回調隊列線程中執行回調,被執行的回調方法裡面對calls進行遍歷,然後分別執行makeJavaCall(前面提過Javascript會將多個執行結果放到一次應答通信中回調給Native)。

executeCallbackOnCallbackQueueThread方法裡面會創建一個JavaRunnable,然後將其塞入隊列中enqueueNativeRunnableOnQueue,當Runnable回調被執行時,也就是上面的makeJavaCall被遍歷執行了。

這個所謂的executeCallbackOnCallbackQueueThreadPlatformBridgeCallback的兩個構造參數weakCallback_weakCallbackQueueThread_來執行的,這兩個參數對象又是由Java層構建,傳到create方法,又傳給PlatformBridgeCallback對象。

registerNatives("com/facebook/react/bridge/ReactBridge", {
        makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/JavaScriptExecutor;Lcom/facebook/react/bridge/ReactCallback;Lcom/facebook/react/bridge/queue/MessageQueueThread;)V", bridge::create),
        ...
    });

上面代碼可以看出:
weakCallback_參數是com.facebook.react.bridge.ReactCallback的實例;
weakCallbackQueueThread_參數是com.facebook.react.bridge.queue.MessageQueueThread的實例;
兩者都是通過com.facebook.react.bridge.ReactBridge通過initialize本地方法傳入的。

再來看ReactCallback的回調裡面makeJavaCall方法裡面干了啥。

static void makeJavaCall(JNIEnv* env, ExecutorToken executorToken, jobject callback, const MethodCall& call) {
  if (call.arguments.isNull()) {
    return;
  }

  ...

  auto newArray = ReadableNativeArray::newObjectCxxArgs(std::move(call.arguments));
  env->CallVoidMethod(
      callback,
      gCallbackMethod,
      static_cast(executorToken.getPlatformExecutorToken().get())->getJobj(),
      call.moduleId,
      call.methodId,
      newArray.get());
}
jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
    bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call", "(Lcom/facebook/react/bridge/ExecutorToken;IILcom/facebook/react/bridge/ReadableNativeArray;)V");

這一連串的jni調用結果就是,來自Javascript層的moduleId、methodId、args,被調用到Java層的ReactCallbackcall方法裡面,Bridge層的流程也就到此結束了。

當然,onCallNativeModules方法裡面最後還有一個signalBatchComplete方法,也是ReactCallback.java的回調,意圖是告訴Native端,從Native->Javascript->Native一次完整的通信結束。

static void signalBatchComplete(JNIEnv* env, jobject callback) {
  env->CallVoidMethod(callback, gOnBatchCompleteMethod);
}
jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
bridge::gOnBatchCompleteMethod = env->GetMethodID(callbackClass, "onBatchComplete", "()V");

Bridge層的邏輯其實非常簡單,都是簡單的調用Java層的對象,下面就開始分析Java層的處理邏輯了,聰明些的同學可能已經猜測到具體流程了!


3、Java層的接收

人類向宇宙深處發射無線電波,經歷了無數個歲月終於接收到其他文明的回應了,NativeJavascript通信其實也是一個非常相似的過程,作為Native端的接收者ReactCallback,到底做了什麼呢?

首先,來瞧瞧Java層的ReactCallback對象是怎樣創建的,先來看下CatalystInstanceImplReactBridge初始化的過程:

public class CatalystInstanceImpl implements CatalystInstance {
   ...

  private ReactBridge initializeBridge(JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) {
    ...
    ReactBridge bridge;
    try {
      bridge = new ReactBridge(jsExecutor, new NativeModulesReactCallback(), mReactQueueConfiguration.getNativeModulesQueueThread());
    } finally {
        ...
    }

    ...

    return bridge;
  }

   ...
}
public class ReactBridge extends Countable {
   ...

   public ReactBridge(JavaScriptExecutor jsExecutor, ReactCallback callback, MessageQueueThread nativeModulesQueueThread) {
    mJSExecutor = jsExecutor;
    mCallback = callback;
    mNativeModulesQueueThread = nativeModulesQueueThread;
    initialize(jsExecutor, callback, mNativeModulesQueueThread);
  }

  private native void initialize(JavaScriptExecutor jsExecutor, ReactCallback callback, MessageQueueThread nativeModulesQueueThread);

   ...
}

ReactBridge構造方法裡面,調用initialize這個native方法,將創建的NativeModulesReactCallback對象傳到了JNI層,而NativeModulesReactCallback又是ReactCallback的直接子類,所以JNI層調用的ReactCallback其實就是NativeModulesReactCallback對象了。

jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
    bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call", "(Lcom/facebook/react/bridge/ExecutorToken;IILcom/facebook/react/bridge/ReadableNativeArray;)V");

NativeModulesReactCallbackCatalystInstanceImpl的一個內部類,實現了ReactCallback的兩個抽象方法:callonBatchComplete

onBatchComplete是用來通知Native->Javascript->Native的一次雙向通信完成的,通知到各個監聽器,處理一些特殊邏輯,比如視圖刷新之類,這個過程與本文主題無關,就暫不深入研究了。

call是接收Javascript端應答的,我們來分析一下:

private class NativeModulesReactCallback implements ReactCallback {

    @Override
    public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
      ...

      mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
    }

    @Override
    public void onBatchComplete() {
      ...
    }
  }

mJavaRegistry指的是NativeModuleRegistry,字面意思就是Native組件注冊表,call方法參數除了CatalystInstanceImpl外,還有來自Javascript端的moduleId, methodId, parameters三個。

不出所料的話,需要在NativeModuleRegistry注冊表裡面,通過moduleId匹配到注冊的Native組件,再通過methodId匹配到組件的方法,然後執行parameters

至於為什麼能夠匹配上,別忘了,無論JavascriptModule還是NativeModule,所有的moduleIdmethodId,都是通過Native端的ReactBridgesetGlobalVariable方法傳遞到Javascript端的,這個邏輯在前篇中已經重點研究過了!

NativeModuleRegistrycall方法很簡單,在com.facebook.react.bridge包下:

public class NativeModuleRegistry {
   ...

   private final List mModuleTable;
   private final Map, NativeModule> mModuleInstances;

  void call(CatalystInstance catalystInstance, int moduleId, int methodId, ReadableNativeArray parameters) {
    ModuleDefinition definition = mModuleTable.get(moduleId);
    if (definition == null) {
      throw new RuntimeException("Call to unknown module: " + moduleId);
    }
    definition.call(catalystInstance, methodId, parameters);
  }

   ...

}

MessageQueue.js中保存著JavascriptModule的映射表一樣,NativeModuleRegistry 中也保持著NativeModule的映射表,名為mModuleTable

NativeModule注冊的過程中,會生成代表自身且唯一的moduleID,同時其內部所有public方法也會生成唯一的methodID,這些信息都保存在一個名叫ModuleDefinition的對象中,最終走的也是它的call方法,而ModuleDefinition則是NativeModuleRegistry的一個內部類,代碼如下:

  private static class ModuleDefinition {
    public final int id;
    public final String name;
    public final NativeModule target;
    public final ArrayList methods;

    public ModuleDefinition(int id, String name, NativeModule target) {
      this.id = id;
      this.name = name;
      this.target = target;
      this.methods = new ArrayList();

      for (Map.Entry entry : target.getMethods().entrySet()) {
        this.methods.add(new MethodRegistration(entry.getKey(), "NativeCall__" + target.getName() + "_" + entry.getKey(), entry.getValue()));
      }
    }

    public void call(CatalystInstance catalystInstance, int methodId, ReadableNativeArray parameters) {
      MethodRegistration method = this.methods.get(methodId);
      ...
      try {
        this.methods.get(methodId).method.invoke(catalystInstance, parameters);
      } finally {
          ...
      }
    }
  }

ModuleDefinitioncall方法裡面寫得有點不嚴謹,不過無關緊要,走的是MethodRegistrationmethod成員變量的invoke方法。

ModuleDefinitionNativeModule內方法信息的封裝類,代碼也在NativeModuleRegistry中:

  private static class MethodRegistration {
    public MethodRegistration(String name, String tracingName, NativeModule.NativeMethod method) {
      this.name = name;
      this.tracingName = tracingName;
      this.method = method;
    }

    public String name;
    public String tracingName;
    public NativeModule.NativeMethod method;
  }

其內部method成員變量是NativeModule.NativeMethod對象,真正的實現則是JavaMethod類,後者是BaseJavaModule的內部類。而BaseJavaModuleNativeModule的抽象實現,所以所有的Native組件類都是其直接或間接子類,比如我們常用的ToastModule就是它的一個間接子類!

接下來,我們來看JavaMethodinvoke方法:

private class JavaMethod implements NativeMethod {
    ...

    private Method mMethod;
    private final ArgumentExtractor[] mArgumentExtractors;
    private final Object[] mArguments;

    @Override
    public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters){
       ...
       int i = 0, jsArgumentsConsumed = 0;
        try {
          for (; i < mArgumentExtractors.length; i++) {
            mArguments[i] = mArgumentExtractors[i].extractArgument(catalystInstance, parameters, jsArgumentsConsumed);
            jsArgumentsConsumed +=mArgumentExtractors[i].getJSArgumentsNeeded();
          }
        } catch (UnexpectedNativeTypeException e) {
          throw new NativeArgumentsParseException(
              ...
        }
       mMethod.invoke(BaseJavaModule.this, mArguments);
       ...
   }

   ...
}

這應該是最終的調用了,由於來自Javascript端的args參數,都在JNI層裡被封裝成ReadableNativeArray對象,比如例子中Toast的‘Awesome, Clicking!’文案和時長SHORT,一個是字符串String,一個是整型int,都被封裝在ReadableNativeArray裡,那麼這裡就需要進行提取了。

這裡定義了一個提取器,名為ArgumentExtractor,是個抽象類:

  private static abstract class ArgumentExtractor {
    public int getJSArgumentsNeeded() {
      return 1;
    }

    public abstract @Nullable T extractArgument(
        CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex);
  }

BaseJavaModule內部定義了9種類型的提取器用於處理不同類型的參數,如下表:
ARGUMENT_EXTRACTOR_BOOLEAN
ARGUMENT_EXTRACTOR_DOUBLE
ARGUMENT_EXTRACTOR_FLOAT
ARGUMENT_EXTRACTOR_INTEGER
ARGUMENT_EXTRACTOR_STRING
ARGUMENT_EXTRACTOR_ARRAY
ARGUMENT_EXTRACTOR_MAP
ARGUMENT_EXTRACTOR_CALLBACK
ARGUMENT_EXTRACTOR_PROMISE

最後兩個比較特殊,CallbackPromise類型,後面博文中我會一一分析,這裡順帶提一下。

還是Toast那個例子,顯示文案String類型和顯示時長int類型,都被提取出來了,存放到了mArguments數組中,現在萬事具備只剩東風了,

mMethod.invoke(BaseJavaModule.this, mArguments);

mMethod指被調用的方法,用於反射的java.lang.reflect.Method對象,BaseJavaModule.this指代當前NativeModule對象的實例,如果是Toast組件的話就是ToastModule了,mArguments是參數。invoke反射NativeModule的目標方法,完成Java層的最終調用。

如果是彈Toast,被反射的就是ToastModuleshow方法了:

public class ToastModule extends ReactContextBaseJavaModule {

  private static final String DURATION_SHORT_KEY = "SHORT";
  private static final String DURATION_LONG_KEY = "LONG";

  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  public String getName() {
    return "ToastAndroid";
  }

  ...

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
}

參數message是Awesome, Clicking!’,duration是Toast.LENGTH_SHORT。


4、總結

Javascript端調用Native端,同樣分了三層:JavascriptBridgeJava,概括一下主要流程。

A、Javascript層:
邏輯最為復雜的一層,NativeModules.js組件是向Native端調用的入口,其指向的又是MessageQueueRemoteModules對象,而RemoteModules將所有的method指向了一個function函數,即__nativeCall__nativeCall負責將所有向Native端請求的信息pushthis._queue數組,在應答Native端通信請求的flushedQueue方法內將this._queue返給Bridge

B、Bridge層:
Bridge層接收到Javascript層的應答信息this._queue(一個JSON字符串)後,調用PlatformBridgeCallback對象的onCallNativeModules方法,onCallNativeModules裡面創建了一個Runnable塞到執行Callback的線程隊列中等待回調,回調中執行makeJavaCall方法,裡面最終通過env->CallVoidMethod調用了Java層的方法。

C、Java層:
Bridge層中調起了JavaNativeModulesReactCallbackcall方法,其裡面又是NativeModuleRegistrycall調用。NativeModuleRegistry通過moduleID從保存在其內部的NativeModule映射表,匹配到需要被執行的NativeModule對象,再通過methodID匹配到NativeModule的方法。最後從ReadableNativeArray中提取出參數後通過invoke反射方式執行NativeModule的方法。

全部過程用一張流程圖大概描述如下:
這裡寫圖片描述

Javascript端向Native端通信到此結束,結合前篇,正常情況下NativeJavascript通信機制應該是完整了,但是但是,還缺少了一些東西。不妨想象一下這樣的場景:Javascript想要實時獲取App頁面的生命周期狀態,處在前台運行還是後台運行?這就需要Javascript->Native->Javascript這種機制了,也就是說在Native在接收到Javascript的應答後還應該給Javascript一個反饋,與前篇Native主動調用Javascript不同的是這是一個被動回調的過程。

謝謝閱讀,下篇再見!


 

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