React-native 原理探究
运用一个架构,总得了解一下背后的原理,本文基于react-native的最新版本(0.47.1),简要探究一下react-native背后到底做了哪些操作。
一、通信机制
RN框架最主要的就是实现了一套JAVA和 JS通信的方案,该方案可以做到比较简便的互调对方的接口。一般的JS运行环境是直接扩展JS接口,然后JS通过扩展接口发送信息到主线程。但RN的通信的实现机制是单向调用,Native线程定期向JS线程拉取数据, 然后转成JS的调用预期,最后转交给Native对应的调用模块。这样最终同样也可以达到Java和 JS 定义的Module互相调用的目的。
1.js调用Java
①现在版本是通过如下调用
import { NativeModules, DeviceEventEmitter} from 'react-native';
export const download=(opt) =>{
NativeModules.DownloadFileManager.download(opt);
}
上面是我们封装一个下载插件,我们去看下NativeModules,我们拿出这块代码:
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初始化,其实就是把js的信息写入到一个消息队列中(messageQueue),其中注意一下genModule这个方法,我们去看这个源码发现这里先是对js传过来的config信息做非空、命名检查Promise和sync方法检查,如果检查合格就调用genMethod方法,如下图
1-1.png然后我们看下genMethod方法,如2-1图,这里我们看的很清楚了,这个方法根据方法类型来分别调用BatchedBridge.enqueueNativeCall这个方法,这个方法里面有三个入参,moduleID,MethodID,args和两个回调,看到这里,我们应该都能猜想到,native层应该是通过moduleID来确定是调用哪个类,MethodID确定是这个类的哪个方法,args就是参数了,回调就是正确回调和错误回调了,不信我们往下看。
2-1.png那么我们看下enqueueNativeCall这个方法,这里我们注意到多了一个callID,其实往下看你就会知道native层就是通过这个callID进行回调,通过一些处理后,把方法调用信息push到消息队列中(_queue).
3-1.png最后,通过nativeFlushQueueImmediate 方法理解发送到消息队列,等待native来取,至此,js层的处理已经全部完成。这里我们还要注意一点,这个方法把模块名、方法名、调用参数放到数组里存起来,如果上次调用和本次调用想着超过5ms则调用c++的nativeFlushQueueImmediate方法,如果小于5ms就直接返回了。可见js调用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通过一定条件触发,这里我们暂时不管什么时候触发,先看下native怎么调用js层的信息。从上面我们知道js最后一步是走到了调用nativeFlushQueueImmediate方法,那这个方法在native里面是怎么调用生成的呢?
nativeFlushQueueImmediate这个方法其实C++代码中注入到Js的一个全局变量,具体怎么注入的,就是在JSCExecutor.cpp
中调用installGlobalFunction,installGlobalFunction的是通过JavaScriptCore的API来实现让Js可以调用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又调用了flushQueueImmediate方法,flushQueueImmediate方法中有调用了callNativeModules,那么问题来了,这个callNativeModules是从哪里来的呢?看下图5-1,这里我们已经很清楚了在JsToNativeBridge方法中调用了callNativeMethod,再看下入参,是不是跟js中的入参一样,O(∩_∩)O哈哈~。
5-1.png这里的callNativeMethod是调用ModuleRegistry中的方法,在这个方法中,我们看到了用到反射invoke方法,那么这个invoke方法其实是定义在NativeModule .h文件中的一个虚方法,这个虚方法又是被JavaModuleWrapper.cpp实现了,在这个方法里面最后是使用反射调用了jni中对应JavaModuleWrapper.java的方法,然后我们去看下JavaModuleWrapper.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中,我们看到了这个方法,这里我们应该就可以明白了已经调到Java的方法了,那么至此js调用native到此结束。
@DoNotStrip
public void invoke(int methodId, ReadableNativeArray parameters) {
if (mMethods == null || methodId >= mMethods.size()) {
return;
}
mMethods.get(methodId).invoke(mJSInstance, parameters);
}
下面是整个交互流程。
6-1.png②Native调用JS流程
首先我们找到CatalystInstance类,这个是在JNI中,下面这两方法,第一个方法正是上面js调用完native后,回调通过这里回调回去,可以看到用到的是callbackid,这个正是上面js在messagequeue中生成的那个id;然后第二个方法是调用js方法,对应三个参数:js类,js方法,参数。
@Override @DoNotStrip
void invokeCallback(
int callbackID,
NativeArray arguments);
@DoNotStrip
void callFunction(
String module,
String method,
NativeArray arguments);
再去看看catalystInstance具体的实现类,如下最后调用了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);
}
讲到这里,实际上reactContext目前并没有入口提供给我们。我们通常调用的是ReactContext.getJSModule(JSModule类名.class).方法名(params);
ReactContext调用的是CatalystInstance的同名方法。
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
}
那这个方法也是先收集js模块的所有信息并验证最后调用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,这个就有开始调用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);
});
}
最后发消息发送到js的messagequeue上。
总结一下整个流程:
1.MessageQueue把Native调用的方法放到JavaScriptCore中
2.JS Module把可以调用的方法放到MessageQueue的一个对列中
3.Native从JavaScriptCore中拿到JS的调用入口,并把Module Name、Method Name、Parameters传过去
4.执行JS Module的方法