Skip to content

Instantly share code, notes, and snippets.

@Caldis
Forked from kiok46/bridge.md
Last active March 23, 2021 08:33
Show Gist options
  • Save Caldis/8c659ba799408e722ab4f6f336cc1af3 to your computer and use it in GitHub Desktop.
Save Caldis/8c659ba799408e722ab4f6f336cc1af3 to your computer and use it in GitHub Desktop.
Bridge React-Native and JS

这个 Gist 的内容源自 Peggy Rayzis 在 Chain React 2017 上的演讲 Chain React 2017: Breaking Down Bridging in React Native by Peggy Rayzis


如果你遇到了这几种情况, 你或许需要依赖 Bridge 才能让 JS 代码和 Native 之间互相通信

  • 集成某些第三方 SDK
  • 高性能
  • 构建一个底层应用
  • 访问端到端的 API (例如摄像头, GPS等)

以下是 React-Native 的线程模型

基于此, Javascript 线程可以在运行时访问所有的 Native Modules, 他们会被注入到 Javascript 的上下文全局变量中, 一个可访问的的 Native Module 的数据结构大概长这样

{
  moduleName: 'ExampleModule',
  constants: {},
  methods: ['ChainReact', 'bridgingIsAwesome'],
  promiseMethods: [0],
  syncMethods: [1],
}

而以上这些信息的传输都依赖 Bridge, 而这些信息都依赖其中的 MessageQueue 模块进行处理, 其在 Javascript 和 Native 端都有对应的实现

当所有东西都通过 MessageQueue 在 Native 和 Javascript 之间互相传输时, 最重要的是确保 Javascript 的线程没有被 Block, 同时 MessageQueue 也没有任何拥塞.

一旦 MessageQueue 产生了拥塞现象, 你的 ReactNative App 马上就会被拖慢, 这时候会有一些奇妙的现象产生, 例如点击 UI 没有任何响应, 但是 ScrollView 仍然可以正常响应滚动操作.

虽然这类滚动的 Touch 事件由 Native 端产生, 并随后通过 Bridge 发送至 Javascript 端, 并从 Bridge 返回某些结果, 但 ScrollView 的滚动并不依赖这些结果, 因此即便 Bridge 拥塞, 滚动也不会受到影响.

另外, 所有这些通信都是经过序列化的异步通信, 他们会分批在 Bridge 中进行传送, 这之间的模型有点类似服务器与客户端进行通信

如何才能避免 Bridge 阻塞 ?

  • 尽量避免使用那些需要大量用到 Bridge 传输数据的 API (例如 Animation, 将其设置成 Native Derive 模式可以保证 Bridge )
  • 保持数据扁平, 由于在 Bridge 传输数据时, 需要将其进行序列化/反序列化的操作, 这个操作会产生极大的开销, 保持数据结构的扁平有利于加快这个过程
  • 保持数据简介, 例如不要将图片转成 Base64 来传输, 这会极大地拖慢 Bridge 进行序列化的过程
  • 仅在某一端编写你的逻辑, 避免数据来回传递

如何检视 ReactNative 的 MessageQueue

type BridgeData = {
   type: number,
   module: ?string,
   method: string | number,
   args: any
}

每一条在 Bridge 中传输的数据结构类似这样, 从网络请求到View创建, 每一个 Message 都有着相同的数据结构

type: number,

这个属性用于标识 Message 的来源, 0 代表从 Native 发往 Javascript, 1 则反之

这里有一个库可以用于查看这些信息 RN Snoopy, 或者你可以在 Javascript 端插入如下简单的代码, 即可在控制台打印出每一条 Message

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'
MessageQueue.spy(i => console.log(i))

原生 UI 组件

ViewManager 负责管理 Native 端的 UI 渲染和访问

不论是在 iOS 还是 Android 平台上, React Native 都会在 Native 端构造一个 ViewManager 负责绘制由 Javascript 端传来的布局命令, 而且每一个 Bridge 仅会创建唯一的一个 ViewManager

同时, Bridge 可以在运行时获得所有 Native Modules 的信息, 而 ViewManager 就像是一个独特的 Native Modules, 他运行在 ShadowQueue 上执行特定的操作并向 Javascript 端暴露对应的 UI 实例

当我们创建一个View时, 我们的 JSX 代码会被解析为一个个命令, 用于告知其需要绘制一个怎么样式的 UI. 他同时也会检查对应的View类型, 确保我们没有传入错误的参数

当 ViewManager 创建了一个 View, 并且将其的引用保存在了 Bridge 中, 我们就可以在 Javascript 中以一个 React 组件的形式访问到这个 UI 实例了 (Refs)

  • 一个 ViewManager 实例对应一个 Bridge, ViewManager 管理所有的 View
  • ViewManager 负责声明常量, 设置属性, 处理事件, 创建 View
  • 当处理事件时, ViewManager 还会负责代理所有由他创建的 View 的所有委托, 并给 Javascript 发去对应的事件
  • View 会管理其自己的生命周期和事件和子视图, 并通过事件的形式与 ViewManager 交互

NATIVE MODULES (CROSS-PLATFORM CONCEPTS)

上面提到的 UI COMPONENTS 就像是一个特殊的 Module, 它只是会返回一个 UI 的实例, 而 Native Modules 则更简单, 他不会渲染任何 UI, 他仅仅负责转发和管理一些方法和常量

在 Native 端, 这类组件可以直接调用某些原生方法, 如果需要与 Javascript 端进行交互, 可以使用 Callback 或 Promise 方法, 或 Event 的形式通过 Bridge 进行传递

  • 每一个 Bridge 只会创建一个对应的 Native Module 实例
  • Native Module 可以通过 Callback 或 Promise 方法, 或 Event 的形式将数据传到 Javascript 端
  • Javascript 端通过调用 Bridge 中暴露出来的 Module 的方法来传递数据
  • Javascript 端访问常量是同步操作, 因为只需要访问 Bridge 中保存的引用

Bridge 带来的问题

  • 在不同语言和文件之间的上下文切换带来极大的开销
  • 需要很多 Boilerplate 才能抹平不同设备和版本之间的运行时问题, 例如你需要继承 RCTViewManagers 来创建一个派生类才能在 Android 中重写某些关键的方法

react-native-create-bridge CLI

https://github.com/peggyrayzis/react-native-create-bridge

cool!

@Caldis
Copy link
Author

Caldis commented Mar 23, 2021

截屏2021-03-23 下午3 40 13
截屏2021-03-23 下午4 17 40
截屏2021-03-23 下午4 18 12
截屏2021-03-23 下午4 18 22

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment