Flutter之三棵树

学习整理,转自:

(写得真的超级超级好!!!) 

Flutter渲染之Widget、Element 和 RenderObject - 简书 (jianshu.com)

Flutter的Widget-Element-RenderObject-阿里云开发者社区 (aliyun.com)

Widget 树

我们平时用 Widget 使用声明式的形式写出来的界面,可以理解为 Widget 树,这是要介绍的第一棵树。

image.png

官方对Widget的说明:

  • Flutter的Widgets的灵感来自React,中心思想是构造你的UI使用这些Widgets。
  • Widget使用配置和状态,描述这个View(界面)应该长什么样子。
  • 当一个Widget发生改变时,Widget就会重新build它的描述,框架会和之前的描述进行对比,来决定使用最小的改变(minimal changes)在渲染树中,从一个状态到另一个状态。

个人理解:

Widget树是对我们要创建的UI界面的描述文件,当其中的状态属性发生变化时,则会重新build 。

RenderObject 树

Flutter 引擎需要把我们写的 Widget 树的信息都渲染到界面上,这样人眼才能看到,跟渲染有关的当然有一颗渲染树 RenderObject tree,这是第二颗树。

渲染树节点叫做 RenderObject,这个节点里面处理布局、绘制相关的事情。这两个树(Widget树和RenderObject 树)的节点并不是一一对应的关系,有些 Widget是要显示的,有些 Widget ,比如那些继承自 StatelessWidget & StatefulWidget 的 Widget 只是将其他 Widget 做一个组合,这些 Widget 本身并不需要显示,因此在 RenderObject 树上并没有相对应的节点。

Element 树

由于Widget 树是非常不稳定的,状态一改变就会重新执行 build方法,一旦调用 build 方法意味着这个 Widget 依赖的所有其他 Widget 都会重新创建,所以如果 Flutter 直接解析 Widget树,将其转化为 RenderObject 树来直接进行渲染,那么将会是一个非常消耗性能的过程,那对应的肯定有一个东西来消化这些变化中的不便,来做cache。

因此,这里就有另外一棵树 Element 树。Element 树这一层将 Widget 树的变化(类似 React 虚拟 DOM diff)做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

image.png

官方对Element的描述:

  • Element是一个Widget的实例,在树中详细的位置。
  • Widget描述和配置子树的样子,而Element实际去配置在Element树中特定的位置。

从上图可以看出,widget 树和 Element 树节点是一一对应关系,每一个 Widget 都会有其对应的 Element,但是 RenderObject 树则不然,只有需要渲染的 Widget 才会有对应的节点。

Element 树相当于一个中间层,大管家,它对 Widget 和 RenderObject 都有引用。

当 Widget 不断变化的时候,将新 Widget 拿到 Element 来进行对比,看一下和之前保留的 Widget 类型和 Key 是否相同,如果都一样,那完全没有必要重新创建 Element 和 RenderObject,只需要更新里面的一些属性即可,这样可以以最小的开销更新 RenderObject,引擎在解析 RenderObject 的时候,发现只有属性修改了,那么也可以以最小的开销来做渲染。

以上只是引出了非常重要的三棵树和他们之间的关系,简而言之,Widget 树就是配置信息的树,我们平时写代码写的就是这棵树,RenderObject 树是渲染树,负责计算布局,绘制,Flutter 引擎就是根据这棵树来进行渲染的,Element 树作为中间者,管理着将 Widget 生成 RenderObject和一些更新操作。

前面只是从概念角度粗略来介绍,下面从源码层面来看一看。

Widget

下面对 Widget 的概述截图来自官网

翻译:

Widget 描述 Element 的配置信息,是 Flutter 框架里的核心类层次结构,一个 Widget 是用户界面某一部分的不可变描述。Widgets 可以转为 Elements,Elements 管理着底层的渲染树。

Widget 有可渲染和不可渲染的分别。可渲染里面分为多孩子和单孩子,也就是属性为 child 或 children,在不可渲染的 Widgets 里面又分为有状态和无状态,也就是 StatefullWidget 和 StatelessWidget。

我们选择四个典型的Widgets来看看吧,如 Padding、RichText、Container、TextField。通过查阅源码,我们看到这几个类的继承关系如下图所示:

abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  
  final Key? key;

  @protected
  @factory
  Element createElement();

  /// A short, textual description of this widget.
  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

  
  static int _debugConcreteSubtype(Widget widget) {
    return widget is StatefulWidget ? 1 :
           widget is StatelessWidget ? 2 :
           0;
  }
}

Widget 是个抽象类,所有的 Widgets 都是它的子类。所有的Widgets都会调用 

  @protected
  Element createElement();

实现Widges和Element 的一一对应关系。

SingleChildRenderObjectWidget

@override
 SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);

MultiChildRenderObjectWidget

@override
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);

StatefulWidget

@override
StatefulElement createElement() => StatefulElement(this);

StatelessWidget

@override
StatelessElement createElement() => StatelessElement(this);

可以发现规律,创建 Element 都会传入 this,也就是当前 Widget,然后返回对应的 Element,这些 Element 都是继承自 Element,Element 会有引用指向当前 Widget。

现在我们继续来到 RichText 和 Padding 类定义里面,他们都是继承自 RenderObjectWidget,可以直接渲染的,都有 createRenderObject 方法,如下

Padding

  @override
  RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
  }

RichText

@override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      strutStyle: strutStyle,
      textWidthBasis: textWidthBasis,
      locale: locale ?? Localizations.localeOf(context, nullOk: true),
    );
  }

RenderPadding 和 RenderParagraph 最终都是继承自 RenderObject。

通过以上源码分析,我们可以看出来 Widget 里面有生成 Element 和 RenderObject 的方法,所以我们平时只需要埋头写好 Widget 就行,Flutter 框架会帮我们生成对应的 Element 和 RenderObject。

首先还是进入 Element 类里面看看,这是个抽象类,可以看到一些关键的方法和属性。

  /// Typically called by an override of [Widget.createElement].
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

上面介绍Widget 里面 createElement 方法的时候可以看到会传入 this,这里从 Element 的构造方法中可以看到,this 最后传给了 Element 里面的 _widget。也就是说每个 Element 里面都会有一个 Widget 的引用。_widget 在 Element 里面定义如下:

  /// The configuration for this element.
  @override
  Widget get widget => _widget;
  Widget _widget;

从源码里面知道 Element 里面的 widget 是一个 get 方法,直接返回 _widget。从上面的注释信息也再一次提到 Widget 和 Element 的关系,Widget 是 Element 的配置。

对于 Element 的构造方法,StatefulElement 有一些特殊的地方,如下

class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    ... 省略断言 ...
    assert(_state._element == null);
    _state._element = this;
     ... 省略断言 ...
    _state._widget = widget;
    assert(_state._debugLifecycleState == _StateLifecycle.created);
  }
  
  /// The [State] instance associated with this location in the tree.
  ///
  /// There is a one-to-one relationship between [State] objects and the
  /// [StatefulElement] objects that hold them. The [State] objects are created
  /// by [StatefulElement] in [mount].
  State<StatefulWidget> get state => _state;
  State<StatefulWidget> _state;
}

StatefulElement 的构造方法中还调用了对应 Widget 的 createState 方法,并赋值给 _state。

StatefulElement 里面不仅有对 Widget 的引用,也有对 StatefulWidget 的 State 的引用。并且在构造函数里面还将 widget 赋值给了 _state 里面的 _widget。所以我们在 State 里面可以直接使用 widget 就可以拿到 State 对应的 Widget。

Element 还有一个关键的方法 mount,如下

  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    ... 省略断言 ...  
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);
    }
    _updateInheritance();
    ... 省略断言 ...
  }

Flutter 框架会根据 Widget 创建对应的 Element,Element 生成以后会调用 Element 的 mount 方法,将生成的 Element 挂载到 Element 树上。

这里的 createElement 和 mount 都是 Flutter 框架自动调用的,不需要开发者手动调用。因此我们平时可能没关注这些过程。Element 里面的 mount 方法需要子类实现,我们来看看ComponentElement 里的 mount 方法。

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_active);
    _firstBuild();
    assert(_child != null);
  }

这里一步一步看源码,发现执行链路如下:
_firstBuild()【ComponentElement】 -> rebuild() 【Element】-> performRebuild()【ComponentElement】 -> build()【StatelessElement】

StatelessElement的源码:

/// An [Element] that uses a [StatelessWidget] as its configuration.
class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  Widget build() => (widget as StatelessWidget).build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}

 StatelessElement的 build() 的源码如下:

  @override
  StatelessWidget get widget => super.widget;

  @override
  Widget build() => widget.build(this);

StatefulElement 的 build() 的源码如下

  @override
  Widget build() => state.build(this);  

可以看出ComponentElement 的 mount 最后执行的是 build 方法。

但是 StatelessElement 和 StatefulElement 是有区别的,StatelessElement 执行的是 Widget 里的 build 方法,而 StatefulElement 里面执行的是 state 的 build 方法。

另外,我们看到上面执行执行build 方法传递的参数 this,也就是当前 Element,而我们在写代码的时候 build 方法是这样的

  @override
  Widget build(BuildContext context) {
  }

因此我们知道了,这个 BuildContext 其实就是这个 Widget 所对应的 Element。看看 Element 的定义就更清楚了。。

abstract class Element extends DiagnosticableTree implements BuildContext {
}

再来看看 RenderObjectElement 里的 mount 方法

  @override
  void mount(Element parent, dynamic newSlot) {
    ... 省略断言 ...
    _renderObject = widget.createRenderObject(this);
    ... 省略断言 ...
    attachRenderObject(newSlot);
    _dirty = false;
  }

对比一下 ComponentElement 和 RenderObjectElement 里面的 mount 方法,前面介绍过,ComponentElement 是非渲染 Widget 对应的 Element,而 RenderObjectElement 是渲染 Widget 对应的 Element,前者的mount 方法主要是负责执行 build 方法,而后者的 mount 方法主要是调用 Widget 里面的 createRenderObject 方法生成 RenderObject,然后赋值给自己的 _renderObject。

因此可以总结,ComponentElement 的 mount 方法主要作用是执行 build,而 RenderObjectElement 的 mount 方法主要作用是生成 RenderObject。

Widget 类里面有一个很重要的静态方法,本来可以放到上面讲 Widget 的时候说,但是还是放到 Element 里面吧。就是这个:

  /// Whether the `newWidget` can be used to update an [Element] that currently
  /// has the `oldWidget` as its configuration.
  ///
  /// An element that uses a given widget as its configuration can be updated to
  /// use another widget as its configuration if, and only if, the two widgets
  /// have [runtimeType] and [key] properties that are [operator==].
  ///
  /// If the widgets have no key (their key is null), then they are considered a
  /// match if they have the same type, even if their children are completely
  /// different.
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

Element 里面有一个 _widget 作为其配置信息,当widget变化或重新生成以后,Element 要不要销毁重建呢,还是直接将新生成的 Widget 替换旧的 Widget。答案就是通过这个方法判断的,上面的注释可以翻译如下:

判断新 Widget 是否可以用来取代 Element 当前的配置信息 _widget。
Element 使用特定的 widget 作为其配置信息,如果 runtimeType 和 key 和之前的 widget 相同,那么可以使用一个新的 widget 更新 Element 里面旧的 widget。
如果这两个widget 都没有赋值 key,那么只要 runtimeType 相同也可以更新,即使这两个 widget 的孩子 widget 都完全不一样。

因此可以看出,即使外面的 widget 树经常变换重建,我们的 Element 可以维持相对稳定,不会重复创建,当然也就不会重复 mount, 生成 RenderObject,只需要以最小代价更新相关属性即可,最大可能减小了性能消耗。Widget 本身只是一些配置信息,简单的对象,它的变更重建不直接影响渲染,对性能影响很小。

RenderObject

从 RenderObject 的名字,我们就能很直观地知道,RendreObject 是主要负责实现视图渲染的对象。从上文中我们知道了一下几点

  1. RenderObject 和 widget 并不是一一对应的,只有继承自 RenderObjectWidget 的 widget 才有对应的 RenderObject;
  2. 生成 RenderObject 的方法 createRenderObject 是在 Widget 里面定义的;
  3. 在 RenderObjectElement 执行 mount 方法的时候调用的 widget 里面的 createRenderObject 方法的;
  4. RenderObjectElement 里面既有对 Widget 的引用也有对 RenderObject 的引用,它作为中间者,管理着双方。

RenderObject 在 Flutter 的展示分为四个阶段,即布局、绘制、合成和渲染。其中,布局和绘制在 RenderObject 中完成,Flutter 采用深度优先机制遍历渲染对象树,确定树中各个对象的位置和尺寸,并把它们绘制在不同的图层上。绘制完毕后,合成和渲染的工作则交给 Skia 搞定。

Skia是一个跨平台的2D图形库,它为Flutter提供了绘制和渲染的支持。Flutter使用Skia来将渲染对象树合成为最终屏幕上的像素,从而实现高性能、流畅的用户界面。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值