加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-15 03:34:50 所属栏目:百科 来源:网络整理
导读:前一篇博客分析了 Native 端向 Javascript 端通信的全流程,这次来研究下 Javascript 端向 Native 端通信的全流程,与前篇恰好构成一个基本完整的通信机制。 本篇博客内容与前篇联系较大,有些分析过的东西这次就直接拿来用了,不再赘述,所以希望阅读这篇文

前一篇博客分析了Native端向Javascript端通信的全流程,这次来研究下Javascript端向Native端通信的全流程,与前篇恰好构成一个基本完整的通信机制。

本篇博客内容与前篇联系较大,有些分析过的东西这次就直接拿来用了,不再赘述,所以希望阅读这篇文章之前先熟悉下前篇:
React-Native系列Android——Native与Javascript通信原理(一)
http://www.52php.cn/article/p-ahzjgpxg-up.html。

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

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<folly::dynamic> 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<folly::dynamic>& arguments) {

  ...

  // Evaluate script with JSC
  folly::dynamic jsonArgs(arguments.begin(),arguments.end());
  auto js = folly::to<folly::fbstring>(
      "__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,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_modulesreact-nativeLibrariesComponentsToastAndroidToastAndroid.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_modulesreact-nativeLibrariesBatchedBridgeBatchedBridgedModulesNativeModules.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方法

_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,asyncMethods;
    if (moduleHasConstants(config)) {
      [moduleName,asyncMethods] = config;
    } else {
      [moduleName,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,methodType);
}

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

methods里面每个methodName都通过_genMethod方法被赋值成一个function

_genMethod(module,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,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,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,ToastAndroid.SHORT);

再回到之前Toast的场景,执行完上面的Javascript语句后,this._queue数组中应该有以下内容了(假设moduleID=0,methodID=0),后面来分析Bridge层的调用。

this._queue = [[0],[0],['Awesome,'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,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<MethodCall> 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<MethodCall> 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<WeakReference>(callback);
  auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
  auto bridgeCallback = folly::make_unique<PlatformBridgeCallback>(weakCallback,weakCallbackQueueThread);
  auto nativeExecutorFactory = extractRefPtr<CountableJSExecutorFactory>(env,executor);
  auto executorTokenFactory = folly::make_unique<JExecutorTokenFactory>();
  auto bridge = createNew<CountableBridge>(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<WeakReference> weakCallback_,RefPtr<WeakReference> weakCallbackQueueThread_) :
    weakCallback_(std::move(weakCallback_)),weakCallbackQueueThread_(std::move(weakCallbackQueueThread_)) {}

  ...

  virtual void onCallNativeModules(ExecutorToken executorToken,std::vector<MethodCall>&& calls,bool isEndOfBatch) override {
    executeCallbackOnCallbackQueueThread([executorToken,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<WeakReference> weakCallback_;
  RefPtr<WeakReference> 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,const MethodCall& call) {
  if (call.arguments.isNull()) {
    return;
  }

  ...

  auto newArray = ReadableNativeArray::newObjectCxxArgs(std::move(call.arguments));
  env->CallVoidMethod(
      callback,gCallbackMethod,static_cast<JExecutorTokenHolder*>(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,mNativeModulesQueueThread);
  }

  private native void initialize(JavaScriptExecutor jsExecutor,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,"(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,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<ModuleDefinition> mModuleTable;
   private final Map<Class<? extends NativeModule>,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,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<MethodRegistration> methods;

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

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

    public void call(CatalystInstance catalystInstance,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<T> {
    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不同的是这是一个被动回调的过程。

谢谢阅读,下篇再见!


本博客不定期持续更新,欢迎关注和交流:

http://blog.csdn.net/megatronkings

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读