React-Native系列Android——Native与Javascript通信原理(二)
前一篇博客分析了Native端向Javascript端通信的全流程,这次来研究下Javascript端向Native端通信的全流程,与前篇恰好构成一个基本完整的通信机制。
本篇博客内容与前篇联系较大,有些分析过的东西这次就直接拿来用了,不再赘述,所以希望阅读这篇文章之前先熟悉下前篇:
React-Native系列Android——Native与Javascript通信原理(一)
引用下前篇中的通信模型:
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 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最终脚本后返回了一个名为calls的JSON串,这个字符串又被塞进了Bridge.cpp的callNativeModules方法,这个方法大家都能根据字面意思猜测到是调用Native端组件的,那么这个JSON串内容就是来自Javascript的应答内容了。
callNativeModules方法及里面的细节我们暂时放一放,先来看看这个Javascript的应答内容里面到底是些神马东西!
前篇里面,被WebKit库执行的Javascript语句,大家应该还记得吧,最终如下:
MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);
再次看一下MessageQueue.js的callFunctionReturnFlushedQueue方法内容:
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;
},
});
});
这段代