com.facebook.react:react-native:0.60.4
// ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp
void CatalystInstanceImpl::initializeBridge(
jni::alias_ref<ReactCallback::javaobject> callback,
// This executor is actually a factory holder.
JavaScriptExecutorHolder* jseh,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> nativeModulesQueue,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
// ...
moduleMessageQueue_ = std::make_shared<JMessageQueueThread>(nativeModulesQueue);
// ...
moduleRegistry_ = std::make_shared<ModuleRegistry>(
buildNativeModuleList(
std::weak_ptr<Instance>(instance_),
javaModules,
cxxModules,
moduleMessageQueue_));
instance_->initializeBridge(
std::make_unique<JInstanceCallback>(
callback,
moduleMessageQueue_),
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
moduleRegistry_);
}
// ReactAndroid/src/main/jni/react/jni/ModuleRegistryBuilder.cpp
std::vector<std::unique_ptr<NativeModule>> buildNativeModuleList(
std::weak_ptr<Instance> winstance,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules,
std::shared_ptr<MessageQueueThread> moduleMessageQueue) {
std::vector<std::unique_ptr<NativeModule>> modules;
if (javaModules) {
for (const auto& jm : *javaModules) {
modules.emplace_back(folly::make_unique<JavaNativeModule>(
winstance, jm, moduleMessageQueue));
}
}
if (cxxModules) {
for (const auto& cm : *cxxModules) {
modules.emplace_back(folly::make_unique<CxxNativeModule>(
winstance, cm->getName(), cm->getProvider(), moduleMessageQueue));
}
}
return modules;
}
// ReactCommon/cxxreact/Instance.cpp
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
std::lock_guard<std::mutex> lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
CHECK(nativeToJsBridge_);
}
创建 NativeToJsBridge
runOnQueueSync
// ReactCommon/cxxreact/NativeToJsBridge.cpp
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory *jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false)),
m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
m_executorMessageQueueThread(std::move(jsQueue)),
m_inspectable(m_executor->isInspectable()) {}
ReactCommon/cxxreact/SampleCxxModule.cpp
auto SampleCxxModule::getMethods() -> std::vector<Method> {
return {
Method("hello", [this] {
sample_->hello();
}),
// ...
Method("addIfPositiveAsAsync", [](dynamic args, Callback cb, Callback cbError) {
auto a = jsArgAsDouble(args, 0);
auto b = jsArgAsDouble(args, 1);
if (a < 0 || b < 0) {
cbError({"Negative number!"});
} else {
cb({a + b});
}
}, AsyncTag),
};
}
Native对于js的调用如下图所示:
以上可以看出。上层所有的入口都是CatalystInstanceImpl的jniCallJSFunction,之后通过Instance即可到达NativeToJsBridge。
而NativeToJsBridge就是Native到JS的通信桥了,从这里所有的调用首先会在初始化时定义的jsQueue线程中运行,从而可以保证所有对JS的调用都是在同一线程中运行的。
在NativeToJsBridge中可以拿到对于JSExecutor的实现类(目前默认的是JSIExecutor),调用JSExecutor中的callFunction可以真正直达js Runtime,完成对js函数调用。如下:
// react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
void JSIExecutor::callFunction(
const std::string &moduleId,
const std::string &methodId,
const folly::dynamic &arguments) {
// ...
if (!callFunctionReturnFlushedQueue_) {
bindBridge();
}
// ...
Value ret = Value::undefined();
try {
scopedTimeoutInvoker_(
[&] {
ret = callFunctionReturnFlushedQueue_->call(
*runtime_,
moduleId,
methodId,
valueFromDynamic(*runtime_, arguments));
},
std::move(errorProducer));
} catch (...) {
std::throw_with_nested(
std::runtime_error("Error calling " + moduleId + "." + methodId));
}
callNativeModules(ret, true);
}
这里其实是三个逻辑:
// ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
void JSIExecutor::bindBridge() {
std::call_once(bindFlag_, [this] {
SystraceSection s("JSIExecutor::bindBridge (once)");
Value batchedBridgeValue = runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
if (batchedBridgeValue.isUndefined()) {
throw JSINativeException("Could not get BatchedBridge, make sure your bundle is packaged correctly");
}
Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(*runtime_, "callFunctionReturnFlushedQueue");
invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction( *runtime_, "invokeCallbackAndReturnFlushedQueue");
flushedQueue_ = batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
callFunctionReturnResultAndFlushedQueue_ = batchedBridge.getPropertyAsFunction( *runtime_, "callFunctionReturnResultAndFlushedQueue");
});
}
这里并不会每次都执行,如上在每次调用之前都会校验是否初始化过,如果没有则进行初始化。
这里主要的逻辑,就是在C++层完成对js层相关对象等的持有,以便直接对js层访问。
大概持有如下对象(函数):
这里我们只关注callFunctionReturnFlushedQueue即可。如下:
这里是callFunctionReturnFlushedQueue在js层的实现:
// Libraries/BatchedBridge/MessageQueue.js
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
this.__guard(() => {
this.__callFunction(module, method, args);
});
return this.flushedQueue();
}
__callFunction(module: string, method: string, args: any[]): any {
// ...
const moduleMethods = this.getCallableModule(module);
// ...
const result = moduleMethods[method].apply(moduleMethods, args);
Systrace.endEvent();
return result;
}
通过module可以拿到这个模块对应的函数列表,并通过函数名找到目标函数并配合参数执行js代码即可。
callNativeMethod是JsToNativeBridge内部的实现,最终会进入ModuleRegistry.cpp调用对应method,这就是js到native层的通信了。
为什么会有这种操作?其实RN中所有的事件都是异步的,js层到native的通信依赖与多种异步的触发点。比如这里说的native调用js时会触发调用js到native的缓存事件。
CatalystInstanceImpl对象build之后紧接着执行runJSBundle()用于加载JS代码。最终通过JSBunlderLoader会走到如下三个jni函数:
// ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously); private native void jniLoadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously); private native void jniLoadScriptFromDeltaBundle(String sourceURL, NativeDeltaClient deltaClient, boolean loadSynchronously);
Java层的CatalystInstanceImpl在调用jniLoadScriptFromAssets时最终会进到CatalystInstanceImpl.cpp对应的函数中,如上图。
之后调用到Instance的loadApplication中,如下:
// ReactCommon/cxxreact/Instance.cpp
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously) {
if (loadSynchronously) {
loadApplicationSync(nullptr, std::move(string), std::move(sourceURL));
} else {
loadApplication(nullptr, std::move(string), std::move(sourceURL));
}
}
void Instance::loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
callback_->incrementPendingJSCalls();
nativeToJsBridge_->loadApplication(std::move(bundleRegistry), std::move(string),std::move(sourceURL));
}
此时会首先调用incrementPendingJSCalls,这时java层的ReactCallback会收到对应回调。接着还是会进入万能的NativeToJsBridge里:
// ReactCommon/cxxreact/NativeToJsBridge.cpp
void NativeToJsBridge::loadApplication(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
[this,
bundleRegistryWrap=folly::makeMoveWrapper(std::move(bundleRegistry)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
auto bundleRegistry = bundleRegistryWrap.move();
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
});
}
同样的,所有对于js层的调用会都在jsQueue中执行,确保所有调用在同一线程运行。之后调用JSExecutor的loadApplicationScript真正加载js代码。
下面看看具体加载过程:
// ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
void JSIExecutor::loadApplicationScript(
std::unique_ptr<const JSBigString> script,
std::string sourceURL) {
// ...
runtime_->global().setProperty(
*runtime_,
"nativeModuleProxy",
Object::createFromHostObject(
*runtime_, std::make_shared<NativeModuleProxy>(*this)));
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) {
if (count != 1) {
throw std::invalid_argument(
"nativeFlushQueueImmediate arg count must be 1");
}
callNativeModules(args[0], false);
return Value::undefined();
}));
runtime_->global().setProperty(
*runtime_,
"nativeCallSyncHook",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
1,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) { return nativeCallSyncHook(args, count); }));
// ...
if (runtimeInstaller_) {
runtimeInstaller_(*runtime_);
}
// ...
runtime_->evaluateJavaScript(std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
flush();
// ...
}
首先,这里实际上会在js runtime中注册global的回调。如:
前者直接执行JSIExecutor的 nativeCallSyncHook ,后者对应的是 callNativeModules 。
其次,js runtime中进行evaluateJavaScript,对js代码进行载入。
最后,调用flush()。这里实际上是将缓存的native call拿出来执行。
下面先看一下js的初始化过程:
初始化时通过读取全局的remoteModuleConfig,获取当前支持的NativeModule。
之后通过loadModule函数,完成对Module的读取。注意,由上图可知这一步并不立即发生,而是lazy的方式。在这过程中,会读取module所包含的所有的Method并加入到缓存,如下:
// Libraries/BatchedBridge/NativeModules.js
function loadModule(name: string, moduleID: number): ?Object {
// ...
const config = global.nativeRequireModuleConfig(name);
const info = genModule(config, moduleID);
return info && info.module;
}
function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{name: string, module?: Object} {
if (!config) {
return null;
}
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
// ...
if (!constants && !methods) {
// Module contents will be filled in lazily later
return {name: moduleName};
}
const module = {};
methods && methods.forEach((methodName, methodID) => {
const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
const isSync = syncMethods && arrayContains(syncMethods, methodID);
// ...
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = genMethod(moduleID, methodID, methodType);
});
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
}
// ...
return {name: moduleName, module};
}
可以看到 genModule 中会遍历config中的所有声明的函数信息,根据函数名 映射由 模块ID/函数ID/函数类型 生成的函数结构。函数生成过程如下:
// Libraries/BatchedBridge/NativeModules.js
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
if (type === 'promise') {
fn = function(...args: Array<any>) {
// In case we reject, capture a useful stack trace here.
const enqueueingFrameError: ExtendedError = new Error();
enqueueingFrameError.framesToPop = 1;
return new Promise((resolve, reject) => {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData =>
reject(updateErrorWithErrorData(errorData, enqueueingFrameError)),
);
});
};
} else {
fn = function(...args: Array<any>) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccessCallback = typeof lastArg === 'function';
const hasErrorCallback = typeof secondLastArg === 'function';
hasErrorCallback &&
invariant(
hasSuccessCallback,
'Cannot have a non-function arg after a function arg.',
);
const onSuccess = hasSuccessCallback ? lastArg : null;
const onFail = hasErrorCallback ? secondLastArg : null;
const callbackCount = hasSuccessCallback + hasErrorCallback;
args = args.slice(0, args.length - callbackCount);
if (type === 'sync') {
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
} else {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
onFail,
onSuccess,
);
}
};
}
fn.type = type;
return fn;
}
由上可知,如果时同步方式那么直接调用BatchedBridge.callNativeSyncHook,这里对应的是上面的nativeCallSyncHook。
否则默认都是BatchedBridge.enqueueNativeCall,即异步方式,如下:
enqueueNativeCall是异步调用native method的入口,流程如下:
// Libraries/BatchedBridge/MessageQueue.js
enqueueNativeCall(
moduleID: number,
methodID: number,
params: any[],
onFail: ?Function,
onSucc: ?Function,
) {
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
// ...
this._queue[PARAMS].push(params);
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);
}
// ...
}
callNativeModules 执行native模块,如图步骤5所示。具体 callNativeModules 的实现,后面再说。 如果_lastFlush没有超过对应时间(MIN_TIME_BETWEEN_FLUSHES_MS),那么就会静静等待被临幸。
来自JS的调用大概会有如下三种被临幸的方式:
flush()在加载js bundle之后会直接被调用到,由上层runJSBundle()触发,直接获取flushedQueue_一一执行。
callFunction()来自jniCallJsFunction(),即来自native对js函数的调用。即先执行js的funcation之后返回flushedQueue_。
invokeCallback()来自native层对js callback的调用(来自JavaMethodWrapper.java,即Native被js调用时注册了的Callback),同上面类似都是native to js。之后返回flushedQueue_等触发js对native的调用。
上面三个函数最后都通过拿到的flushedQueue_,跑到callNativeModules完成对native的调用。
下面是对应上面三个变量对应的js实现:
// Libraries/BatchedBridge/MessageQueue.js
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) {
this.__guard(() => {
this.__callFunction(module, method, args);
});
return this.flushedQueue();
}
callFunctionReturnResultAndFlushedQueue(
module: string,
method: string,
args: any[],
) {
let result;
this.__guard(() => {
result = this.__callFunction(module, method, args);
});
return [result, this.flushedQueue()];
}
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) {
this.__guard(() => {
this.__invokeCallback(cbID, args);
});
return this.flushedQueue();
}
flushedQueue() {
this.__guard(() => {
this.__callImmediates();
});
const queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
JS 对 Native的调用到这里就结束了。下面再来看看JSI调用Native的具体方式。
由上面可知,最终js调用native会在callNativeModules实现,大概过程如上如示。下面看看具体代码:
// ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
delegate_->callNativeModules(
*this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}
这里的delegate就是JsToNativeBridge(这个类在NativeToJsBridge.cpp内部,一不小心就混淆了)。其内部的callNativeModules实现如下:
// ReactCommon/cxxreact/NativeToJsBridge.cpp
class JsToNativeBridge : public react::ExecutorDelegate {
void callNativeModules(
__unused JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
m_batchHadNativeModuleCalls = m_batchHadNativeModuleCalls || !calls.empty();
for (auto& call : parseMethodCalls(std::move(calls))) {
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
if (m_batchHadNativeModuleCalls) {
m_callback->onBatchComplete();
m_batchHadNativeModuleCalls = false;
}
m_callback->decrementPendingJSCalls();
}
}
}
这里会将MessageQueue.js中的flushQueue解析成一个由MethodCall组成的向量。之后遍历这个向量,对单个MethodCall一个一个调用。解析过程如下:
// ReactCommon/cxxreact/MethodCall.cpp
std::vector<MethodCall> parseMethodCalls(folly::dynamic&& jsonData) {
// ...
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) {
callId = (int)jsonData[REQUEST_CALLID].asInt();
}
std::vector<MethodCall> methodCalls;
for (size_t i = 0; i < moduleIds.size(); i++) {
// ...
methodCalls.emplace_back(
moduleIds[i].asInt(),
methodIds[i].asInt(),
std::move(params[i]),
callId);
callId += (callId != -1) ? 1 : 0;
}
return methodCalls;
}
函数调用参数是以json数据进行封装的,模块/函数/参数等分别在对应的json字段中作为单独的数组存在。
// ReactCommon/cxxreact/ModuleRegistry.cpp
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
folly::to<std::string>("moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
这里的modules_在CatalystInstanceImpl初始化时生成,所有的NativeModule(包括JavaModule和CxxModule)共享相同的模块命名(因此写Native模块时需要主要命名)。
这里所做的事就是通过moduleId找到对应模块,并且给相应模块传递所需要的函数id以及参数等。
下面分别介绍一下java和Cxx模块是如何被调用的。
void JavaNativeModule::invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) {
messageQueueThread_->runOnQueue([this, reactMethodId, params=std::move(params), callId] {
static auto invokeMethod = wrapper_->getClass()->getMethod<void(jint, ReadableNativeArray::javaobject)>("invoke");
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#endif
invokeMethod(
wrapper_,
static_cast<jint>(reactMethodId),
ReadableNativeArray::newObjectCxxArgs(std::move(params)).get());
});
}
// ReactCommon/cxxreact/CxxNativeModule.cpp
void CxxNativeModule::invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) {
// ...
CxxModule::Callback first;
CxxModule::Callback second;
const auto& method = methods_[reactMethodId];
// ...
if (method.callbacks == 1) {
first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
} else if (method.callbacks == 2) {
first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
second = convertCallback(makeCallback(instance_, params[params.size() - 1]));
}
params.resize(params.size() - method.callbacks);
messageQueueThread_->runOnQueue([method, params=std::move(params), first, second, callId] () {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#else
(void)(callId);
#endif
SystraceSection s(method.name.c_str());
try {
method.func(std::move(params), first, second);
}
// ....
});
}
// ReactCommon/cxxreact/NativeToJsBridge.cpp
MethodCallResult callSerializableNativeHook(
__unused JSExecutor& executor, unsigned int moduleId, unsigned int methodId,
folly::dynamic&& args) override {
return m_registry->callSerializableNativeHook(moduleId, methodId, std::move(args));
}
// ReactCommon/cxxreact/ModuleRegistry.cpp
MethodCallResult ModuleRegistry::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
folly::to<std::string>("moduleId ", moduleId, "out of range [0..", modules_.size(), ")"));
}
return modules_[moduleId]->callSerializableNativeHook(methodId, std::move(params));
}
// ReactCommon/cxxreact/CxxNativeModule.cpp
MethodCallResult CxxNativeModule::callSerializableNativeHook(unsigned int hookId, folly::dynamic&& args) {
if (hookId >= methods_.size()) {
throw std::invalid_argument(
folly::to<std::string>("methodId ", hookId, " out of range [0..", methods_.size(), "]"));
}
const auto& method = methods_[hookId];
if (!method.syncFunc) {
throw std::runtime_error(
folly::to<std::string>("Method ", method.name,
" is asynchronous but invoked synchronously"));
}
return method.syncFunc(std::move(args));
}
// ReactCommon/cxxreact/MethodCall.cppBy@hyongbai 共23741个字
本文链接