1.ReactNative源码分析 - 概述
2.ReactNative源码分析 - JavaScriptCore C语言篇
3.ReactNative源码分析 - 启动流程
4.ReactNative源码分析 - 通信机制
5.ReactNative源码分析 - 渲染原理
- 0、前言
- 一、Native&JS底层通信桥如何搭建
- 1.原生端底层通信桥搭建
- 2.原生模块信息如何导入到JS端
- 3.JS端底层通信桥搭建
- 二、Native call JS
- 三、JS call Native
0、前言
- 本文主要分析ReactNative框架中Native&JS的通信机制,主要包括以下三个部分:
- 1.Native&JS底层通信桥如何搭建;
- 2.JS call Native调用流程;
- 3.Native call JS调用流程;
通信机制的分析贯穿ReactNative框架:JS层 》 C++公共层 》OC层,函数调用流程相对较长。
一、Native&JS底层通信桥如何搭建
1.原生端底层通信桥搭建
Native&JS交互最终通过JSCRuntime来实现,基本原理在JavaScriptCore C语言篇分析过。
JSCRuntime中Native&JS交互都是在JS Context全局对象global上做文章
:原生端给global对象设置属性供JS端使用,JS端给global对象设置属性供原生端使用。global对象是最根本的桥接对象。-
启动流程篇在最后执行js bundle代码之前,先向JS端(global对象)注入原生对象作为交互代理,作用分别是:
- 1.原生模块信息代理nativeModuleProxy,用于JS端获取原生模块信息。
- 2.原生函数调用代理nativeFlushQueueImmediate,用于JS端调用原生模块函数。
- 3.原生“同步”函数调用代理nativeCallSyncHook,用于JS端同步调用原生模块函数(同步函数几乎不会用到,整个ReactNative框架只用到一次,不用关注)
1.原生模块信息代理nativeModuleProxy
向JS端注入原生模块信息代理nativeModuleProxy
。它关联原生对象(结构体)HostObjectProxy
,JS端获取原生模块信息是,触发取值回调,最终会触发NativeModuleProxy
获取原生模块信息
// JSIExecutor.cpp
void JSIExecutor::loadApplicationScript(...) {
// 1.向JS端注入原生模块代理NativeModuleProxy
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(*this)));
....
}
// JSCRuntime.cpp
jsi::Object JSCRuntime::createObject(std::shared_ptr<jsi::HostObject> ho) {
// 原生模块代理
struct HostObjectProxy : public detail::HostObjectProxyBase {
// 取值回调
static JSValueRef getProperty(JSContextRef ctx,JSObjectRef object,JSStringRef propertyName,JSValueRef* exception) {
auto proxy = static_cast<HostObjectProxy*>(JSObjectGetPrivate(object));
auto& rt = proxy->runtime;
jsi::PropNameID sym = rt.createPropNameID(propertyName);
jsi::Value ret;
try {
// 取值(获取原生模块信息)
ret = proxy->hostObject->get(rt, sym);
} catch (const jsi::JSError& error) {
...
}
return rt.valueRef(ret);
}
static bool setProperty(...) { ... }
static void finalize(JSObjectRef obj) { ... }
};
// 构建hostObjectClass
std::call_once(hostObjectClassOnceFlag, []() {
JSClassDefinition hostObjectClassDef = kJSClassDefinitionEmpty;
...
hostObjectClassDef.getProperty = HostObjectProxy::getProperty;
hostObjectClass = JSClassCreate(&hostObjectClassDef);
});
// 创建JS对象,关联原生对象HostObjectProxy
JSObjectRef obj =
JSObjectMake(ctx_, hostObjectClass, new HostObjectProxy(*this, ho));
return createObject(obj);
}
-
2.原生函数调用代理nativeFlushQueueImmediate
向JS端注入原生函数调用代理nativeFlushQueueImmediate
,它关联原生对象HostFunctionMetadata
,JS端发起JS call Native会触发回调函数,最终触发HostFunctionType hostFunction_
发起函数调用
// JSIExecutor.cpp
void JSIExecutor::loadApplicationScript(...) {
...
// 2.向JS端注入原生函数调用代理
runtime_->global().setProperty(
*runtime_,
"nativeFlushQueueImmediate",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
1,
[this](jsi::Runtime&, const jsi::Value&, const jsi::Value* args, size_t count) {
// 调用原生模块函数(批量)
callNativeModules(args[0], false);
return Value::undefined();
}));
....
}
// JSCRuntime.cpp
jsi::Function JSCRuntime::createFunctionFromHostFunction(
const jsi::PropNameID& name,
unsigned int paramCount,
jsi::HostFunctionType func) {
// 原生函数调用代理
class HostFunctionMetadata : public HostFunctionProxy {
public:
static void initialize(JSContextRef ctx, JSObjectRef object) { ... }
// 函数调用回调
static JSValueRef call(JSContextRef ctx,JSObjectRef function,JSObjectRef thisObject,size_t argumentCount,const JSValueRef arguments[],JSValueRef* exception) {
HostFunctionMetadata* metadata =
static_cast<HostFunctionMetadata*>(JSObjectGetPrivate(function));
JSCRuntime& rt = *(metadata->runtime);
...
JSValueRef res;
jsi::Value thisVal(rt.createObject(thisObject));
try {
// hostFunction_() 调用关联对象函数
res = rt.valueRef(
metadata->hostFunction_(rt, thisVal, args, argumentCount));
} catch (...) { ... }
return res;
}
HostFunctionMetadata(JSCRuntime* rt,jsi::HostFunctionType hf,unsigned ac,JSStringRef n)
: HostFunctionProxy(hf),runtime(rt),argCount(ac),name(JSStringRetain(n)){}
JSCRuntime* runtime;
unsigned argCount;
JSStringRef name;
};
std::call_once(hostFunctionClassOnceFlag, []() {
JSClassDefinition functionClass = kJSClassDefinitionEmpty;
...
functionClass.callAsFunction = HostFunctionMetadata::call;
hostFunctionClass = JSClassCreate(&functionClass);
});
// 创建JS对象,关联原生对象HostFunctionMetadata
JSObjectRef funcRef = JSObjectMake(
ctx_,
hostFunctionClass,
new HostFunctionMetadata(this, func, paramCount, stringRef(name)));
return createObject(funcRef).getFunction(*this);
}
2.原生模块信息如何导入到JS端
JS端批处理桥的代码在react-native/Libraries/BatchedBridge
目录下。下面基于Native&JS底层Bridge、NativeModules,从使用端(JS端)开始分析原生模块信息导入JS端的完整流程。使用例子TestManager(DEMO)
- 1.JS端业务层使用NativeModules获取原生模块TestManager相关信息
const testManager = NativeModules.TestManager;
- 2.JS端获取原生模块信息通过
NativeModules.js
,它主要负责两件事情:- 1.生成原生模块信息(详见下文步骤4);
- 2.获取原生模块信息。
NativeModules
即原生端注入Js端的的原生模块信息代理nativeModuleProxy
,NativeModules.TestManager
会触发HostObjectProxy
取值回调getProperty
,最终通过NativeModuleProxy
获取原生模块TestManager
相关信息。
// NativeModules.js
// 设置原生模块信息生成函数genModule,供原生端调用生成原生模块
global.__fbGenNativeModule = genModule;
// 获取原生模块(nativeModuleProxy是注入JS环境的原生对象)
let NativeModules: {[moduleName: string]: Object} = {};
NativeModules = global.nativeModuleProxy;
// JSCRuntime.cpp
struct HostObjectProxy : public detail::HostObjectProxyBase {
// 取值回调
static JSValueRef getProperty(...) {
...
// 取值(获取原生模块)
ret = proxy->hostObject->get(rt, sym);
return rt.valueRef(ret);
}
}
- 3.取值回调使用负责导出原生模块的代理
hostObject
(即NativeModuleProxy
)来获取原生模块信息
class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
public:
NativeModuleProxy(JSIExecutor& executor) : executor_(executor) {}
// 获取原生模块, name:原生模块名
Value get(Runtime& rt, const PropNameID& name) override {
return executor_.nativeModules_.getModule(rt, name);
}
};
- 4.
NativeModuleProxy
进一步通过JSINativeModules
来获取。JSINativeModules
主要负责:创建并持有导入JS端最终版原生模块信息。
它采用懒加载机制:首次获取某个原生模块信息需先创建并缓存(m_objects);非首次获取则直接取缓存数据,并就原路返回JS端,流程完毕。
注:由此可见原生模块信息最终版并不在JS端缓存,而是缓存在原生端,每次执行NativeModules.XXXX
都会触发JS端去原生端获取。ReactNative框架JS端封装原生模块通常只执行一次记录为全局数据缓存起来,避免重复经过Bridge做数据传递。
// JSINativeModules.cpp
// 根据模块名称,获取原生模块
Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
std::string moduleName = name.utf8(rt);
// 懒加载
const auto it = m_objects.find(moduleName);
if (it != m_objects.end()) {
return Value(rt, it->second);
}
// 首次获取时创建、缓存
auto module = createModule(rt, moduleName);
auto result =
m_objects.emplace(std::move(moduleName), std::move(*module)).first;
return Value(rt, result->second);
}
生成在JS端使用的最终版原生模块信息,需要两个步骤:
- 1.通过原生模块信息注册机ModuleRegistry
生成C++版本的原生模块信息;
- 2.使用JS端导入的__fbGenNativeModule
函数(即genModule
函数)生成最终版。这个设计非常灵活:原生端传入基本的原生模块数据,最终由JS端(使用者)来决定如何生成。
-
注:ReactNative注入JS环境供两端调用的函数,命名规则是:
JS导出供Native使用的属性都是以“__fb”开头,Native导出供JS使用则以“native”开头
。
// NativeModules.js
// 设置原生模块信息生成函数genModule,供原生端调用生成原生模块
global.__fbGenNativeModule = genModule;
// JSINativeModules.cpp
folly::Optional<Object> JSINativeModules::createModule(
Runtime& rt,
const std::string& name)
{
// JS端导入的原生模块生成函数
if (!m_genNativeModuleJS) {
m_genNativeModuleJS =
rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule");
}
// 1.生成C++版本原生模块信息
auto result = m_moduleRegistry->getConfig(name);
// 2.调用js端注入的工具函数,传入C++版本模块配置信息,生成JS端模块信息
Value moduleInfo = m_genNativeModuleJS->call(
rt,
valueFromDynamic(rt, result->config),
static_cast<double>(result->index));
// 取出module,构造为js对象
folly::Optional<Object> module(
moduleInfo.asObject(rt).getPropertyAsObject(rt, "module"));
return module;
}
- 5.生成C++版本的原生模块信息,是数组形式
[name, constants, methodNames, promiseMethodIds, syncMethodIds]
。
methodId即函数在数组中的索引index,ReactNative JS call Native都是通过id(moduleId、methodId)来找到对象的模块、函数。- name:string,模块名称
- constants:object,导出常量 {常量名:常量值, ... }
- methodNames:array[string],方法js名称集合
- promiseMethodIds:array[int],promise函数id(methodId)集合。
- syncMethodIds:array[int],同步函数id集合
此处使用了facebook的开源库folly
,它扩展了C++的动态能力,使之能和动态化JavaScript无缝衔接。构建了哈希表modulesByName_存放<模块名:模块id>,应该是为了提高查找效率。
// ModuleRegistry.cpp
folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
// 初始化原生模块哈希表modulesByName_ <模块名, 模块id>
if (modulesByName_.empty() && !modules_.empty()) {
moduleNames();
}
// 根据原生模块名,查找模块索引 index
auto it = modulesByName_.find(name);
size_t index = it->second;
// 获取原生模块RCTNativeModule
NativeModule *module = modules_[index].get();
// 构建包含原生模块信息(常量、函数)的动态数组
folly::dynamic config = folly::dynamic::array(name);
config.push_back(module->getConstants());
{
std::vector<MethodDescriptor> methods = module->getMethods();
folly::dynamic methodNames = folly::dynamic::array;
folly::dynamic promiseMethodIds = folly::dynamic::array;
folly::dynamic syncMethodIds = folly::dynamic::array;
for (auto& descriptor : methods) {
methodNames.push_back(std::move(descriptor.name));
if (descriptor.type == "promise") {
promiseMethodIds.push_back(methodNames.size() - 1); // 索引(id)
} else if (descriptor.type == "sync") {
syncMethodIds.push_back(methodNames.size() - 1);
}
}
if (!methodNames.empty()) {
config.push_back(std::move(methodNames));
if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
config.push_back(std::move(promiseMethodIds));
if (!syncMethodIds.empty()) {
config.push_back(std::move(syncMethodIds));
}
}
}
}
return ModuleConfig{index, config};
}
例子TestManager,导出函数、常量,和最终生成的C++版本原生模块如下
// FYRNTestManager.m
RCT_EXPORT_MODULE(TestManager)
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSNumber *)secondsSinceUnixEpoch) { ... }
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) { ... }
RCT_REMAP_METHOD(findEventsWithResolver, findEvents:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { ... }
- (NSDictionary *)constantsToExport {
return @{
@"age" : @18,
@"name" : @"fyfy",
@"tag" : @"Handsome" };
}
// 生成C++版本原生模块信息
[
'TestManager',
{ name: 'fyfy', tag: 'Handsome', age: 18 },
[ 'addEvent', 'findEvents', 'findEventsWithResolver' ],
[ 2 ]
]
- 6.JS端函数
genModule
跟局传入的C++版本原生模块信息来生成最终版。- 根据导出函数信息,生成对应的JS版函数。
genMethod
生成的JS函数包装JS call Native,同步函数是直接发起原生函数调用,其他类型函数则调用BatchedBridge.enqueueNativeCall
把调用信息入队,等待执行。 - 添加原生模块常量
至此,原生模块信息加工完毕,从JS端返回Native端,取出module
内容作为最终结果,再原路JS端。JS端可以愉快地调用导出原生模块函数、使用原生模块常量了。
- 根据导出函数信息,生成对应的JS版函数。
// NativeModules.js
// 生成原生模块信息
function genModule( config: ?ModuleConfig, moduleID: number,): ?{name: string, module?: Object} {
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
const module = {};
// 添加 JS版原生模块函数
methods &&
methods.forEach((methodName, methodID) => { // methodID即index
const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
const isSync = syncMethods && arrayContains(syncMethods, methodID);
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
// genMethod 生成函数
module[methodName] = genMethod(moduleID, methodID, methodType);
});
// 添加原生模块导出常量
Object.assign(module, constants);
module.getConstants = () => constants;
const result = {name: moduleName, module};
return result;
}
// 生成函数
genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
if (type === 'promise') {
fn = function(...args: Array<any>) {
return new Promise((resolve, reject) => {
// 函数入队
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData => reject(createErrorFromErrorData(errorData)),
);
});
};
} else if (type === 'sync') {
fn = function(...args: Array<any>) {
return global.nativeCallSyncHook(moduleID, methodID, args);
};
} else {
fn = function(...args: Array<any>) {
...
BatchedBridge.enqueueNativeCall(...);
};
}
fn.type = type;
return fn;
}
例子TestManager最终生成的JS端原生模块最终版如下,原生端会取出module
,传递到JS端使用。
{
name: 'TestManager',
module: {
addEvent: { [Function: fn] type: 'async' }, // addEvent 函数类型,新增type属性
findEvents: { [Function: fn] type: 'async' },
findEventsWithResolver: { [Function: fn] type: 'promise' },
name: 'fyfy',
tag: 'Handsome',
age: 18,
getConstants: [Function]
}
}
3.JS端底层通信桥搭建
-
BatchedBridge:即批处理桥,JS call Native并非调用一次执行一次,而是把调用操作暂存,在适当的时机批量进行调用。
BatchedBridge即MessageQueue对象,以属性名__fbBatchedBridge
注入JS环境全局对象global
,供原生端使用。
// BatchedBridge.js
const BatchedBridge = new MessageQueue();
Object.defineProperty(global, '__fbBatchedBridge', {
configurable: true,
value: BatchedBridge,
});
- 注:ReactNative框架多处使用批处理思想,常用在调用频繁,无需同步响应的场景,例如JS call Native、渲染流程……
-
MessageQueue:消息队列,JS&Native互调在JS端都是由它管理,从这里可以看出几个要点
- 1.JS模块并不导出到Native端,仅仅是注册到
MessageQueue
中的JS模块表_lazyCallableModules
。
Native call JS都是在原生端通过字符串编码来进行,最终通过callFunctionReturnFlushedQueue
触发。 - 2.JS call Native并非每次调用就立即触发,而是批处理。JS端调用原生模块函数时把调用信息存放在
_queue
,在适当时机触发调用。 - 3.JS call Native的回调,以调用id(_callID)作为key,暂存在
MessageQueue
中的回调表_successCallbacks、_failureCallbacks
,等原生函数执行完毕时发起回调,最终通过invokeCallbackAndReturnFlushedQueue
触发回调。这里使用位运算左移/右移/按位与来区分失败、成功回调。
- 1.JS模块并不导出到Native端,仅仅是注册到
// MessageQueue.js
const MIN_TIME_BETWEEN_FLUSHES_MS = 5; // JS call Native批量调用时间间隔 >= 5毫秒
class MessageQueue {
// JS模块表 { 模块名:模块函数集合getter }
_lazyCallableModules: {[key: string]: (void) => Object};
// Native模块函数调用信息暂存表(缓冲区) [[MODULE_IDS], [METHOD_IDS], [PARAMS], _callID], _callID仅需传递1个,原生端自动叠加计算其他callID值
_queue: [number[], number[], any[], number];
// Native函数回调 暂存表 { callID:回调函数 }
_successCallbacks: {[key: number]: ?Function};
_failureCallbacks: {[key: number]: ?Function};
// Native函数调用id,自增
_callID: number;
// 上一次刷新缓冲区的时间
_lastFlush: number;
/////////////////////// Native端使用 //////////////////
// 调用JS函数,返回Native函数调用暂存表
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
this.__callFunction(module, method, args);
return this.flushedQueue();
}
// 执行Native函数对应的JS回调,返回Native函数调用暂存表
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
this.__invokeCallback(cbID, args);
return this.flushedQueue();
}
// 执行JS异步任务,返回Native函数调用暂存表 并清空
flushedQueue() {
this.__callImmediates();
const queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
/////////////////////// JS端使用 //////////////////
// 注册JS模块
registerCallableModule(name: string, module: Object) {
this._lazyCallableModules[name] = () => module;
}
// 注册JS模块(懒加载)
registerLazyCallableModule(name: string, factory: void => Object) {
let module: Object;
let getValue: ?(void) => Object = factory;
this._lazyCallableModules[name] = () => {
if (getValue) {
module = getValue(); // 加载JS模块
getValue = null;
}
return module;
};
}
// 原生函数调用入队,执行
enqueueNativeCall(moduleID: number,methodID: number,params: any[], onFail: ?Function, onSucc: ?Function,
) {
if (onFail || onSucc) {
// 位运算左移对_callID进行编码,作为 失败、成功回调参数值,最后一位用于区分失败回调(0)、成功回调(1)
onFail && params.push(this._callID << 1);
onSucc && params.push((this._callID << 1) | 1);
// 暂存回调
this._successCallbacks[this._callID] = onSucc;
this._failureCallbacks[this._callID] = onFail;
}
// 调用id自增
this._callID++;
// 暂存函数回调函数
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
// 暂存函数调用信息
this._queue[PARAMS].push(params);
// 距上一次批量调用时间间隔大于5ms,则执行调用
// nativeFlushQueueImmediate 原生端注入的调用代理
const now = Date.now();
if (
global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
) {
const queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
}
// JSIExecutor.cpp
void JSIExecutor::bindBridge() {
// 获取js对象MessageQueue
std::call_once(bindFlag_, [this] {
Value batchedBridgeValue =
runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
}
// 绑定JS端注入的函数
Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
*runtime_, "callFunctionReturnFlushedQueue");
invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
*runtime_, "invokeCallbackAndReturnFlushedQueue");
flushedQueue_ =
batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
});
}
总结:
JSIExecutor、MessageQueue是两端交互的核心,通过这两者注入代理对象供另一方调用,以实现Native&JS数据传递、互相调用。
-
JS call Native的触发时机有:
- 1.调用
enqueueNativeCall
函数入队(存入暂存表)时发现距离上一次调用大于5毫秒时,通过nativeFlushQueueImmediate
执行调用; - 2.执行
flushedQueue
时(flushedQueue
用于执行JS端setImmediate异步任务,在此不展开讨论),把原生模块调用信息作为返回值传递到原生端,执行调用; - 3.通过
callFunctionReturnFlushedQueue
执行JS call Native也会触发flushedQueue
,同样返回原生模块调用信息 - 4.通过
invokeCallbackAndReturnFlushedQueue
执行JS回调,同理。
笔者猜想这种设计的目的是:保证能及时发起函数调用的前提下,减少调用频率。毕竟 JS call Native的调用是非常频繁的。
- 1.调用
二、Native call JS
基于上面对Native&JS底层Bridge的分析,下面分析完整的Native call JS流程
- 1.原生端以字符串编码形式,通过
RCTBridge
发起Native call JS。以RCTRootView
运行根组件为例:调用JS模块AppRegistry
函数runApplication
。
// RCTRootView.m
- (void)runApplication:(RCTBridge *)bridge
{
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}
- 2.RCTCxxBridge执行入队函数(module:js模块名,method:js方法名,args:参数, completion:回调)。
这里通过_runAfterLoad
判断js bundle是否运行完毕,完毕则直接使用Instance
继续中转JS call Native;否则先暂存在_pendingCalls
等待完毕信号。
从这里也可以看出Native call JS由JS线程管理。
// RCTCxxBridge.mm
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
[self _runAfterLoad:^(){
// convertIdToFollyDynamic OC转C++动态类型
strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],convertIdToFollyDynamic(args ?: @[]));
...
}];
}
- (void)_runAfterLoad:(RCTPendingCall)block
{
if (self.loading || _pendingCount > 0) {
_pendingCount++;
dispatch_block_t jsQueueBlock = ^{
if (self.loading) {
[self->_pendingCalls addObject:block];
} else {
block();
self->_pendingCount--;
}
};
// JS线程执行
[self ensureOnJavaScriptThread:jsQueueBlock];
} else {
block();
}
}
- 3.
Instance
使用NativeToJsBridge
中转调用
// Instance.cpp
void Instance::callJSFunction(std::string &&module, std::string &&method,
folly::dynamic &¶ms) {
nativeToJsBridge_->callFunction(std::move(module), std::move(method),
std::move(params));
}
- 4.
NativeToJsBridge
使用JSIExecutor
中转调用
// NativeToJsBridge.cpp
void NativeToJsBridge::callFunction(module, method, arguments) {
runOnExecutorQueue([...](JSExecutor* executor) {
executor->callFunction(module, method, arguments);
});
}
- 5.
JSIExecutor
是最终调用者,使用JS端MessageQueue
注入函数callFunctionReturnFlushedQueue
发起JS call Native。 JS call Native会返回JS端暂存的原生模块调用信息,并发起 Native call JS(下文会详细分析)。
// JSIExecutor.cpp
void JSIExecutor::callFunction( moduleId, methodId, arguments) {
// 首次使用触发`bindBridge`绑定;
if (!callFunctionReturnFlushedQueue_) {
bindBridge();
}
// 执行JS模块函数
Value ret = Value::undefined();
ret = callFunctionReturnFlushedQueue_->call(*runtime_, moduleId, methodId, valueFromDynamic(*runtime_, arguments));
// 调用原生模块函数
callNativeModules(ret, true);
}
- 6.
MessageQueue
根据模块id、函数名,在JS模块表_lazyCallableModules
查找到对应模块,发起调用。
// MessageQueue.js
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
// 调用JS模块函数
this.__callFunction(module, method, args);
// 执行JS异步任务,返回Native函数调用暂存表
return this.flushedQueue();
}
// 调用JS函数 module:模块名, method:函数名,args:参数
__callFunction(module: string, method: string, args: any[]): any {
// 获取JS模块 函数集合
const moduleMethods = this.getCallableModule(module);
// 根据函数名method,取出对应函数,进行调用
const result = moduleMethods[method].apply(moduleMethods, args);
return result;
}
// 获取JS模块
getCallableModule(name: string) {
const getValue = this._lazyCallableModules[name];
return getValue ? getValue() : null;
}
该例子中JS模块AppRegistry
事先调用registerCallableModule
把自身注册到BatchedBridge
中的JS模块表。到此流程完毕。
// AppRegistry.js
const AppRegistry = {
// 运行组件
runApplication(appKey: string, appParameters: any): void {
...
runnables[appKey].run(appParameters);
},
}
// 注册JS模块 AppRegistry
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
三、JS call Native
还是用原生模块信息导入JS端的例子TestManager
来分析。这里分析JS端调用原生模块带回调的函数
,其他类型的函数调用可在此基础上自行分析。对比两个图,左边的路线是类似的。
- 1.JS端业务层使用
NativeModules
获取原生模块TestManager
,触发上文原生模块导入JS端流程
,最终获取到原生模块信息。执行带回调的原生模块函数findEvents
import { NativeModules,} from 'react-native';
const manager = NativeModules.TestManager;
manager.findEvents((error, events) => {
if (!error) {
this.setState({events: events});
}
});
- 2.根据上述原生模块信息导出流程,可知
findEvents
函数会调用BatchedBridge.enqueueNativeCall
,把调用信息(模块id、函数id、参数)入队暂存
// NativeModules.js
function genMethod(moduleID: number, methodID: number, type: MethodType) {
...
fn = function(...args: Array<any>) {
...
// 函数入队
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
};
...
fn.type = type;
return fn;
}
原生模块调用信息、回调信息暂存于MessageQueue
,等待调用时机(见上文)进行批量调用,把调用信息传递到原生端执行。
// MessageQueue.js
// 原生函数调用入队,执行
enqueueNativeCall(moduleID: number, methodID: number, params: any[], onFail: ?Function, onSucc: ?Function,) {
// 暂存回调
if (onFail || onSucc) {
this._successCallbacks[this._callID] = onSucc;
this._failureCallbacks[this._callID] = onFail;
}
// 暂存函数调用信息
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
this._queue[PARAMS].push(params);
...
global.nativeFlushQueueImmediate(queue);
}
- 3/4 JS call Native的触发时机(详见上文),原生模块信息由JS端传递到原生端
JSIExecutor
,触发callNativeModules
。
此处会把调用信息由JSValue类型转化为C++动态类型,并调用JsToNativeBridge
中转。
// JSIExecutor.cpp
void JSIExecutor::callNativeModules(const Value& queue, bool isEndOfBatch) {
// dynamicFromValue 函数调用信息由 JSValue 转 c++ 动态类型
delegate_->callNativeModules(
*this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}
- 5.
JsToNativeBridge
管理JS call Native,把批量原生模块调用信息
由C++动态类型解析为MethodCall
集合(原生模块函数调用信息结构体)并逐个调用。我们多次强调ModuleRegistry
是真正拥有原生模块信息者,原生模块函数调用必然中转到它头上。
// JsToNativeBridge.cpp
void callNativeModules(JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
// 解析函数调用元素,依次执行函数调用
for (auto& call : parseMethodCalls(std::move(calls))) {
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
...
}
// MethodCall.cpp
struct MethodCall {
int moduleId; // 模块id
int methodId; // 函数id
folly::dynamic arguments; // 调用参数
int callId; // 调用id
};
- 6.
ModuleRegistry
接收原生模块调用信息,根据moduleId获取到对应原生模块,接着中转函数调用。此时函数调用已经精确到对应的原生模块
// ModuleRegistry.cpp
// 原生模块函数调用
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
// 根据索引获取对应模块,调用函数
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
- 7.
RCTNativeModule
把函数执行线程切换到原生模块指定的执行线程。JavaScriptCore C语言篇提到关联了原生对象的JS对象,属性获取回调函数、函数调用回调函数的执行线程,与JS脚本执行的线程一致
,因此可知获取原生模块、JS call Native的执行线程都是JS线程。
静态函数invokeInner
,根据函数索引methodId获取到对应的函数,此时函数调用精确到对应原生模块的对应函数。
参数值由C++动态类型转化为OC数组,并获取原生模块实例instance
,接着中转。
// RCTNativeModule.mm
// 原生模块函数调用 methodId:函数索引 params:参数
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) {
__weak RCTBridge *weakBridge = m_bridge;
__weak RCTModuleData *weakModuleData = m_moduleData;
dispatch_block_t block = [weakBridge, weakModuleData, methodId, params=std::move(params), callId] {
invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
};
// 执行函数调用,由JS线程,切换到对应线程执行
if (m_bridge.valid) {
dispatch_queue_t queue = m_moduleData.methodQueue;
if (queue == RCTJSThread) {
block();
} else if (queue) {
dispatch_async(queue, block);
}
}
}
static MethodCallResult invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic ¶ms) {
// 根据函数索引methodId获取函数
id<RCTBridgeMethod> method = moduleData.methods[methodId];
// 参数 c++动态类型 》 oc数组
NSArray *objcParams = convertFollyDynamicToId(params);
// 执行函数调用
@try {
id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
return convertIdToFollyDynamic(result);
} @catch (NSException *exception) { ... }
}
- 8.
RCTModuleMethod
是原生模块导出函数描述对象,也是最终的执行者。- 1.首次调用函数会通过
processMethodSignature
解析导出函数信息,主要是:根据函数名生成函数消息、构造参数赋值回调argumentBlocks。
这里有点冗长,最好看源码,并把宏还原。回调类型参数RCTResponseSenderBlock
会生成一个封装了js回调操作的block([bridge enqueueCallback:json args:args];
),在第二步设置为函数消息对象invocation的参数。 - 2.接着遍历执行argumentBlocks给消息对象invocation设置函数参数值。
- 3.最后执行函数调用
[_invocation invokeWithTarget:module]
。
- 1.首次调用函数会通过
// RCTModuleMethod.mm
- (id)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
{
// 首次执行,解析导出函数信息
if (_argumentBlocks == nil) {
[self processMethodSignature];
}
// 执行传参block,设置参数
for (id json in arguments) {
RCTArgumentBlock block = _argumentBlocks[index];
}
// 调用原生模块方法
[_invocation invokeWithTarget:module];
}
- (void)processMethodSignature
{
// 通过导出函数名解析函数参数、SEL
NSArray<RCTMethodArgument *> *arguments;
_selector = NSSelectorFromString(RCTParseMethodSignature(_methodInfo->objcName, &arguments));
// 构造函数签名、消息
NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = _selector;
_invocation = invocation;
NSMutableArray *retainedObjects = [NSMutableArray array];
_retainedObjects = retainedObjects;
// 以block形式封装函数传参逻辑,并保存到_argumentBlocks
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
NSMutableArray<RCTArgumentBlock> *argumentBlocks =
[[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
BOOL isNullableType = NO;
RCTMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
SEL selector = selectorForType(typeName);
if ([RCTConvert respondsToSelector:selector]) {
...
} else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
/*
BLOCK_CASE((NSArray *args), {
[bridge enqueueCallback:json args:args];
});
*/
// 宏展开后如下,可以看出带回调的参数会通过enqueueCallback执行暂存在js端的回调
[argumentBlocks addObject:^(__unused __weak RCTBridge *bridge, NSUInteger index, id json) {
// 参数:回调block
id value = [^(NSArray *args) {
// 调用JS函数 json:callbackId,args:参数
[bridge enqueueCallback:json args:args];
} copy];
[invocation setArgument:&value atIndex:(index) + 2];
if (value) {
[retainedObjects addObject:value];
}
return YES;
}];
} else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
...
}
}
_argumentBlocks = argumentBlocks;
}
发起函数调用就调用到最终的原生模块函数。如果该JS call Native是不带回调的普通函数,至此已经执行完毕
// FYRNTestManager.m
// 带回调函数
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
RCTLogInfo(@"do something...");
RCTLogInfo(@"thread : %@", [NSThread currentThread]);
NSArray *events = @[@"events1", @"events2"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
callback(@[[NSNull null], events]);
});
}
9.函数调用完毕,执行回调。该回调是对
[bridge enqueueCallback:json args:args];
的封装,会触发C++Bridge执行js callback,把callback ID(即JS call Native的callID)传递到JS端用于获取到对应的回调函数,详见processMethodSignature
中RCTResponseSenderBlock
类型参数宏展开。10.
RCTCxxBridge
进一步把回调中转给Instance
,参数由OC数组转化为C++动态类型
// RCTCxxBridge.mm
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
__weak __typeof(self) weakSelf = self;
[self _runAfterLoad:^(){
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf->_reactInstance) {
// 执行JS回调
strongSelf->_reactInstance->callJSCallback([cbID unsignedLongLongValue], convertIdToFollyDynamic(args ?: @[]));
}
}];
}
- 11.
Instance
把回调中转给NativeToJsBridge
// Instance.cpp
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic &¶ms) {
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
- 12.
NativeToJsBridge
把回调中转给JSIExecutor
。此处线程切换到JS线程
// NativeToJsBridge.cpp
void NativeToJsBridge::invokeCallback(double callbackId, folly::dynamic&& arguments) {
runOnExecutorQueue([this, callbackId, arguments = std::move(arguments),]
executor->invokeCallback(callbackId, arguments);
});
}
- 13.
JSIExecutor
调用JS端BatchedBridge
的注入函数callFunctionReturnFlushedQueue
执行回调。
// JSIExecutor.cpp
void JSIExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
Value ret;
try {
ret = invokeCallbackAndReturnFlushedQueue_->call(
*runtime_, callbackId, valueFromDynamic(*runtime_, arguments));
} catch (...) { ... }
}
- 14.
BatchedBridge
根据回调id,对其进行位运算右移恢复,得到缓存key,并取出执行.
enqueueNativeCall
对callID进行位运算左移,这里是其逆运算,简洁的方式区分成功/失败回调。
// MessageQueue.js
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
this.__guard(() => {
// 执行Native函数对应的JS回调
this.__invokeCallback(cbID, args);
});
return this.flushedQueue();
}
__invokeCallback(cbID: number, args: any[]) {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
// 对cbID进行位运算右移,获取对应回调。 通过最后一位区分成功/失败回调
const callID = cbID >>> 1;
const isSuccess = cbID & 1;
const callback = isSuccess
? this._successCallbacks[callID]
: this._failureCallbacks[callID];
delete this._successCallbacks[callID];
delete this._failureCallbacks[callID];
// 执行回调
callback(...args);
}
触发业务层回调,流程完毕
const manager = NativeModules.TestManager;
manager.findEvents((error, events) => {
console.log("执行回调");
if (!error) {
this.setState({events: events});
}
});