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

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

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

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

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

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

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

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

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

前言

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

概览

这块的内容比较多且有些复杂,为了避免让你们迷失在源码的海洋里,咱们仍是举个例子先简单了解一下这个体系。字体

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框架是怎么运行的。

相关文章
相关标签/搜索