跨端框架一些原理分析

JavaScript | 2021-05-07 12:36:32 2324次 3次

React Native

v2-575c6e101c10f9fcec2ecd432004d959_720w.png


三线程

1. JS Thread 执行线程, 负责逻辑层面的处理, Metro 将 React 源码打包成 bundle 文件, 然后传给 JS 引擎执行, 现在 IOS 和 Android 统一的是 JSC

2. UI Thead 主要主责原生渲染 Native UI 和调用原生能力 (Native Module)

3. Shadow Thead 这个线程主要创建 Shadow Tree 来模拟 React 结构树, RN 使用 Flexbox 布局, 但是原生不支持, Yoga 引擎就是将 Flexbox 布局转换为原生布局的


Bridge

异步。这些消息队列是异步的,无法保证处理事件。

序列化。通过JSON格式来传递消息,每次都要经历序列化和反序列化,开销很大。

批处理。对Native调用进行排队,批量处理。


通信的桥梁,比如如下 RN 代码:

<View style={{  width: 200 }}/>

JS thread 会先对其序列化,形成下面一条消息:

UIManager.createView([343,"RCTView",31,{"width":200}])

通过 Bridge 发到 ShadowThread。Shadow Tread 接收到这条信息后,先反序列化,形成 Shadow tree,然后传给 Yoga,形成原生布局信息。

接着又通过 Bridge 传给 UI thread。

UI thread 拿到消息后,同样先反序列化,然后根据所给布局信息,进行绘制。

从上面过程可以看到三个线程的交互都是要通过 Bridge,因此瓶颈也就在此。

所以为了性能可以减少 json 大小,减少通信或者不通信。


app运行过程

1. 用户点击 App 图标

2. UIManager 线程: 加载所有 Native 库和 Native 组件比如 Images, View, Text

3. 告诉 JS 线程, Native 部分准备好了, JS 侧开始加载 bundle 文件

4. JS 通过 Bridge 发送一条 Json 数据到 Native , 告诉 Native 怎么创建 UI, 所有的 Bridge 通信都是异步的, 为了避免阻塞 UI

5. Shadow 线程最先拿到消息, 创建 UI 树

6. Yoga 引擎获取布局并转为 Native 的布局

7. 之后 UI Manager 执行一些操作展示 Native UI


React Native 改版

1. JSI 将会替换 Bridge 是一个 JS 引擎接口, 它是 JavaScript Interface 的缩写, 一个用 C++ 写成的轻量级框架, 作用就是通过 JSI, JS 可以直接获取 C++ 对象引用, 然后调用对应方法, 为了让 JS 和 Native 相互感知, 不需要在通过 JSON 数据传输通信, 将允许 Native 对象被导出成 JS 对象, 反过来也可以, 相互通信的方式也会变为同步, 架构的其他部分是基于这个的, JSI 的另一优势是磨平了 JS 引擎的差异, 使用 JSI 不需要关心 JavaScript Core 还是 Hermes, JSI 底层都消化了, 因此只要基于 JSI 的接口编写即可。

2. Fabric 是 UI Manager 的新名称, 将负责 Native UI 渲染, 和当前 Bridge 不同的是, 可以通过 JSI 导出自己的 Native 函数, 在 JS 层可以直接使用这些函数引用, 反过来 Native 可以直接调用 JS 层, 从而实现同步调用, 这带来更好的数据传输和性能提升, 另外一个好处就是 Fabric 也支持渲染优先级, 比如 React 里的 Concurrent 和 Suspense 模式。


app运行过程

1. 点击 App 图标

2. Fabric 加载 Native,不再加载 Native Module

3. 然后通知 JS 线程 Native 侧准备好了, JS 侧会加载所有的 bundle JS 文件, 里面包含了所有的 JS 和 React 逻辑组件

4. JS 通过一个 Native 函数的引用, 调用到 Fabric, 同时 Shadow Node 创建一个和以前一样的 UI 树

5. Yoga 进行布局计算, 把基于 Flexbox 的布局转化为 Native 端的布局

6. Fabric 执行操作并显示 UI


Flutter

Flutter 拥有自己的开发工具,开发语言、虚拟机,编译机制,线程模型和渲染管线,和 Android 相比,它也可以看做一个小型的 OS。

它的结构是分层模式:

Embedder:操作系统适配层,实现渲染 Surface 设置、线程设置等。

Engine:实现 FLutter 渲染引擎、文字排版、事件处理、Dart 运行时等功能。包括了 Skia 图形绘制库、Dart VM、Text 等,其中 Skia 和 Text 为上层接口提供了调用底层渲染和排版的能力。

Framework:是一个用 Dart 实现的 UI SDK,从上之下包括了两大风格组件库、基础组件库、图形绘制、手势识别、动画等功能。

Flutter 渲染原理:

v2-ac6f5e340ef75418f57ce1dd72939185_720w.png

GPU 的 VSync 信号同步给到 UI 线程,UI 线程使用 Dart 来构建抽象的视图结构(这里是 Framework 层的工作),绘制好的抽象视图数据结构在 GPU 线程中进行图层合成(在 Flutter Engine 层的工作),然后提供给 Skia 引擎渲染为 GPU 数据,最后通过OpenGL 或者 Vulkan 提供给 GPU。

在 Flutter 中,构建视图数据结构、布局、绘制、合成、与 Engine 的数据同步和通信放到了 Framework 层,而光栅化则放在了 Engine 层中。


Flutter 的视图数据抽象分为3部分,分别是 Widget、Element、RenderObject。

Widget 采用 immutable 思想,当视图配置信息发生变化时,Flutter 会重建 Widget 来进行更新。

Element 则类似 react dom diff 部分,进行一层对比,将需要修改的部分同步到 RenderObject。

RenderObject 完成布局和绘制,采用深度优先机制遍历渲染对象树,确定树中各个对象的位置和尺寸,并把它们绘制到不同的图层上。绘制完毕后,合成和渲染的工作则交给 Skia 处理。


光栅化

直接光栅化:它就是直接将所有可见图层的 DisplayList 中可见区域内的绘图指令,执行光栅化直接在目标 Surface 的像素缓冲区上生成像素的颜色值。如果是完全的直接光栅化,这时,其实就不需要后面合成的步骤了。

间接光栅化像 Android 和 Flutter,它们的 UI Toolkit 主要使用直接光栅化的策略,但是同时也支持间接光栅化。它们允许为指定图层分配额外的像素缓冲区,该图层的光栅化会先写入自身的像素缓冲区,渲染引擎再将这些图层的像素缓冲区通过合成输出到目标 Surface 的像素缓冲器。

无论是直接光栅化还是间接光栅化,它们都是所谓的同步光栅化,也就是说光栅化和合成通常都在同一个线程,即使不在同一个线程,也会通过线程同步的方式来保证光栅化和合成的执行顺序。这种同时使用直接和间接光栅化的方式,有时我们也称为即时光栅化(On Demand Rasterization)。


异步分块光栅化光栅化是以分块为单位进行,每个光栅化任务执行对应图层的对应分块区域内的绘图指令,结果写入该分块的像素缓冲区。光栅化和合成不在同一个线程执行,并且不是同步的,如果合成过程中某个分块没有完成光栅化,那它就会保留空白或者绘制一个棋盘格的图形(Checkerboard)

异步光栅化占用内存大,首屏绘制不如同步光栅化速度快,对于变化的内容缓存区也会无效,需要重新光栅化;但是对于图层动画,图层内容无变化,则异步光栅化通过缓存可以提升效率,对于惯性滚动也是如此。


参考来源【1】

参考来源【2】

3人赞

分享到: