運(yùn)用一個(gè)架構(gòu),總得了解一下背后的原理,本文基于react-native的最新版本(0.47.1),簡要探究一下react-native背后到底做了哪些操作。
一、通信機(jī)制
RN框架最主要的就是實(shí)現(xiàn)了一套JAVA和 JS通信的方案,該方案可以做到比較簡便的互調(diào)對方的接口。一般的JS運(yùn)行環(huán)境是直接擴(kuò)展JS接口,然后JS通過擴(kuò)展接口發(fā)送信息到主線程。但RN的通信的實(shí)現(xiàn)機(jī)制是單向調(diào)用,Native線程定期向JS線程拉取數(shù)據(jù), 然后轉(zhuǎn)成JS的調(diào)用預(yù)期,最后轉(zhuǎn)交給Native對應(yīng)的調(diào)用模塊。這樣最終同樣也可以達(dá)到Java和 JS 定義的Module互相調(diào)用的目的。
1.js調(diào)用Java
①現(xiàn)在版本是通過如下調(diào)用
import { NativeModules, DeviceEventEmitter} from 'react-native';
export const download=(opt) =>{
NativeModules.DownloadFileManager.download(opt);
}
上面是我們封裝一個(gè)下載插件,我們?nèi)タ聪翹ativeModules,我們拿出這塊代碼:
let NativeModules : {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else {
const bridgeConfig = global.__fbBatchedBridgeConfig;
invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules');
const defineLazyObjectProperty = require('defineLazyObjectProperty');
(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (!info) {
return;
}
if (info.module) {
NativeModules[info.name] = info.module;
}
// If there's no module config, define a lazy getter
else {
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID)
});
}
});
}
這里我們簡要看一下,大體意思就是nativeModule初始化,其實(shí)就是把js的信息寫入到一個(gè)消息隊(duì)列中(messageQueue),其中注意一下genModule這個(gè)方法,我們?nèi)タ催@個(gè)源碼發(fā)現(xiàn)這里先是對js傳過來的config信息做非空、命名檢查Promise和sync方法檢查,如果檢查合格就調(diào)用genMethod方法,如下圖

然后我們看下genMethod方法,如2-1圖,這里我們看的很清楚了,這個(gè)方法根據(jù)方法類型來分別調(diào)用BatchedBridge.enqueueNativeCall這個(gè)方法,這個(gè)方法里面有三個(gè)入?yún)?,moduleID,MethodID,args和兩個(gè)回調(diào),看到這里,我們應(yīng)該都能猜想到,native層應(yīng)該是通過moduleID來確定是調(diào)用哪個(gè)類,MethodID確定是這個(gè)類的哪個(gè)方法,args就是參數(shù)了,回調(diào)就是正確回調(diào)和錯(cuò)誤回調(diào)了,不信我們往下看。

那么我們看下enqueueNativeCall這個(gè)方法,這里我們注意到多了一個(gè)callID,其實(shí)往下看你就會知道native層就是通過這個(gè)callID進(jìn)行回調(diào),通過一些處理后,把方法調(diào)用信息push到消息隊(duì)列中(_queue).

最后,通過nativeFlushQueueImmediate 方法理解發(fā)送到消息隊(duì)列,等待native來取,至此,js層的處理已經(jīng)全部完成。這里我們還要注意一點(diǎn),這個(gè)方法把模塊名、方法名、調(diào)用參數(shù)放到數(shù)組里存起來,如果上次調(diào)用和本次調(diào)用想著超過5ms則調(diào)用c++的nativeFlushQueueImmediate方法,如果小于5ms就直接返回了。可見js調(diào)用native總是有開銷的。
if (global.nativeFlushQueueImmediate &&
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
this._inCall === 0)) {
var queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
native通過一定條件觸發(fā),這里我們暫時(shí)不管什么時(shí)候觸發(fā),先看下native怎么調(diào)用js層的信息。從上面我們知道js最后一步是走到了調(diào)用nativeFlushQueueImmediate方法,那這個(gè)方法在native里面是怎么調(diào)用生成的呢?
nativeFlushQueueImmediate這個(gè)方法其實(shí)C++代碼中注入到Js的一個(gè)全局變量,具體怎么注入的,就是在JSCExecutor.cpp中調(diào)用installGlobalFunction,installGlobalFunction的是通過JavaScriptCore的API來實(shí)現(xiàn)讓Js可以調(diào)用C++代碼的。
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
installGlobalFunction(m_context, "nativeLoggingHook", JSCNativeHooks::loggingHook);
installGlobalFunction(m_context, "nativePerformanceNow", JSCNativeHooks::nowHook);
#if DEBUG
installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
#endif
...
JSValueRef JSCExecutor::nativeFlushQueueImmediate(
size_t argumentCount,
const JSValueRef arguments[]) {
if (argumentCount != 1) {
throw std::invalid_argument("Got wrong number of args");
}
flushQueueImmediate(Value(m_context, arguments[0]));
return Value::makeUndefined(m_context);
}
...
void JSCExecutor::flushQueueImmediate(Value&& queue) {
auto queueStr = queue.toJSONString();
m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false);
}
這里nativeFlushQueueImmediate又調(diào)用了flushQueueImmediate方法,flushQueueImmediate方法中有調(diào)用了callNativeModules,那么問題來了,這個(gè)callNativeModules是從哪里來的呢?看下圖5-1,這里我們已經(jīng)很清楚了在JsToNativeBridge方法中調(diào)用了callNativeMethod,再看下入?yún)?,是不是跟js中的入?yún)⒁粯樱琌(∩_∩)O哈哈~。

這里的callNativeMethod是調(diào)用ModuleRegistry中的方法,在這個(gè)方法中,我們看到了用到反射invoke方法,那么這個(gè)invoke方法其實(shí)是定義在NativeModule .h文件中的一個(gè)虛方法,這個(gè)虛方法又是被JavaModuleWrapper.cpp實(shí)現(xiàn)了,在這個(gè)方法里面最后是使用反射調(diào)用了jni中對應(yīng)JavaModuleWrapper.java的方法,然后我們?nèi)タ聪翵avaModuleWrapper.java方法
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);
}
....
class NativeModule {
public:
virtual ~NativeModule() {}
virtual std::string getName() = 0;
virtual std::vector<MethodDescriptor> getMethods() = 0;
virtual folly::dynamic getConstants() = 0;
virtual void invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) = 0;
virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic&& args) = 0;
};
}
}
...
void NewJavaNativeModule::invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
folly::to<std::string>("methodId ", reactMethodId, " out of range [0..", methods_.size(), "]"));
}
CHECK(!methods_[reactMethodId].isSyncHook()) << "Trying to invoke a synchronous hook asynchronously";
messageQueueThread_->runOnQueue([this, reactMethodId, params=std::move(params), callId] () mutable {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#endif
invokeInner(reactMethodId, std::move(params));
});
}
MethodCallResult NewJavaNativeModule::invokeInner(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {
return methods_[reactMethodId].invoke(instance_, module_.get(), token, params);
}
在JavaModuleWrapper.java中,我們看到了這個(gè)方法,這里我們應(yīng)該就可以明白了已經(jīng)調(diào)到Java的方法了,那么至此js調(diào)用native到此結(jié)束。
@DoNotStrip
public void invoke(int methodId, ReadableNativeArray parameters) {
if (mMethods == null || methodId >= mMethods.size()) {
return;
}
mMethods.get(methodId).invoke(mJSInstance, parameters);
}
下面是整個(gè)交互流程。

②Native調(diào)用JS流程
首先我們找到CatalystInstance類,這個(gè)是在JNI中,下面這兩方法,第一個(gè)方法正是上面js調(diào)用完native后,回調(diào)通過這里回調(diào)回去,可以看到用到的是callbackid,這個(gè)正是上面js在messagequeue中生成的那個(gè)id;然后第二個(gè)方法是調(diào)用js方法,對應(yīng)三個(gè)參數(shù):js類,js方法,參數(shù)。
@Override @DoNotStrip
void invokeCallback(
int callbackID,
NativeArray arguments);
@DoNotStrip
void callFunction(
String module,
String method,
NativeArray arguments);
再去看看catalystInstance具體的實(shí)現(xiàn)類,如下最后調(diào)用了jniCallJSFunction
@Override
public void callFunction(
final String module,
final String method,
final NativeArray arguments) {
if (mDestroyed) {
final String call = module + "." + method + "(" + arguments.toString() + ")";
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed: " + call);
return;
}
if (!mAcceptCalls) {
// Most of the time the instance is initialized and we don't need to acquire the lock
synchronized (mJSCallsPendingInitLock) {
if (!mAcceptCalls) {
mJSCallsPendingInit.add(new PendingJSCall(module, method, arguments));
return;
}
}
}
jniCallJSFunction(module, method, arguments);
}
講到這里,實(shí)際上reactContext目前并沒有入口提供給我們。我們通常調(diào)用的是ReactContext.getJSModule(JSModule類名.class).方法名(params);
ReactContext調(diào)用的是CatalystInstance的同名方法。
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
}
那這個(gè)方法也是先收集js模塊的所有信息并驗(yàn)證最后調(diào)用InvocationHandler的invoke()方法。
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
NativeArray jsArgs = args != null
? Arguments.fromJavaArgs(args)
: new WritableNativeArray();
mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs);
return null;
}
這里我們看到了上面說到的callFunction 方法了,然后我們看下jniCallJSFunction,這個(gè)就有開始調(diào)用C++
private native void jniCallJSFunction(
String module,
String method,
NativeArray arguments);
在nativeToBridge.cpp中,我們看到了跟Java同名的方法,就是它了
void NativeToJsBridge::callFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE,
"JSCall",
systraceCookie);
#endif
runOnExecutorQueue([module = std::move(module), method = std::move(method), arguments = std::move(arguments), systraceCookie]
(JSExecutor* executor) {
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE,
"JSCall",
systraceCookie);
SystraceSection s("NativeToJsBridge::callFunction", "module", module, "method", method);
#endif
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
executor->callFunction(module, method, arguments);
});
}
最后發(fā)消息發(fā)送到j(luò)s的messagequeue上。
總結(jié)一下整個(gè)流程:
1.MessageQueue把Native調(diào)用的方法放到JavaScriptCore中
2.JS Module把可以調(diào)用的方法放到MessageQueue的一個(gè)對列中
3.Native從JavaScriptCore中拿到JS的調(diào)用入口,并把Module Name、Method Name、Parameters傳過去
4.執(zhí)行JS Module的方法
