前一篇博客分析了Native端向Javascript端通信的全流程,这次来研究下Javascript端向Native端通信的全流程,与前篇恰好构成一个基本完整的通信机制。
本篇博客内容与前篇联系较大,有些分析过的东西这次就直接拿来用了,不再赘述,所以希望阅读这篇文章之前先熟悉下前篇:
React-Native系列Android——Native与Javascript通信原理(一)
http://www.jb51.cc/article/p-ahzjgpxg-up.html。
引用下前篇中的通信模型:
Native与Javascript之间的双向通信其实是你来我往的一个循环过程,就好像是两个人在对话,你一句我一句然后你再一句我再一句。那么总有一个会话的发起者吧?当然是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最终脚本后返回了一个名为calls的JSON串,这个字符串又被塞进了Bridge.cpp的callNativeModules方法,这个方法大家都能根据字面意思猜测到是调用Native端组件的,那么这个JSON串内容就是来自Javascript的应答内容了。
callNativeModules方法及里面的细节我们暂时放一放,先来看看这个Javascript的应答内容里面到底是些神马东西!
前篇里面,被WebKit库执行的Javascript语句,大家应该还记得吧,最终如下:
MessageQueue.callFunctionReturnFlushedQueue.apply(null,module,method,args);
再次看一下MessageQueue.js的callFunctionReturnFlushedQueue方法内容:
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_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中供下一次调用。总结一下,这段代码代码的作用可以看成是NativeModules对RemoteModules对象的一次拷贝。
那么,再次还原一下,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数组为值的集合moduleArray,module数组中按顺序存放着:
[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;
}
...
}
onFail和onSucc两个参数是用于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.cpp的callNativeModules:
void Bridge::callNativeModules(JSExecutor& executor,const std::string& callJSON,bool isEndOfBatch) {
if (*m_destroyed) {
return;
}
m_callback->onCallNativeModules(getTokenForExecutor(executor),parseMethodCalls(callJSON),isEndOfBatch);
}
再里面又是调的m_callback的onCallNativeModules,同时又先将callJSON做了解析。先来看看callJSON被parseMethodCalls方法解析之后的样子吧,代码位于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;
}
代码和Javascript中this._queue数组存储的时候非常相似,只不过这边是读取,然后封装成一个MethodCall对象。
回到m_callback->onCallNativeModules那段代码中,Bridge.cpp的m_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的子类,所以来看PlatformBridgeCallback的onCallNativeModules方法。
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方法里面会创建一个Java类Runnable,然后将其塞入队列中enqueueNativeRunnableOnQueue,当Runnable回调被执行时,也就是上面的makeJavaCall被遍历执行了。
这个所谓的executeCallbackOnCallbackQueueThread由PlatformBridgeCallback的两个构造参数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层的ReactCallback的call方法里面,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层的接收
人类向宇宙深处发射无线电波,经历了无数个岁月终于接收到其他文明的回应了,Native与Javascript通信其实也是一个非常相似的过程,作为Native端的接收者ReactCallback,到底做了什么呢?
首先,来瞧瞧Java层的ReactCallback对象是怎样创建的,先来看下CatalystInstanceImpl中ReactBridge初始化的过程:
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");
NativeModulesReactCallback是CatalystInstanceImpl的一个内部类,实现了ReactCallback的两个抽象方法:call和onBatchComplete。
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,所有的moduleId和 methodId,都是通过Native端的ReactBridge的setGlobalVariable方法传递到Javascript端的,这个逻辑在前篇中已经重点研究过了!
NativeModuleRegistry的call方法很简单,在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 {
...
}
}
}
ModuleDefinition的call方法里面写得有点不严谨,不过无关紧要,走的是MethodRegistration的method成员变量的invoke方法。
ModuleDefinition是NativeModule内方法信息的封装类,代码也在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的内部类。而BaseJavaModule是NativeModule的抽象实现,所以所有的Native组件类都是其直接或间接子类,比如我们常用的ToastModule就是它的一个间接子类!
接下来,我们来看JavaMethod的invoke方法:
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
最后两个比较特殊,Callback和Promise类型,后面博文中我会一一分析,这里顺带提一下。
还是Toast那个例子,显示文案String类型和显示时长int类型,都被提取出来了,存放到了mArguments数组中,现在万事具备只剩东风了,
mMethod.invoke(BaseJavaModule.this,mArguments);
mMethod指被调用的方法,用于反射的java.lang.reflect.Method对象,BaseJavaModule.this指代当前NativeModule对象的实例,如果是Toast组件的话就是ToastModule了,mArguments是参数。invoke反射NativeModule的目标方法,完成Java层的最终调用。
如果是弹Toast,被反射的就是ToastModule的show方法了:
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端,同样分了三层:Javascript、Bridge、Java,概括一下主要流程。
A、Javascript层:
逻辑最为复杂的一层,NativeModules.js组件是向Native端调用的入口,其指向的又是MessageQueue的RemoteModules对象,而RemoteModules将所有的method指向了一个function函数,即__nativeCall。__nativeCall负责将所有向Native端请求的信息push进this._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层中调起了Java层NativeModulesReactCallback的call方法,其里面又是NativeModuleRegistry的call调用。NativeModuleRegistry通过moduleID从保存在其内部的NativeModule映射表,匹配到需要被执行的NativeModule对象,再通过methodID匹配到NativeModule的方法。最后从ReadableNativeArray中提取出参数后通过invoke反射方式执行NativeModule的方法。
全部过程用一张流程图大概描述如下:
Javascript端向Native端通信到此结束,结合前篇,正常情况下Native与Javascript通信机制应该是完整了,但是但是,还缺少了一些东西。不妨想象一下这样的场景:Javascript想要实时获取App页面的生命周期状态,处在前台运行还是后台运行?这就需要Javascript->Native->Javascript这种机制了,也就是说在Native在接收到Javascript的应答后还应该给Javascript一个反馈,与前篇Native主动调用Javascript不同的是这是一个被动回调的过程。
谢谢阅读,下篇再见!
本博客不定期持续更新,欢迎关注和交流: