在使用 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 的一部分,用最简单的话描述这个框架,它大概提供了两种能力:

  1. 在Native的环境下执行JS代码,不需要浏览器或Node的环境。
  2. 把原生代码注入到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() {@Overridepublic 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源码

【React Native】深入理解Native与RN通信原理相关推荐

  1. ReactNative与iOS通信原理解析-通信篇

    文章首发个人博客: ReactNative与iOS通信原理解析-通信篇 导语:其实原本是想编写一篇  react-native (下文简称 rn) 在  iOS 中如何实现  jsbridge 的文章 ...

  2. React-Native系列Android——Native与Javascript通信原理(一)

    React-Native最核心的是Native与Javascript之间的通信,并且是双向通信.Native层到Javascript层,Javascript层到Native层.虽说是两个方向,但实现上 ...

  3. Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)

    摘要:本节主要来讲解Android10.0 Native层HIDL服务的注册原理 阅读本文大约需要花费23分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Androi ...

  4. Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)

    摘要:本节主要来讲解Android10.0 Native层HIDL服务的获取原理 阅读本文大约需要花费23分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Androi ...

  5. RN通信机制和渲染流程

    前言 React Native与传统的HybirdApp最大区别就是抛开WebView,使用JSC+原生组件的方式进行渲染,那么整个App启动/渲染流程又是怎样的呢? 一.整体框架 RN 这套框架让 ...

  6. H5如何与原生App(ios,安卓,RN)通信?

    关注前端公众号 前端每日一博,每日为你分享一篇前端系列博文.永不断更! 前言 为了提高开发效率,开发人员往往会使用原生app里面嵌套前端h5页面的快速开发方式,这就要涉及到h5和原生的相互调用,互相传 ...

  7. (三.0)通过FPGA实现以太网通信原理及理解

    0.0 OSI模型的简单理解 OSI(Open System Interconnect),即开放式系统互联.一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互联模型.该体系 ...

  8. 学习通信原理之——从实验中理解频谱/功率谱/功率谱密度(MATLAB演示)

    我的个人博客文章链接如下:学习通信原理之--从实验中理解频谱/功率谱/功率谱密度(MATLAB演示) 前言 最近在复习通信原理,每次到了功率谱这一块就感到困惑,每次都要去查,我觉得不能再这样循环下去了 ...

  9. 【通信原理 入坑之路】——深入、详细地理解通信里面“卷积”概念

    文章目录 一.多项式乘法和"卷积" 二."卷积"的计算公式 2.1 实例:利用卷积计算两个信号相乘 三.卷积的生动理解 3.1 卷积能够用来解决什么问题? 3. ...

最新文章

  1. [导入]解决“Internet Explorer 无法打开 Internet站点已终止操作”问题
  2. maven开发mybatis 让*.xml 拷贝到classes目录下
  3. 第十七单元 Samba服务
  4. Java NIO3:缓冲区Buffer
  5. excel趋势线公式导出_Java 添加、读取、删除Excel中的图表趋势线
  6. android随机匹配,基于进程调度的Android真随机数和设备特征生成方法的研究
  7. 小米回应设备被谷歌禁用;苹果中国宣布 2019 款 iPad 降价;Wine 5.0-rc4 发布 | 极客头条...
  8. matepad和鸿蒙,华为MatePadPro配置速看:搭载了鸿蒙系统的它真比iPadPro强?
  9. 为什么安装了python桌面没有图标怎嘛办_安装了软件找不到图标怎么办_电脑软件安装了为什么不见图标...
  10. 周末C语言基础学习小结
  11. 光电反射传感器 红外对管模块
  12. vue实现动态二维码完成签到功能
  13. 腾讯云服务器云硬盘如何快速扩容!
  14. Q版人物动作怎么画?如何画好Q版人物动作?
  15. 2021了,为什么说音视频技术是技术风口?Android音视频开发这么吃香
  16. 开关电源-反激+单级PFC超低纹波超低THD
  17. opencv 基于sift的多张图片全景图拼接
  18. Android Studio_day02 实际应用登录界面
  19. 逻辑学探幽 Part1
  20. apollo学习基础之三[适配器adapter学习]

热门文章

  1. macromedia dreamweaber 8 激活码
  2. 720phi10p 和 720p有什么区别_很多人都在都使用视频采集卡,那视频采集卡有几种?有什么特点和区别?...
  3. C#使用EmguCV库介绍(一)
  4. 图解法求最优解的例题_简单的线性规划求最优解
  5. JavaScript——jQuery速查手册
  6. [iOS]Xcode快捷键和添加注释
  7. STC 51单片机43——看门狗
  8. 华为c8815手机在开发Android调试时logcat不显示输出信息的解决办法
  9. 创业团队产品如何战胜大公司的抄袭 腾讯抄你肿么办?
  10. 4核处理器_【装机帮扶站】第489期:盘点一波100元以下的4核/6核/8核/10核/12核处理器(2)...