美文网首页React Native
ReactNatvie通讯机制

ReactNatvie通讯机制

作者: 游侠_6fb7 | 来源:发表于2020-04-15 16:13 被阅读0次
    通讯框架图
    image.png image.png image.png
    JAVA层

    Java层,这块的实现在ReactAndroid中
    • ReactContext : Android上下文子类,包含一个CatalystInstance实例,用于获取NativeModule,JSModule、添加各种回调、处理异常等
    • ReactInstanceManager : 管理CatalystInstance的实例,处理RN Root View,启动JS页面,管理生命周期
    • CatalystInstance : 通讯的关键类,提供调用JS Module也支持JS调用Native Module,与Bridge进行交互,对开发者不可见

    C++层

    C++层,这块实现在ReactCommon中,供Android与iOS使用
    • NativeToJsBridge : native与JS的桥接,负责调用JS Module、回调Native(调用JsToNativeBridge)、加载JS代码(调用JavaScriptCore)
    • JsToNativeBridge : 调用Native Module的方法
    • JSCExecutor : 加载/执行JS代码(调用JavaScriptCore)、调用JS Module、回调native、性能统计等,都是比较核心的功能

    JS层

    JS层,实现在Libraries中,RN JS相关的实现在都这个文件夹中
    • MessageQueue : 管理JS的调用队列、调用Native/JS Module的方法、执行callback、管理JS Module等
    • JavaScriptModule : 代指所有的JSModule实现,在java层中也有对应的代码(都是interface),使用动态代理调用,统一入口在CatalystInstance中

    C++与JS通讯

    Native与JS通讯无非就是Java/OC与JS跨语言间的调用,在分析Native与JS通讯前先来了解下Java/OC与JS跨语言间的调用。
    在ReactNative中使用JavaScriptCore来执行JS,这部分的关键就是如何利用JavaScriptCore。
    ReactAndroid/build.gradle
    导入 compile ‘org.webkit:android-jsc:r174650’
    RN并没有用系统自带的webkit,WebKit主要包括WebCore排版引擎和JSCore引擎,这里主要使用了JSCore引擎,排版交给Native去做。
    在RN中通过下面的方法设置native方法和属性:
    JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);

    这个方法正是上面gradle脚本下载的JSObjectRef.h中,实现在libjsc.so中。这样就可以在Native设置,然后在JS中取出执行,反过来也是同样的。

    Native与JS通讯

    加载bundle文件
    Native与JS的通讯首先需要加载Bundle文件,是在native初始化完成的时候,而Bundle文件的位置是可配置的。


    image.png
    Native与JS通讯
    image.png

    JSBundleLoader从哪里加载,也是根据文件的位置,可以看看其loadScript方法,最终都会调用CatalystIntance去加载,有三个实现

    private native void jniSetSourceURL(String sourceURL);
    private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL);
    private native void jniLoadScriptFromFile(String fileName, String sourceURL);

    void CatalystInstanceImpl::jniLoadScriptFromFile(const std::string& fileName, const std::string& sourceURL) { auto zFileName = fileName.c_str(); if (isIndexedRAMBundle(zFileName)) { auto bundle = folly::make_unique<JSIndexedRAMBundle>(zFileName); auto startupScript = bundle->getStartupCode(); instance_->loadUnbundle( std::move(bundle), std::move(startupScript), sourceURL); } else { instance_->loadScriptFromFile(fileName, sourceURL); }}

    从文件中加载就是先读取bundle的内容,当作一个字符串,这里有一个UnBundle,是RN打包的一种方式,除了生成整合JS文件index.android.bundle外,还会生成各个单独的未整合JS文件(但会被优化),全部放在js-modules目录下,同时会生成一个名为UNBUNDLE的标识文件,一并放在其中。UNBUNDLE标识文件的前4个字节固定为0xFB0BD1E5,用于加载前的校验。需要注意的是,js-modules目录会一并打包到apk的assets文件夹中,这里就是处理这种情况的,后面具体的加载暂不分析

    image.png

    Native中调用JS的方式如下:
    reactContext
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit(eventName, params);
    ReactContext调用的是CatalystInstanceImpl中的getJSModule方法;
    @Override
    public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
    return getJSModule(mMainExecutorToken, jsInterface);
    }

    @Override
    public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
    return Assertions.assertNotNull(mJSModuleRegistry)
    .getJavaScriptModule(this, executorToken, jsInterface);
    }

    mMainExecutorToken是在initializeBridge时创建的,根据注释是和web workers相关,是JS多线程相关的,即使用Token来区分线程。当前这种情况使用mMainExecutorToken就可以

    CatalystInstance的实现
    public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
    CatalystInstance instance,
    ExecutorToken executorToken,
    Class<T> moduleInterface) {
    HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
    mModuleInstances.get(executorToken);
    if (instancesForContext == null) {
    instancesForContext = new HashMap<>();
    mModuleInstances.put(executorToken, instancesForContext);
    }

    JavaScriptModule module = instancesForContext.get(moduleInterface);
    if (module != null) {
    return (T) module;
    }

    JavaScriptModuleRegistration registration =
    Assertions.assertNotNull(
    mModuleRegistrations.get(moduleInterface),
    "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
    moduleInterface.getClassLoader(),
    new Class[]{moduleInterface},
    new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
    }

    在CatalystInstance创建的时候会把所有JavaScriptModule都收集到JavaScriptModuleRegistry的Map(mModuleRegistrations)中。而mModuleInstances是缓存已经调用过的JS Module的代理对象,如果已经调用过,则从map中直接返回,否则创建其代理对象,然后缓存起来。
    这里使用的是动态代理模式,先创建一个interface的代理对象,当调用其方法时会InvocationHandler的invoke()方法。

    @Override
    public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
    ExecutorToken executorToken = mExecutorToken.get();
    if (executorToken == null) {
    FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
    return null;
    }
    NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
    mCatalystInstance.callFunction(
    executorToken,
    mModuleRegistration.getName(),
    method.getName(),
    jsArgs
    );
    return null;
    }

    JS接收调用和处理

    先来解释下为什么会走到callFunctionReturnFlushedQueue。
    1. 在生成的bundle.js中会把MessageQueue对象放到一个全局的属性中
    const MessageQueue = require('MessageQueue');const BatchedBridge = new MessageQueue();// Wire up the batched bridge on the global object so that we can call into it.// Ideally, this would be the inverse relationship. I.e. the native environment// provides this global directly with its script embedded. Then this module// would export it. A possible fix would be to trim the dependencies in// MessageQueue to its minimal features and embed that in the native runtime.Object.defineProperty(global, '__fbBatchedBridge', { configurable: true, value: BatchedBridge,});module.exports = BatchedBridge;

    在上面加载bundle文件的时候,会执行下面的方法   
    

    void JSCExecutor::bindBridge() throw(JSException) { SystraceSection s("JSCExecutor::bindBridge"); auto global = Object::getGlobalObject(m_context); auto batchedBridgeValue = global.getProperty("__fbBatchedBridge"); if (batchedBridgeValue.isUndefined()) { throwJSExecutionException("Could not get BatchedBridge, make sure your bundle is packaged correctly"); } auto batchedBridge = batchedBridgeValue.asObject(); m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject(); m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject(); m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject(); m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();}

    这里会把MessageQueue的三个方法会当作对象保存在c++中,当我们调用JS的方法时会直接用到。
    void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) { SystraceSection s("JSCExecutor::callFunction"); // This weird pattern is because Value is not default constructible. // The lambda is inlined, so there's no overhead. auto result = [&] { try { return m_callFunctionReturnFlushedQueueJS->callAsFunction({ Value(m_context, String::createExpectingAscii(m_context, moduleId)), Value(m_context, String::createExpectingAscii(m_context, methodId)), Value::fromDynamic(m_context, std::move(arguments)) }); } catch (...) { std::throw_with_nested( std::runtime_error("Error calling " + moduleId + "." + methodId)); } }(); callNativeModules(std::move(result));}

    最终还是通过JavaScriptCore的方法JSObjectCallAsFunction来调用JS的。下面就好办了,直接分析JS代码吧。
    在callFunctionReturnFlushedQueue这个方法主要调用了__callFunction,来看一下它的实现:
    __callFunction(module: string, method: string, args: Array<any>) { this._lastFlush = new Date().getTime(); this._eventLoopStartTime = this._lastFlush; Systrace.beginEvent(${module}.${method}()); if (this.__spy) { this.__spy({ type: TO_JS, module, method, args}); } const moduleMethods = this._callableModules[module]; invariant( !!moduleMethods, 'Module %s is not a registered callable module (calling %s)', module, method ); invariant( !!moduleMethods[method], 'Method %s does not exist on module %s', method, module ); const result = moduleMethods[method].apply(moduleMethods, args); Systrace.endEvent(); return result;}

    方法是从_callableModules中取出来的,那他的值是从哪里来的呢,看了下这个文件原来答案是有往里添加的方法

    registerCallableModule(name: string, module: Object) { this._callableModules[name] = module;}
    也就是说所有的JS Module都需要把该Module中可供Native调用的方法都放到这里来,这样才能够执行。以AppRegistry.js为例,来看看它是怎么往里添加的
    BatchedBridge.registerCallableModule( 'AppRegistry', AppRegistry);

    JS与Native通讯

    Native的初始化流程,这里总结一下对Native 模块的处理。
    1. 在初始化CatalystInstance时会把所有的Native Module放在一个列表中,并在C++(ModuleRegistry)和Java(NativeModuleRegistry)中都保存了
    2. 在JavaScriptCore中设置了全局属性__fbBatchedBridgeConfig,其值为Module Name列表
    那么问题来了,在JS中只能取到Native Module的名字,怎么调用它的方法呢。下面来分析下这个问题。在JSCExecutor初始化的时候,向JavaScriptCore中注册了几个c++的方法供JS调用,其中就有获取Native Module详细信息的方法
    NativeModules
    function loadModule(name: string, moduleID: number): ?Object { invariant(global.nativeRequireModuleConfig, 'Can't lazily create module without nativeRequireModuleConfig'); const config = global.nativeRequireModuleConfig(name); const info = genModule(config, moduleID); return info && info.module;}

    const config = global.nativeRequireModuleConfig(name);
    这块会遍历RemoteModules中所有的模块名,每个模块名都定义一个对象,使用的时候才会为其赋值。看到在赋值的时候会调用c++的nativeRequireModuleConfig,也就是获取每个Module的详细信息。
    获取详细信息就是调用上面提到的m_delegate->getModuleConfig(moduleName),m_delegate是JsToNativeBridge对象,getModuleConfig直接调用了ModuleRegistry::getConfig(name)

    folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) { SystraceSection s("getConfig", "module", name);………………………… CHECK(it->second < modules_.size()); NativeModule* module = modules_[it->second].get(); { SystraceSection s("getConstants"); config.push_back(module->getConstants()); } { SystraceSection s("getMethods"); 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) { // TODO: #10487027 compare tags instead of doing string comparison? methodNames.push_back(std::move(descriptor.name)); if (descriptor.type == "promise") { promiseMethodIds.push_back(methodNames.size() - 1); } else if (descriptor.type == "sync") { syncMethodIds.push_back(methodNames.size() - 1); } } ……………………}

    function 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>) { 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); BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess); }; } fn.type = type; return fn;}

    BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
    调用到MessageQueue中
    enqueueNativeCall中
    if (global.nativeFlushQueueImmediate && now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) { global.nativeFlushQueueImmediate(this._queue); this._queue = [[], [], [], this._callID]; this._lastFlush = now;}

    image.png
    JS接收调用和处理

    到这里Native调用JS就已经完成了。总结一下整个流程:
    1. MessageQueue把Native调用的方法放到JavaScriptCore中
    2. JS Module把可以调用的方法放到MessageQueue的一个对列中
    3. Native从JavaScriptCore中拿到JS的调用入口,并把Module Name、Method Name、Parameters传过去
    4. 执行JS Module的方法

    相关文章

      网友评论

        本文标题:ReactNatvie通讯机制

        本文链接:https://www.haomeiwen.com/subject/zwgcvhtx.html