irpas技术客

【React Native】深入理解Native与RN通信原理_骑着代马去流浪_reactnative通信原理

网络投稿 6458

? ? ? ? 在使用 React Native 开发应用程序的时候,有时候需要使用?JavaScript 中默认不可用的 IOS 或 Android 的原生 API。 也许你想复用一些现有的 OC、Swift、Java 或 C++ 库,而不必在 JavaScript 中重新实现它,或者为图像处理之类的事情编写一些高性能、多线程的代码。那么此时就不得不与Native打交道了。

? ? ? ? 幸运的是,React Native已经提供了这样的能力来供你使用,在JS Core的强大支持下,我们RN侧可以使用NativeModule 来将Native代码作为 JS 对象暴露给 JavaScript来调用,从而允许您从 JS 内可以调用Native代码。

? ? ? ? 作为非原生开发,虽然我们不希望此功能成为通常开发过程的一部分,但它的存在至关重要。 并且如果 React Native 没有导出你的应用程序需要的原生 API,那么你应该能够自己封装并且导出它!

下面,我们来深入聊一聊RN的初始化阶段,以及原生端与JS端互相通信的过程。

JS Core

????????开始之前,我觉得有必要简单介绍一下JS Core,众所周知,RN是通过JavaScriptCore提供的能力来与native交互的,JavaScript Core(简称 JSCore)是一个开源的框架,是?WebKit?的一部分,用最简单的话描述这个框架,它大概提供了两种能力:

在Native的环境下执行JS代码,不需要浏览器或Node的环境。把原生代码注入到JS中,提供给JS调用原生代码的能力。

????????JSExport 是整个 JSCore 里面最神奇的部分,也正是有了 JSExport 才让我们把 Native 对象暴露给 JS 环境非常的容易,简单说,通过JSExport我们可以把 Native 对象的属性和方法暴露给 JS 环境,先来看一段OC的接口代码:

@protocol NativeObjectExport <JSExport> @interface NativeObject : NSObject<NativeObjectExport> @property (nonatomic, assign) BOOL property1; @property (nonatomic, strong) id property2; - (void)method1:(JSValue *)arguments; - (void)method2; @end

????????NativeObject 可以被实现为任意的对象,只要他实现了NativeObjectExport那么这个协议里面的属性和对象就可以直接被 JS 环境使用,例如上面的 property1 和 method1。我们只要在 context 里面注入一个NativeObject的对象,就可以在 JS 环境放肆的与 Native 进行交互了:

context[@"helper"] = [NativeObject new]; [arguments[@"handler"] callWithArguments:@[object]];

在JS的环境中

// 可以通过property1访问native var prop = helper.property1; // 可以通过method1调用native的方法 helper.method1({ handler: function(object) {} });

????????当Native代码执行完 method1之后,可以通过这个 handler 回调到 JS 环境,JS 环境通过 function 的 object 拿到返回结果,这就是一个完整的流程。

初始化阶段

? ? ? ? 在RN初始化阶段,首先原生端会遍历开发者自定义的原生模块与RN框架提供的原生模块,将其注册到一张原生模块映射表中,然后,原生端也会将需要调用的JS模块注册到一张JS模块映射表中,需要注意的是,原生端并没有实现JS Module,只是有一份接口而已(Android需要实现JS接口,但是IOS不需要)。

? ? ? ? 紧接着JS Core会将两份映射表传入到JS侧,在JS侧,原生模块映射表会绑定到RN的提供的NativeModule上面,这样JS就可以通过NativeModule来调用Native提供的API了,对于JS模块映射表来说,JS侧会实现对应的JS方法,并注册进去。这样,Native就可以调用JS提供的方法了。并且JS可以以回调参数的形式来接受Native传来的数据。

请看源码:

import { NativeModules } from 'react-native'; const _ActionSheet = { showActionSheet: createNoop('ActionSheet.showActionSheet'), ...NativeModules.GAActionSheet, };

????????这是一段从Native SDK里拿出来的源码,可以看到,GAActionSheet就是native暴露的api,createNoop方法只是为了在native api还没加载完成时就调用时的一种兜底方案。

const ViewEventEmitter = new NativeEventEmitter(NativeModules.GAViewEventEmitter) ViewEventEmitter.addListener(self_callback);

? ? ? ? 相应地,上述代码展示了怎样通过ReactNative提供的NativeEventEmitter来订阅一个JS事件。

????????附一段react native源码来解释NativeModules到底是什么,由于React Native源码较多,已被我删减了很多,感兴趣的同学可以去react-native/Libraries/BatchedBridge/NativeModules.js底下查看,可以看到,NativeModules正是所谓的“原生模块映射表”。

? ? ? ? 另外,可以看到,通过global全局对象可以注册JS函数供Native调用,JS可以通过global来获取Native传递过来的数据。

function genModule( config: ?ModuleConfig, moduleID: number, ): ?{ name: string, module?: Object, } { const [moduleName, constants, methods, promiseMethods, syncMethods] = config; const module = {}; methods && methods.forEach((methodName, methodID) => { ... }); return {name: moduleName, module}; } global.__fbGenNativeModule = genModule; let NativeModules: {[moduleName: string]: Object, ...} = {}; if (global.nativeModuleProxy) { NativeModules = global.nativeModuleProxy; } else if (!global.nativeExtensions) { const bridgeConfig = global.__fbBatchedBridgeConfig; (bridgeConfig.remoteModuleConfig || []).forEach( (config: ModuleConfig, moduleID: number) => { const info = genModule(config, moduleID); if (!info) { return; } if (info.module) { NativeModules[info.name] = info.module; } }, ); }

????????这里展示了JSCore中如何获取并执行JS中注册好的global.__fbGenNativeModule = genModule

folly::Optional<Object> JSINativeModules::createModule( Runtime &rt, const std::string &name) { bool hasLogger(ReactMarker::logTaggedMarker); if (hasLogger) { ReactMarker::logTaggedMarker( ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str()); } if (!m_genNativeModuleJS) { m_genNativeModuleJS = rt.global().getPropertyAsFunction(rt, "__fbGenNativeModule"); } auto result = m_moduleRegistry->getConfig(name); if (!result.hasValue()) { return folly::none; } Value moduleInfo = m_genNativeModuleJS->call( rt, valueFromDynamic(rt, result->config), static_cast<double>(result->index)); CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null"; folly::Optional<Object> module( moduleInfo.asObject(rt).getPropertyAsObject(rt, "module")); if (hasLogger) { ReactMarker::logTaggedMarker( ReactMarker::NATIVE_MODULE_SETUP_STOP, name.c_str()); } return module; } 原生端调用JS

? ? ? ? ?接下来看一下原生端怎样调用JS,首先,Native在初始化时候创建好的JS模块映射表中找到需要被调用的JSModule_1,然后通过JSCore来传递被调用的模块名、方法名以及参数,传递到JS侧,JS在JS模块映射表中找到提前注册好的JSModule_1,然后执行并传入参数。这就是一个完整的原生端向JS端通信的过程。

JS端调用原生代码

????????类似地,看一下JS怎样调用Native方法,首先,JS在初始化时传过来的原生模块映射表中找到需要调用的NativeModule_1,然后通过JSCore传递被调用的模块名方法名以及参数到Native侧,Native拿到方法名之后找到注册好的NativeModule_1,然后执行相应的Native方法并传入参数。

? ? ? ? 那么,有人会想,在Native侧执行完之后,如果有返回值,怎么将返回值传到JS侧呢?当JS侧调用原生代码有返回值的时候,流程会相对复杂一些:

? ? ? ? ?如图,可以清晰的看到,如果有返回值的时候,在JS调用NativeModule_1的时候会生成一个回调ID,并将回调ID一起传递给Native,这个回调ID就是用来标识当Native传递执行结果时,JS拿到执行结果后,将执行结果分发给哪个JS变量。

接下来我们看一下JS是怎么注册到JS模块映射表中的:

const SharedStore = { pool: new Map(), setItem(key: String, value: any) { this.pool.set(key, value); }, getItem(key: string) { return this.pool.get(key); }, }; const BatchedBridge = globle.__fbBatchedBridge; BatchedBridge.registerCallableModule("SharedStore", SharedStore);

????????可以看到JS方法是通过调用__fbBatchedBridge上面的registerCallableModule函数来注册的,翻到RN源码的react-native/Libraries/BatchedBridge/BatchedBridge.js处,你会发现,__fbBatchedBridge其实就是一个消息队列,registerCallableModule就是往表里添加一项而已。

class MessageQueue { registerCallableModule(name: string, module: Object) { this._lazyCallableModules[name] = () => module; } } const BatchedBridge: MessageQueue = new MessageQueue(); Object.defineProperty(global, '__fbBatchedBridge', { configurable: true, value: BatchedBridge, }); module.exports = BatchedBridge;

????????下面看一下Android里是怎样调用JS方法的,可以看到在Android里先定义了JS模块的接口。

import com.facebook.react.bridge.JavaScriptModule // 定义js模块的接口 public interface SharedStore extends JavaScriptModule { void setItem(String key, Object value) } // 在ReactNative上下文对象创建后,调用JavaScript模块 ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager(); reactInstanceManager.addReactInstanceEventListener( new ReactInstanceManager.ReactInstanceEventListener() { @Override public void onReactContextInitialied(ReactContext context) { context.getCatalystInstance().getJSModule(SharedStore.class).setItem("example", "hello world!") } } )

? ? ? ? 在IOS内调用JS十分简单:

// 在ReactNative上下文创建后,直接调用JavaScript模块 RETBridge *bridge = //... [bridge enqueueJSCall:@"SharedStore" method:@"setItem" args:@[@"example", @"hello world!"] completion:^{ // 调用JavaScript模块完成的回调函数 }]

?

参考资料

极客时间 - 从原生方向探索React Native

知乎 -?JavaScriptCore 整体介绍

知乎 -?JavaScriptCore全面解析

React Native源码


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #reactnative通信原理 #在使用 #React #Native #中默认不可用的 #iOS #