关于flutter:Flutter渲染之WidgetElementRenderObject

41次阅读

共计 4832 个字符,预计需要花费 13 分钟才能阅读完成。

一、Flutter 架构

家喻户晓,Flutter 是由 Google 推出的开源的高性能跨平台框架,一个 2D 渲染引擎。在 Flutter 中,Widget 是 Flutter 用户界面的根本形成单元,能够说所有皆 Widget。与 Weex 和 RN 框架应用的 JsCore 转化的中间层不同,Flutter 采纳的是全新的架构计划,领有本人的渲染引擎和 Dart 下层,每一层都建设在前一层的根底之上,并且下层比上层的应用频率更高,其框架架构如下图所示。

能够看到,自下而上,Flutter 分为 Embedder、Engine 和 Framework 三层。其中,Embedder 是操作系统适配层,次要负责 Surface 渲染设置,线程设置,以及平台插件等平台相干个性的适配;Engine 层负责图形绘制、文字排版和提供 Dart 运行时,Engine 层具备独立虚拟机,正是因为它的存在,Flutter 程序能力运行在不同的平台上,实现跨平台运行;Framework 层则是应用 Dart 编写的一套根底视图库,蕴含了动画、图形绘制和手势辨认等性能,是应用频率最高的一层。

  • Flutter Embedder:Embedder 是 Flutter 的操作系统适配层,又称为嵌入层,通过该层能够把 Flutter 嵌入到各个不同的平台下来。Embedder 的次要工作包含 Surface 渲染设置、线程设置、事件循环以及插件的平台适配等。
  • Flutter Engine:纯 C++ 实现的 SDK,其中包含 Skia 引擎、Dart 运行时、文字排版引擎等。它是 Dart 的一个运行时,它能够以 JIT 或者 AOT 的模式运行 Dart 代码。这个运行时还管制着 VSync 信号的传递、GPU 数据的填充等,并且还负责把客户端的事件传递到运行时中的代码。
  • Flutter Framework:纯 Dart 实现的 SDK,提供了一整套自底向上的根底库,用于解决动画、绘图和手势。并且基于绘图封装了一套 UI 组件库,而后依据 Material 和 Cupertino 两种视觉格调辨别开来。在平时利用开发中,与开发者打交道最多的就是这一层,并且最多的就是各种 Widget。

二、渲染流程

不论是什么渲染框架,其根本的原理都是:个别以 60Hz 的固定频率刷新,每一帧图像绘制实现后,会持续绘制下一帧,而后显示器就会收回一个 Vsync 信号,按 60Hz 计算,屏幕每秒会收回 60 次这样的信号。CPU 计算好显示内容提交给 GPU,GPU 渲染好交给显示器显示。

在 Flutter 中,渲染会用到很多的线程,次要是 UI 线程和 GPU 线程,下图是 Flutter App 线程的运作原理图。

上面重点看一下 UI 线程和 GPU 线程。

UI Task Runner

UI Task Runner 用于执行 Root Isolate 代码,它运行在线程对应平台的线程上,属于子线程。同时,Root isolate 在引擎启动时会绑定不少 Flutter 须要的函数办法,这些绑定的函数能够提交渲染帧给 Engine 层执行渲染操作,下图演示了 Widgets 生成 Layer Tree 的过程。

对于每一帧,引擎通过 Root Isolate 告诉 Flutter Engine 有帧须要渲染,平台收到 Flutter Engine 告诉后会创建对象和组件并生成一个 Layer Tree,而后将生成的 Layer Tree 提交给 Flutter Engine。此时,只生成了须要绘制的内容,并没有执行屏幕渲染,而 Root Isolate 就是负责将创立的 Layer Tree 绘制到屏幕上,因而如果线程过载会导致卡顿掉帧景象。

除了用于解决渲染之外,Root Isolate 还须要解决来自 Native Plugins 的音讯响应、Timers、MicroTasks 和异步 IO。如果的确有无奈防止的沉重计算,倡议将这些耗时的操作放到独立的 Isolate 去执行,从而防止利用 UI 卡顿问题。

GPU Task Runner

GPU Task Runner 用于执行设施 GPU 指令,UI Task Runner 创立的 Layer Tree 是跨平台的。也就是说,Layer Tree 提供了绘制所须要的信息,然而由谁来实现绘制它是不关怀的。

GPU Task Runner 的次要责任就是负责将 Layer Tree 提供的信息转化为平台可执行的 GPU 指令,同时它也负责管理每一帧绘制所须要的 GPU 资源,包含平台 Framebuffer 的创立,Surface 生命周期治理,以及 Texture 和 Buffers 的绘制机会等,下图 GPU Task Runner 的工作流程。


UI Runner 和 GPU Runner 运行在不同的线程。GPU Runner 会依据目前帧执行的进度去向 UI Runner 申请下一帧的数据,在工作沉重的时候还可能会呈现 UI Runner 的提早工作。不过这种调度机制的益处在于,确保 GPU Runner 不至于过载,同时也防止了 UI Runner 不必要的资源耗费。

GPU Runner 能够导致 UI Runner 的帧调度的提早,GPU Runner 的过载会导致 Flutter 利用的卡顿,因而在理论应用过程中,倡议为每一个 Engine 实例都新建一个专用的 GPU Runner 线程。

三、Widget、Element 和 RenderObject

要了解 Flutter 的渲染原理,那么就必须理解 Widget、RenderObject 和 Element 及其作用。总的来说,Flutter 调用 runApp(rootWidget),将 rootWidget 传给 rootElement,做为 rootElement 的子节点,生成 Element 树,由 Element 树生成 Render 树,如下图所示。

从下面的介绍中,咱们隐约晓得了 Widget、RenderObject 和 Element 的作用,简略的介绍一下。

  • Widget:Widget 的次要作用是用来保留 Element 信息的(包含布局、渲染属性、事件响应等信息),自身是不可变的,Element 也是依据 Widget 外面保留的配置信息来治理渲染树,以及决定本身是否须要执行渲染。
  • RenderObject:RenderObject 做为渲染树中的对象存在,次要作用是解决布局、绘制相干的事件,而绘制的内容是 Widget 传入的内容。
  • Element:Element 能够了解为是其关联的 Widget 的实例,寄存视图构建的上下文数据,能够通过遍历 Element 来查看视图树,Element 同时持有 Widget 和 RenderObject 对象。

Flutter 通过 Widget 树中的每个控件创立不同类型的渲染对象,组成渲染对象树,而渲染对象树在 Flutter 中的展现分为四阶段:布局、绘制、合成及渲染。其中,布局和绘制由 RenderObject 负责实现,Flutter 采纳深度优先机制遍历渲染树对象,确定树中每个对象的地位和尺寸,并把他们绘制到不同的图层上,而合成及渲染则交给 Skia 实现。

下图展现了 Widget、Element 和 RenderObject 的关系。

3.1 Widget

在 Flutter 中,万物皆是 Widget,无论是可见的还是功能型的,上面是官网对 Widget 的介绍。

  • Widget 的作用是用来保留 Element 的配置信息的。
  • Widget 自身是不可变的。
  • Element 依据 Widget 外面保留的配置信息来治理渲染树。
  • Widget 能够屡次的插入到 Widget 树中,每插入一次,Element 都要从新装载一遍 Widget。
  • Widget 外面的 key 属性用来决定依赖这个 Widget 的 Element 在 Element 树中是更新还是移除。

上面是 Widget 源码。

abstract class Widget extends DiagnosticableTree{const Widget({ this.key});
  final Key key;
  
  @protected
  Element createElement();
  
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
   return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Widget 有两个重要的办法,一个是通过 createElement 来创立 Element 对象的,一个是依据 key 来决定更新行为的 canUpdate 办法。

3.2 RenderObject

  • RenderObject 是做为渲染树中的对象存在。
  • RenderObject 不定义束缚关系,也就是不会对子控件的布局地位、大小等进行治理。
  • RenderObject 中有一个 parentData 属性,这个属性用来保留其孩子节点的特定信息,如子节点地位,这个属性对其孩子是通明的。

RenderObject 的源码如下。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ParentData parentData;
  Constraints _constraints;
  void layout(Constraints constraints, { bool parentUsesSize = false}) { }
  void paint(PaintingContext context, Offset offset) { }
  
  void performLayout();
  void markNeedsPaint() {}
}

能够看出,RenderObject 的次要作用就是绘制和布局。RenderObject 在 Flutter 中的作用分为四个阶段,即布局、绘制、合成和渲染。其中,布局和绘制在 RenderObject 中实现,Flutter 采纳深度优先机制遍历渲染对象树,确定树中各个对象的地位和尺寸,并把它们绘制在不同的图层上。绘制结束后,合成和渲染的工作则交给 Skia 实现。

3.2 Element

  • Element 是关联的 Widget 的实例,并且关联在 Widget 树的特定地位上。
  • Widget 是不可变的,一个 Widget 能够同时用来配置多个子 Widget 树,而 Element 就用来代表特定地位的 Widget。
  • Widget 是不可变的,而 Element 是可变的,Element 决定是否须要刷新界面。
  • 一些 Element 只能有一个子节点,如 Container、Opacity、Center,还有一些能够有多个子节点,如 Column、Row 和 ListView 等。

Element 领有本人的生命周期:

  • Flutter framework 通过 Widget.createElement 来创立一个 Element。
  • 每当 Widget 创立并插入到 Widget 树中时,framework 就会通过 mount 办法来把这个 widget 创立并关联的 Element 插入到 Element 树中。
  • 通过 attachRenderObject 办法来将 render objects 来关联到 Render 树上,这时能够认为 Widget 曾经显示在屏幕上了。
  • 每当执行了 rebuid 办法,Widget 代表的配置信息扭转时,framewrok 就会调用这个新的 Widget 的 update 办法执行重绘。
  • 当 Element 的先人想要移除一个子 Element 时,能够通过 deactivateChild 办法,先把这个 Element 从 树中移除,而后将这个 Element 退出到一个“不沉闷元素列表”中,接着 framework 就会将这个 element 从屏幕移除。

总的来说,Flutter 提出所有皆 Widget,Widget 次要用来保留 Element 信息,而 Element 作用 Widget 的实例,寄存视图构建的上下文数据,并且同时持有 Widget 和 RenderObject 对象,RenderObject 的次要作用是解决布局、绘制相干的事件,确定 Element 树中每个对象的地位和尺寸。

正文完
 0