Flutter框架分析(三)-- Widget,Element和RenderObject

8,700 阅读11分钟

Flutter框架分析分析系列文章:

《Flutter框架分析(一)-- 总览和Window》

《Flutter框架分析(二)-- 初始化》

《Flutter框架分析(三)-- Widget,Element和RenderObject》

《Flutter框架分析(四)-- Flutter框架的运行》

《Flutter框架分析(五)-- 动画》

《Flutter框架分析(六)-- 布局》

《Flutter框架分析(七)-- 绘制》

前言

前面两篇Flutter框架分析的文章介绍了渲染流水线,window和框架的初始化。这篇文章继续来理一下对Flutter app开发者来说比较重要的WidgetElementRenderObject体系。Flutter的理念是一切都是Widget(Everythin is Widget)。开发者在开发Flutter app的时候主要都是在写很多Widget。那么这三者之间是什么关系?它们是怎么工作的呢?让我们来一探究竟。

概览

这块的内容比较多且有些复杂,为了不让大家迷失在源码的海洋里,我们还是举个例子先简单了解一下这个体系。

void main() {
  runApp(MyWidget());
}

class MyWidget extends StatelessWidget {
  final String _message = "Flutter框架分析";
  @override
  Widget build(BuildContext context) => ErrorWidget(_message);
}

这个例子的利用Flutter自带的ErrorWidget显示我们自定义的一句话:“Flutter框架分析”。没错,这个ErrorWidget就是当你的代码出bug的时候显示在屏幕上的可怕的红底黄字信息。放张截屏大家感受一下。

这里使用它是因为它是最简单,层级最少的一个Widget。以方便我们理解Flutter框架,避免被MaterialApp那深不可测的element tree和render tree劝退。

运行上述例子以后再打开Flutter Inspector看一下:

element tree
从上图可见就三个层级 root->MyWidget->ErrorWidget。这看起来是个widget tree。这里的root对应的是上篇文章里说的RenderObjectToWidgetAdapter。但这实际上是这样的一个element tree:RenderObjectToWidgetElement->StatelessElement->LeafRenderObjectElement。还记得我们上篇文章里说的,RenderObjectToWidgetElement是element tree的根节点。看看图中上方红框,这个根节点是持有render tree的根节点RenderView的。它的子节点就是我们自己写的MyWidget对应的StatelessElement。而这个element是不持有RenderObject的。只有最下面的ErrorWidget对应的LeafRenderObjectElement才持有第二个RenderObject。所以 render tree是只有两层的: RenderView->RenderErrorBox。以上所说用图来表示就是这样的:

widget element renderobject
图中绿色连接线表示的是element tree的层级关系。黄色的连接线表示render tree的层级关系。

从上面这个例子可以看出来,Widget是用来描述对应的Element的描述或配置。Element组成了element tree,Element的主要功能就是维护这棵树,节点的增加,删除,更新,树的遍历都在这里完成。Element都是从Widget中生成的。每个Widget都会对应一个Element。但是并非每个Widget/Element会对应一个RenderObject。只有这个Widget继承自RenderObjectWidget的时候才会有对应的RenderObject

总的来说就是以下几点:

  • Widget是对Element的配置或描述。Flutter app开发者主要的工作都是在和Widget打交道。我们不需要关心树的维护更新,只需要专注于对Widget状态的维护就可以了,大大减轻了开发者的负担。
  • Element负责维护element tree。Element不会去管具体的颜色,字体大小,显示内容等等这些UI的配置或描述,也不会去管布局,绘制这些事,它只管自己的那棵树。Element的主要工作都处于渲染流水线的构建(build)阶段。
  • RenderObject负责具体布局,绘制这些事情。也就是渲染流水线的布局(layout)和 绘制(paint)阶段。

接下来我们就结合源码,来分析一下WidgetElementRenderObject

Widget

基类Widget很简单

@immutable
abstract class Widget extends DiagnosticableTree {

  const Widget({ this.key });
  ...
  @protected
  Element createElement();
  ...
}

方法createElement()负责实例化对应的Element。由其子类实现。接下来看下几个比较重要的子类:

StatelessWidget

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);
  
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

StatelessWidget对Flutter开发者来讲再熟悉不过了。它的createElement方法返回的是一个StatelessElement实例。

StatelessWidget没有生成RenderObject的方法。所以StatelessWidget只是个中间层,它需要实现build方法来返回子Widget

StatefulWidget

abstract class StatefulWidget extends Widget {
  @override
  StatefulElement createElement() => StatefulElement(this);

  @protected
  State createState();
}

StatefulWidget对Flutter开发者来讲非常熟悉了。createElement方法返回的是一个StatefulElement实例。方法createState()构建对应于这个StatefulWidgetState

StatefulWidget没有生成RenderObject的方法。所以StatefulWidget也只是个中间层,它需要对应的State实现build方法来返回子Widget

State

说到StatefulWidget就不能不说说State

abstract class State<T extends StatefulWidget> extends Diagnosticable {
  T get widget => _widget;
  T _widget;
  
  BuildContext get context => _element;
  StatefulElement _element;

  bool get mounted => _element != null;

  void initState() { }

  void didUpdateWidget(covariant T oldWidget) { }

  void setState(VoidCallback fn) {
    final dynamic result = fn() as dynamic;
    _element.markNeedsBuild();
  }

  void deactivate() { }
  
  void dispose() { }

  Widget build(BuildContext context);

  void didChangeDependencies() { }
}

从源码可见,State持有对应的WidgetElement。注意这一句BuildContext get context => _element;。我们在调用build时候的入参BuildContex其实返回的就是Element

mounted,用来判断这个State是不是关联到element tree中的某个Element。如果当前State不是在mounted == true的状态,你去调用setState()是会crash的。

函数initState()用来初始化State

函数didUpdateWidget(covariant T oldWidget)在这个State被换了个新的Widget以后被调用到。是的,State对应的Widget实例只要是相同类型的是可以被换来换去的。

函数setState()我们很熟悉了。这个函数只是简单执行传入的回调然后调用_element.markNeedsBuild()。你看,如果此时_element为空的时候会不会出问题?所以建议大家在调用setState()之前用mounted判断一下。另外要注意的一点是,这个函数也是触发渲染流水线的一个点。后续我会在另外的文章里从这个点出发,给大家说说渲染流水线如何在WidgetElementRenderObject架构下运行。

函数deactivate()State对应的Element被从树中移除后调用,这个移除可能是暂时移除。

函数dispose()State对应的Element被从树中移除后调用,这个移除是永久移除。

函数build(BuildContext context),大家很熟悉了,不多说了。

函数didChangeDependencies()State的依赖发生变化的时候被调用,具体什么样的依赖后文再说。

StatefullWidgetState对Flutter app开发者来说可能会是打交道最多的。有些细节还需要结合Element做深入的理解。

InheritedWidget

InheritedWidget既不是StatefullWidget也不是StatelessWidget。它是用来向下传递数据的。在InheritedWidget之下的子节点都可以通过调用BuildContext.inheritFromWidgetOfExactType()来获取这个InheritedWidget。它的createElement()函数返回的是一个InheritedElement

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

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

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

RenderObjectWidget

RenderObjectWidget用来配置RenderObject。其createElement()函数返回RenderObjectElement。由其子类实现。相对于上面说的其他Widget。这里多了一个createRenderObject()方法。用来实例化RenderObject

abstract class RenderObjectWidget extends Widget {

  const RenderObjectWidget({ Key key }) : super(key: key);

  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

RenderObjectWidget只是个配置,当配置发生变化需要应用到现有的RenderObject上的时候,Flutter框架会调用updateRenderObject()来把新的配置设置给相应的RenderObject

RenderObjectWidget有三个比较重要的子类:

  • LeafRenderObjectWidget这个Widget配置的节点处于树的最底层,它是没有孩子的。对应LeafRenderObjectElement
  • SingleChildRenderObjectWidget,只含有一个孩子。对应SingleChildRenderObjectElement
  • MultiChildRenderObjectWidget,有多个孩子。对应MultiChildRenderObjectElement

Element

Element构成了element tree。这个类主要在做的事情就是维护这棵树。 从上面对Widget的分析我们可以看出,好像每个特别的Widget都会有一个对应的Element。特别是对于RenderObjectWidget。如果我有一个XXXRenderObjectWidget,它的createElement()通常会返回一个XXXRenderObjectElement。为简单起见。我们的分析就仅限于比较基础的一些Element。 首先来看一下基类Element

abstract class Element extends DiagnosticableTree implements BuildContext {
    Element _parent;
    Widget _widget;
    BuildOwner _owner;
    dynamic _slot;
    
    void visitChildren(ElementVisitor visitor) { }
    
    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
        
    }
    
    void mount(Element parent, dynamic newSlot) {
        
    }
    
    void unmount() {
         
    }
    
    void update(covariant Widget newWidget) {
        
    }
    
    @protected
    Element inflateWidget(Widget newWidget, dynamic newSlot) {
    ...
      final Element newChild = newWidget.createElement();
      newChild.mount(this, newSlot);
      return newChild;
    }
  
    void markNeedsBuild() {
      if (dirty)
        return;
      _dirty = true;
      owner.scheduleBuildFor(this);
    }
    
    void rebuild() {
      if (!_active || !_dirty)
        return;
      performRebuild();
    }
  
    @protected
    void performRebuild();
}

Element持有当前的Widget,一个BuildOwner。这个BuildOwner是之前在WidgetsBinding里实例化的。Element是树结构,它会持有父节点_parent_slot由父Element设置,目的是告诉当前Element在父节点的什么位置。由于Element基类不知道子类会如何管理孩子节点。所以函数visitChildren()由子类实现以遍历孩子节点。

函数updateChild()比较重要,用来更新一个孩子节点。更新有四种情况:

  • Widget为空,老Widget也为空。则啥也不做。
  • Widget为空,老Widget不为空。这个Element被移除。
  • Widget不为空,老Widget为空。则调用inflateWidget()以这个Wiget为配置实例化一个Element
  • Widget不为空,老Widget不为空。调用update()函数更新子Elementupdate()函数由子类实现。

Element被实例化以后会调用mount()来把自己加入element tree。要移除的时候会调用unmount()

函数markNeedsBuild()用来标记Element为“脏”(dirty)状态。表明渲染下一帧的时候这个Element需要被重建。

函数rebuild()在渲染流水线的构建(build)阶段被调用。具体的重建在函数performRebuild()中,由Element子类实现。

Widget有一些比较重要的子类,对应的Element也有一些比较重要的子类。

ComponentElement

ComponentElement表示当前这个Element是用来组合其他Element的。

abstract class ComponentElement extends Element {
  ComponentElement(Widget widget) : super(widget);

  Element _child;

  @override
  void performRebuild() {
    Widget built;
    built = build();
    _child = updateChild(_child, built, slot);
  }

  Widget build();
}

ComponentElement继承自Element。是个抽象类。_child是其孩子。在函数performRebuild()中会调用build()来实例化一个Widgetbuild()函数由其子类实现。

StatelessElement

StatelessElement对应的Widget是我们熟悉的StatelessWidget

class StatelessElement extends ComponentElement {

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

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

build()函数直接调用的就是StatelessWidget.build()。现在你知道你写在StatelessWidget里的build()是在哪里被调用的了吧。而且你看,build()函数的入参是this。我们都知道这个函数的入参应该是BuildContext类型的。这个入参其实就是这个StatelessElement

StatefulElement

StatefulElement对应的Widget是我们熟悉的StatefulWidget

class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    _state._element = this;
    _state._widget = widget;
  }

  @override
  Widget build() => state.build(this);
  
   @override
  void _firstBuild() {
    final dynamic debugCheckForReturnedFuture = _state.initState() 
    _state.didChangeDependencies();
    super._firstBuild();
  }

  @override
  void deactivate() {
    _state.deactivate();
    super.deactivate();
  }

  @override
  void unmount() {
    super.unmount();
    _state.dispose();
    _state._element = null;
    _state = null;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _state.didChangeDependencies();
  }
}

StatefulElement构造的时候会调用对应StatefulWidgetcreateState()函数。也就是说State是在实例化StatefulElement的时候被实例化的。并且State实例会被这个StatefulElement实例持有。从这里也可以看出为什么StatefulWidget的状态要由单独的State管理,每次刷新的时候可能会有一个新的StatefulWidget被创建,但是State实例是不变的。

build()函数调用的是我们熟悉的State.build(this),现在你也知道了Statebuild()函数是在哪里被调用的了吧。而且你看,build()函数的入参是this。我们都知道这个函数的入参应该是BuildContext类型的。这个入参其实就是这个StatefulElement

我们都知道State有状态,当状态改变时对应的回调函数会被调用。这些回调函数其实都是在StatefulElement里被调用的。

在函数_firstBuild()里会调用State.initState()State.didChangeDependencies()

在函数deactivate()里会调用State.deactivate()

在函数unmount()里会调用State.dispose()

在函数didChangeDependencies()里会调用State.didChangeDependencies()

InheritedElement

InheritedElement对应的WidgetInheritedWidget。其内部实现主要是在维护对其有依赖的子ElementMap,以及在需要的时候调用子Element对应的didChangeDependencies()回调,这里就不贴代码了,大家感兴趣的话可以自己去看一下源码。

RenderObjectElement

RenderObjectElement对应的WidgetRenderObjectWidget

abstract class RenderObjectElement extends Element {
  RenderObject _renderObject;
  
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }
  
  @override
  void unmount() {
    super.unmount();
    widget.didUnmountRenderObject(renderObject);
  }
  
  @override
  void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
  
  @override
  void performRebuild() {
    widget.updateRenderObject(this, renderObject);
    _dirty = false;
  }
  
  @protected
  void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot);

  @protected
  void moveChildRenderObject(covariant RenderObject child, covariant dynamic slot);

  @protected
  void removeChildRenderObject(covariant RenderObject child);

}

函数mount()被调用的时候会调用RenderObjectWidget.createRenderObject()来实例化RenderObject

函数update()performRebuild()被调用的时候会调用RenderObjectWidget.updateRenderObject()

函数unmount()被调用的时候会调用RenderObjectWidget.didUnmountRenderObject()

RenderObject

RenderObject负责渲染流水线布局(layout)阶段和绘制(paint)阶段的工作。同时也维护render tree。对render tree的维护方法是来自基类AbstractNode。这里我们主要关注和渲染流水线相关的一些方法。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {

  void markNeedsLayout() {
      ...
  }
  
  void markNeedsPaint() {
      ...
  }
  
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    ...  
    if (sizedByParent) {
        performResize();
    }
    ...
    performLayout();
    ...
  }
  
  void performResize();
  
  void performLayout();
  
  void paint(PaintingContext context, Offset offset) { }
}

markNeedsLayout()标记这个RenderObject需要重新做布局。markNeedsPaint标记这个RenderObject需要重绘。这两个函数只做标记。标记之后Flutter框架会调度一帧,在下一个Vsync信号到来之后才真正做布局和绘制。

真正的布局在函数layout()中进行。这个函数会做一次判断,如果sizedByParenttrue。则会调用performResize()。表明这个RenderObject的尺寸仅由其父节点决定。然后会调用performLayout()做布局。performResize()performLayout()都需要RenderObject的子类去实现。`

总结

WidgetElementRenderObject体系是Flutter框架的核心。其中Element需要好好理解。Flutter的渲染流水线中的构建(build)阶段主要就是在维护更新element tree里面的Element节点。只有理解了Element和element tree,才是真正掌握了Flutter框架。这篇文章里只是一些静态的说明。下篇文章我会尝试从渲染流水线动态运行的角度分析一下Flutter框架是怎么运行的。