Flutter组件构建浅析

504 阅读8分钟

Widget类图

Widget是我们开发过程中主要接触对象,了解Widget家族结构对理解整个组件构建过程有一定的帮助,我们将常见的Widget关系图梳理了一下,可能有遗漏,但大致的关系如下图

Widget.png

Widget子类大致可以分为三类:

  • StatelessWidget、StatefullWidget 可以组合Widget,形成Widget树
  • ProxyWidget 其子类InheritedWidget是全局状态管理的重要组件
  • RenderObjectWidget 可以创建renderObject对象用于布局绘制

Element类图

Widget里面一个主要的方法就是createElement,其Element的实例化都是以当前Widget作为参数的,这样如有需要,Element可以直接访问Widget方法。

StatelessElement createElement() => StatelessElement(this);
StatefulElement createElement() => StatefulElement(this);
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

从下图可以看出,Element的方法比Widget的要丰富的多。结构上,除了多一层ComponentElement、RootRenderObjectElement抽象类,Element家族结构基本和Widget是对应的

Element子类大致可以粗分为两类:

  • ComponentElement 有一个build方法,可以组合其他Element
  • RenderObjectElement 在mount方法中与renderObject关联,后者负责具体的布局绘制操作

Element.png 上面两张图中的类基本都在framwork.dart文件里,一个例外就是RenderObjectToWidgetAdapter和RenderObjectToWidgetElement类,它们比较特殊,其中RenderObjectToWidgetAdapter是Widget树的根,RenderObjectToWidgetElement是Element树的根。

RenderObject类图

RenderObjectWidget除了createElement方法外,还多一个createRenderObject方法,其返回的RenderObject是负责布局和绘制的对象,下图展示了RenderObject一些直接子类,其中,RenderView是Render树的根。由于RenderObject比较原始,一般不建议复写此类,大部分都是继承自RenderBox或RenderSliver(滑动页面)。

render_object.png

目前我们通过Widget、Element、RenderObject类图了解它们大致的关系和结构,并先告知了三棵树的根节点

根节点
WidgetRenderObjectToWidgetAdapter
ElementRenderObjectToWidgetElement
RenderRenderView

构建流程

我们用下面这个Demo,简单起见,使用StatelessWidget,只考虑第一次构建,不考虑更新,从runApp开始浅析Element树和Render树的构建

void main() {
  runApp(const Start());
}

class Start extends StatelessWidget {
  const Start({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const ColoredBox(color: blue);
  }
}

进入runApp方法,参数app就是我们的根组件Start,初始化方法就一句话,看似简单,其实初始化的内容有很多,因为WidgetsFlutterBinding 混合了很多类

binding.dart
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}


class WidgetsFlutterBinding extends BindingBase with GestureBinding, 
    SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, 
    RendererBinding, WidgetsBinding {
      static WidgetsBinding ensureInitialized() {
            if (WidgetsBinding._instance == null) {
              WidgetsFlutterBinding();
            }
            return WidgetsBinding.instance;
  }
}

我们只看RendererBinding方法的初始化,其中实例化了RenderView,也就是Render树的根节点

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
   ...
    initRenderView();
  ...
  }
  
void initRenderView() {
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
}

/// The root of the render tree.
///
/// The view represents the total output surface of the render tree and handles
/// bootstrapping the rendering pipeline. The view has a unique child
/// [RenderBox], which is required to fill the entire output surface.
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
    ...
}

完成初始化后调用scheduleAttachRootWidget,这个方法调用的是attachRootWidget,这个方法里三个树的根节点都出现了,RenderObjectToWidgetAdapter以renderView、rootWidget为参数,实例化后调用attachToRenderTree方法返回Element树根节点RenderObjectToWidgetElement

    void scheduleAttachRootWidget(Widget rootWidget) {
      Timer.run(() {
        attachRootWidget(rootWidget);
      });
    }

    /// Takes a widget and attaches it to the [renderViewElement], creating it if
    /// necessary.
    ///
    /// This is called by [runApp] to configure the widget tree.
    void attachRootWidget(Widget rootWidget) {
     ...
      _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
        container: renderView,
        debugShortDescription: '[root]',
        child: rootWidget,
      ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
      ...
    }

可以看出RenderObjectToWidgetAdapter是一个纽带,三棵树根节点、应用根组件都在其中

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
 
  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
  }) : super(key: GlobalObjectKey(container));

  // 应用的根组件Start
  final Widget? child;
  // Render树根节点RenderView
  final RenderObjectWithChildMixin<T> container;
  // Element树根节点RenderObjectToWidgetElement
  @override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

  @override
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
 }

RenderObjectToWidgetAdapter实例化后,调用了attachToRenderTree开始构建,其中createElement方法调用后三棵树的根都实例化了

three_tree.png

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner,[RenderObjectToWidgetElement<T>? element ]) {
    // 第一次element为空
    if (element == null) {
      owner.lockState(() {
        // createElement就是上面的方法,返回RenderObjectToWidgetElement
        element = createElement();
        // 只有Element根节点才有这个方法
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        // 开始挂载
        element!.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }

RenderObjectToWidgetElement

第一次构建,主要流程在mount方法中,mout方法需要看继承路径,从Element类图可以看出其继承路径如下:RenderObjectToWidgetElement>>RootRenderObjectElement>>RenderObjectElement>>Element,

  • RenderObjectToWidgetElement mount中主要是_rebuild方法
  • RootRenderObjectElement mount中没有实质操作,
  • RenderObjectElement mount中通过widget创建了RenderObject,这里createRenderObject返回的是根节点RenderView
  • Element mount中记录了父类对象、深度、inheritWidget、notification相关一些比较基础公用的东西
RenderObjectToWidgetElement
  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _rebuild();
  }
  
RootRenderObjectElement
  @override
  void mount(Element? parent, Object? newSlot) {
    // Root elements should never have parents.
    assert(parent == null);
    assert(newSlot == null);
    super.mount(parent, newSlot);
  }

RenderObjectElement
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
   ...
    attachRenderObject(newSlot);
    _dirty = false;
  }

Element
void mount(Element? parent, Object? newSlot) {
  ...
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
   ...
   // inheritWidget相关
    _updateInheritance();
    // Notification相关
    attachNotificationTree();
  }

从上面过程可知Render树的构建发生在RenderObjectElement的mount方法中,接着继续看_rebuild方法,这里有两个child,带下划线的_child是Element类型,是RenderObjectToWidgetElement的子节点,目前是null,另一个child是RenderObjectToWidgetAdapter的子节点,之前说过,它被我们应用的根组件Start赋值。

Element? _child;
  void _rebuild() {
    try {
      _child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);
    } catch (exception, stack) {
      ...
    }
  }

那这个updateChild方法返回的Element来自哪里,带入参数跟踪下去,在inflateWidget方法中发现,这个Element来自Start的createElement方法,返回的是StatelessElement

  // child为null,newWidget为Start
  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    ...
    final Element newChild;
    if (child != null) {
    ...
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
  ...
    return newChild;
  }
 // newWidget为Start 
 Element inflateWidget(Widget newWidget, Object? newSlot) {
  ...
   try {
      ...
      final Element newChild = newWidget.createElement();
      ...
      // 继续挂载,注意RenderObjectToWidgetElement把自己作为参数传递了
      newChild.mount(this, newSlot);
      return newChild;
    } finally {
      ...
    }
}

StatelessElement

Element虽然创建了,不过在返回Element之前,又开始了一轮mount,所以接着看这个mount方法,StatelessElement的继承路线是这样的: StatelessElement>>ComponentElement>>Element

  • StatelessElement 没有复写mount方法
  • ComponentElement 主要是调用_firstBuild方法
  • Element记录了parent为RenderObjectToWidgetElement
ComponentElement
  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    ...
    _firstBuild();
  }
  
Element
void mount(Element? parent, Object? newSlot) {
  ...
   // parent是RenderObjectToWidgetElement
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
   ...
   // inheritWidget相关
    _updateInheritance();
    // Notification相关
    attachNotificationTree();
  }

此轮mount操作后,Element树多了一个节点,但这个方法还没结束,不能返回Element,所以StatelesseElement知道其父节点,但RenderObjectToWidgetElement还没挂上这个子节点,只有mount这个递归操作全部结束了才能挂上,那么继续看_firstBuild方法。 flutter组件build流程 (5).png _firstBuild方法最后调用到ComponentElement的performRebuild方法,和上面一样updateChild返回值作为这个Element的子节点,参数_child为空,重点是built参数

abstract class ComponentElement extends Element {
  Element? _child;
  void performRebuild() {
    Widget? built;
    try {
     ...
      built = build();
     ...
    }
    try {
      _child = updateChild(_child, built, slot);
    } catch (e, stack) {
     ...
    }
  }
}

这里build方法由StatelessElement复写,其来自Start的creatElement方法,持有的Widget就是Start,所以build方法返回的就是ColoredBox,注意ColorBox并不是Start子节点,Widget的定义就是 "Describes the configuration for an Element" ,这里build方法是用来给Element提供配置信息的。

class StatelessElement extends ComponentElement {
 
  @override
  Widget build() => (widget as StatelessWidget).build(this);
  ...
}

class Start extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const ColoredBox(color: blue);
  }
}

ColorBox没有子类,不会继续构建,Widget树到此就结束了,其实Widget树是开发人员构建好的,给Element解析的,现在三棵树大致结构如下

flutter组件build流程 (11).png

接着看updateChild方法,这个和之前一样

  • 使用newWidget创建Element
  • 使用创建的Element继续挂载
 // newWidget就是ColorBox
 final Element newChild = newWidget.createElement();
  ...
 newChild.mount(this, newSlot);

这里的newWidget就是ColorBox,其创建的Element就是SingleChildRenderObjectElement

class ColoredBox extends SingleChildRenderObjectWidget {
  
...
  @override
  RenderObject createRenderObject(BuildContext context) {
    return _RenderColoredBox(color: color);
  }

...

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  final Widget? child;
  
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

SingleChildRenderObjectElement

那继续看SingleChildRenderObjectElement的mount过程,其继承路径如下: SingleChildRenderObjectElement>>RenderObjectElement>>Element,

  • SingleChildRenderObjectElement mount中主要是updataChild方法调用
  • RenderObjectElement mount中通过ColoredBox创建了_RenderColoredBox,attachRenderObject会把它挂到render树上
  • Element mount中记录了parent为StatelesssElement
SingleChildRenderObjectElement
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);
  }
  
RenderObjectElement
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
   ...
    attachRenderObject(newSlot);
    _dirty = false;
  }

Element
void mount(Element? parent, Object? newSlot) {
  ...
   // parent是StatelesssElement
    _parent = parent;
  ...
  }

Element的mount会先执行,记录父Element,这个挺重要,后面_findAncestorRenderObjectElement需要这个父Element。现在,Element还是一个只能从子类指向父类的树

flutter组件build流程 (7).png

接着看下attachRenderObject方法,也就Render树构建流程,策略就是向上查找最近的RenderObjectElement节点,找到成为其renderObject的子节点,注意并不是RenderObjectElement的子节点,而是其成员变量renderObject

 void attachRenderObject(Object? newSlot) {
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    ...
  }
  // 向上查找最近的RenderObjectElement节点
  RenderObjectElement? _findAncestorRenderObjectElement() {
    // parent就是StatelessElement,但它不是RenderObjectElement类型,所以会继续向上查找
    Element? ancestor = _parent;
    while (ancestor != null && ancestor is! RenderObjectElement) {
      ancestor = ancestor._parent;
    }
    return ancestor as RenderObjectElement?;
  }

我们从图上也能看出,最后找到的就是Element根节点RenderObjectToWidgetElement,其成员变量renderObject就是RenderView

  void insertRenderObjectChild(RenderObject child, Object? slot) {
    ...
    renderObject.child = child as T;
  }

这波mount操作完毕,三棵树结构如下图

flutter组件build流程 (12).png

最后又到了updateChild方法,现在ColorBox没有子类,参数都为null,结果也返回null,递归开始收敛

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null) {
        deactivateChild(child);
      }
      return null;
    }
    ...
}    

updateChild方法依次返回Element并挂载到父节点上,根Element的mount流程结束,三棵树也完成构建

flutter组件build流程 (13).png

总结

本文以简单的Demo仅仅浅析了一下应用组件第一次构建的流程:它是以Element为核心,不断向下解析其关联的Widget来形成Element树和Render树,其中Render树是目的,用于布局和绘制。如果Widget树复杂一点,层次就深一些,但流程基本是相同的,最后总结一张图

  • 纵向的updateChild解构Widget树
  • 横向mount用以构建Element、Render树

flutter组件build流程 (14).png