这一次,我把RN最核心的Bridge撸干净了
本文以 0.59.10
版本的 React Native
为分析对象
一、架构设计
React Native
架构上由 JS
、 Native
以及连接两者的 Bridge
三部分组成
- JS 部分:由
JSX
实现的视图 以及 调用Native
能力实现的业务逻辑组成 - Native 部分:管理
UI
更新即交互 - Bridge 部分:桥接
JS
和Native
的通讯交互
注意:
0.59.10
版本虽已引入了JSI
,但通讯依旧通过Bridge
进行桥接
1、当前架构
1.1、线程模型
- JS thread:负责处理逻辑层。Metro(打包工具)将
React
打包成单一JS
文件,传递给JSC
执行
JSC
(JavascriptCore) 是JS
代码的运行环境,真正执行JS
代码的是RCTJSCExecutor
对象,执行完成后返回一个数组
- Native thread(Main Thread/UI Thread):负责原生渲染和提供原生能力
NativeModules
在启动阶段全部加载,生成一张映射表,可在两端调用方法时精准地找到对应的方法
- Shadow Thread:负责布局计算和构造
UI
界面。创建Shadow Tree
来模拟React
结构树,类似于虚拟dom
RN
使用的Flexbox
布局原生不支持,通过Yoga
转换为原生平台支持的布局方式
1.2、模拟通讯
- 当
JS thread
收到React
源码时,需对其序列化,生成一条消息发送给Bridge
// 序列化前 <View style={{ "width":200 }}/> // 序列化后 UIManager.createView([343,"RCTView",31,{"width":200}])
Bridge
收到消息后转发给Shadow Thread
- 当
Shadow Thread
收到信息后,需对其反序列化,创建Shadow Tree
,流传给Yoga
生成布局信息后发送给Bridge
Bridge
收到消息后转发给Native thread
Native thread
收到信息后,同样先反序列化,根据布局信息进行绘制
1.3、Bridge
的特点及不足
1.3.1、异步
消息队列是异步的,其好处是不阻塞,但由于三个线程的数据无法共享,需各自保存、各自维护。任何交互都需Bridge
进行异步处理,因无法保证处理时间,可能会出现空白的问题,比如瀑布流滚动
1.3.2、序列化
序列化设计可保证所有 UI
都可相互转换,甚至可以让JS
代码运行在任意的 JS
引擎上。但每次都需经历序列化和反序列化,开销极大
1.3.3、批处理
对Native
调用进行排队,批量处理
2、新架构
从当前的架构中,不难看出两端封闭的交互方式已触及性能的瓶颈了,优化的手段集中于 Bridge
,进而官方推出了新架构,这儿,没有了 Bridge
的烦恼 🚀🚀🚀
2.1、JavascriptInterface(JSI)
JSI
是 一个可运行于多种 JS
引擎的中间适配层,可实现 JS
直接调用 c++
层的对象和方法。有了 JSI
,RN
应用不仅可以运行于 JSC
,还可以执行于 Chrome
的V8
或 hermes
引擎,提高了解析执行的速度
划重点:有了
JSI
后,JS
可以直接调用其他线程,实现同步通信机制,另外数据可以直接引用,不需要拷贝
2.2、Fabric
Fabric
是新架构的渲染系统,能够在 UI
线程上同步调用JS
代码,对应新架构图 Renderer
和 Shadow Thread
划重点:有了
Fabric
后,可支持优先级渲染,比如React Concurrent
的中断渲染功能 和 允许开发者在React
中更合理的组织请求数据代码的Suspense
模式
2.3、TurboModules
TurboModules
主要和原生应用能力相关,对应新架构图上的Native Modules
,其带来的性能提升是Native
模块懒加载
划重点:有了
TurboModules
后,可以实现按需加载Native
模块,减少启动时间,提高性能
二、启动&渲染
1、启动阶段
1.1、创建 App 根视图及JS执行对象
1.1.1、创建RCTRootView会同时创建RCTBridge,适用未拆包应用
RCTRootView
是 RN
的根容器,承载着所有子视图的功能,其子视图RCTRootContentView
直接承载视图的对象
// initWithBundleURL:`jsbundle`的路径 moduleName:启动应用的名称 initialProperties:初始化参数 launchOptions:App启动参数 [[RCTRootView alloc] initWithBundleURL:[NSURL fileURLWithPath:panelPath] moduleName:@"Demo" initialProperties:initialProps launchOptions:launchOptions]; RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
1.1.2、创建RCTBridge再创建RCTRootView,适用分包/特殊处理的应用
若是分包的应用 或 需提取 RCTBridge
参数/方法 的话,可以先创建 RCTBridge
再创建 RCTRootView
。其中参数 moduleProvider
可配置 Bridge
具体访问哪些 NativeModules
在需控制权限的应用作用比较大
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:[[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"] moduleProvider:nil launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Demo"
1.2、batchedBridge初始化RN环境
RCTBridge
初始化时会保存 initWithBundleURL
,并创建一个 RCTCxxBridge
的实例 batchedBridge
去初始化 RN
环境
batchedBridge
启动后发送加载通知batchedBridge
初始化生成模块配置表batchedBridge
创建并初始化JS线程batchedBridge
注册NativeModulesbatchedBridge
开始加载JS代码
1.3、执行JS代码
加载得到的代码是jsbundle
,主要由三部分组成
1.3.1、环境变量及方法定义
- 第一行定义了运行时环境变量,用于表明所运行的node环境处于生产环境以及记录脚本启动的时间
- 第二到十行全局定义了RN环境启动的基本方法
1.3.2、RN框架及业务代码定义
第十一行开始进入 RN
框架、第三方库以及个人代码定义部分,这部分调用了全局 __d
方法对代码中的方法和变量进行定义,其接受三个参数
- r:该模块的定义,即代码逻辑
- n:模块的moduleId,打包系统默认按照数字的递增的形式来定义该id
- i:依赖数组
1.3.3、引用与启动入口
倒数两行调用了全局 __r
方法将 RN
应用运行起来,其接受moduleId
一个参数
- 若该模块没有被初始化,则尝试加载并初始化
- 若未找到该模块,则抛出错误 'Requiring unkonwn module xxx'
执行完 batchedBridge
发送加载完成通知,RCTRootView
接收到通知,创建RCTRootContentView
立即调用 AppRegistry.runApplication
,通过 JSC
以消息的形式将业务启动参数发送给 batchedBridge
的消息队列 MessageQueue
2、渲染阶段
2.1、渲染视图信息
JS
线程将视图信息传递给Shadow
线程- 创建
Shadow Tree
映射React
组件树,流传给Yoga
Yoga
将flexbox
布局生成原生布局信息Shadow
线程将通过一系列计算的完整视图信息传递给Native
线程Native
线程将匹配到的组件按层级渲染到RCTRootContentView
上- 完成渲染
2.2、渲染事件信息
Native
线程将相关信息打包成事件消息传递到Shadow
线程- 根据
Shadow Tree
建立的映射关系生成相应元素的指定事件 - 将事件传递到
JS
线程,执行对应的JS
回调函数
三、通讯逻辑
1、通讯共识
1.1、通讯基础 RCTBridgeModule
RCTBridgeModule
协议允许注册模块以及模块方法,在模块注册时会在原生端和JS
端同时生成一份配置文件 remoteModuleConfig
,它是一张映射表,可在两端调用方法时精准地找到对应的引用
1.1.1、JS
端可通过 __fbBatchedBridgeConfig
查看 remoteModuleConfig
{ "remoteModuleConfig":{ [ "RCTPushNotificationManager", // 模块名称 { "initialNotification": null, // 属性对象 }, [ "fn1", // 方法列表 "fn2", ... ], [2, 7], // 异步方法索引 [1, 4], // 同步方法索引 ] } }
1.2、异步通讯 MessageQueue
MessageQueue
主要承担异步事件交互通知的任务,所有的通讯和交互事件都会推进池中,再通过规则对池子进行读取和刷新。默认情况下 MessageQueue
每 5ms
会进行一次 flush
操作,flush
时发现新的消息会按照消息的参数进行逻辑的执行
由于
MessageQueue
是被动接收数据,主动定时刷新的形式,因此在调用原生方法时,原生端并不会立即执行
2、事件通知
事件通知即发送通知和注册监听,可通过以下 API 进行注册和发送通知
- NativeAppEventEmitter
- DeviceEventEmitter
- NativeEventEmitter
3、原生端暴露原生事件
JS
端可调用原生端的关键是两端都实现了 RCTBridgeModule
协议,原生端在需要暴露给JS
端的方法前面加宏RCT_EXPORT_METHOD
修饰
4、JS端调用原生事件
JS
端实现需调用Native
方法- 查找
JS thread
中的方法配置表remoteModuleConfig
,将要执行的任务写入MessageQueue
异步队列 Bridge
执行队列任务Native thread
根据参数匹配配置,进而找到对应的原生模块及方法,执行原生实现的逻辑,将执行结果打包成消息传递给Bridge
JS
端根据返回的id
找到执行的方法,执行并返回结果

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
数据结构与算法的实际应用(有向图)——有依赖的任务并行处理框架
背景 一开始接到一个技改需求,需要将我们系统的一些首页查询接口进行优化,合并为一个聚合接口提供给前端;最初与前端同学进行交流,认为这些首页查询接口没有依赖关系,因此对于我来说解决方案比较简单,直接将需要查询的接口进行并行处理即可: 其中每一个查询抽象成了一个Function,在子线程中调用apply方法执行。 但是在与前端同学联调时,发现其中一个接口的入参依赖另一个接口的结果,本想让前端给我传过来,但是这样的话又有一个接口会分离出去,影响首页加载速度,于是决定还是我后端想办法处理;如此一来,就需要解决查询时的前后依赖关系,一开始想了几种方案: 使用CountDownLatch进行等待 拿到前一个依赖任务的Future,调用get方法等待 有依赖的任务串行执行 前两种方案大同小异,其实都是利用线程间的等待机制,待前置查询执行完成后,唤醒当前线程继续执行查询;第三种方案则简单粗暴,由于时间较紧,经过短暂思考后权利利弊还是决定用第三种方案,毕竟我现在只有两个子查询是有依赖的,这样做也是效率最高的办法;于是在生成查询的supplier中做了下手脚,将有依赖关系的Function通过andThe...
- 下一篇
K8S-基于Kubeadm安装K8S集群
K8S部署指南 机器环境准备 首先准备3台虚拟机,本文用的VM16.2作为虚拟机,CentOS Linux release 7.9.2009 (Core) 作为镜像 没有什么可说的正常的网上一堆教程,但是我们需要注意的是 1.节点CPU核数必须是>= 2核 ,否则k8s无法启动 2.我是用的DNS网络:最好设置为 本地网络连通的DNS,否则网络不通,无法下载一些镜像 虚拟机设置的NAT模式 3.linux内核:linux内核必须是 4 版本以上,因此必须把linux核心进行升级 uname -r 可以查看你的内核 依赖环境 #1.设置主机名称 hostnamectl set-hostname k8s-master01 hostnamectl set-hostname k8s-node01 hostnamectl set-hostname k8s-node02 --------------------------------------------------------------------- #查看是否设置成功 hostname --...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7设置SWAP分区,小内存服务器的救世主