React Native深入理解Native与RN通信原理
Posted 骑着代马去流浪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Native深入理解Native与RN通信原理相关的知识,希望对你有一定的参考价值。
在使用 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深入理解Native与RN通信原理的主要内容,如果未能解决你的问题,请参考以下文章
《React-Native系列》3RN与native交互之CallbackPromise