Widget类图
Widget是我们开发过程中主要接触对象,了解Widget家族结构对理解整个组件构建过程有一定的帮助,我们将常见的Widget关系图梳理了一下,可能有遗漏,但大致的关系如下图
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关联,后者负责具体的布局绘制操作
上面两张图中的类基本都在framwork.dart文件里,一个例外就是RenderObjectToWidgetAdapter和RenderObjectToWidgetElement类,它们比较特殊,其中RenderObjectToWidgetAdapter是Widget树的根,RenderObjectToWidgetElement是Element树的根。
RenderObject类图
RenderObjectWidget除了createElement方法外,还多一个createRenderObject方法,其返回的RenderObject是负责布局和绘制的对象,下图展示了RenderObject一些直接子类,其中,RenderView是Render树的根。由于RenderObject比较原始,一般不建议复写此类,大部分都是继承自RenderBox或RenderSliver(滑动页面)。
目前我们通过Widget、Element、RenderObject类图了解它们大致的关系和结构,并先告知了三棵树的根节点
| 树 | 根节点 |
|---|---|
| Widget | RenderObjectToWidgetAdapter |
| Element | RenderObjectToWidgetElement |
| Render | RenderView |
构建流程
我们用下面这个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方法调用后三棵树的根都实例化了
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方法。
_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解析的,现在三棵树大致结构如下
接着看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还是一个只能从子类指向父类的树
接着看下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操作完毕,三棵树结构如下图
最后又到了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流程结束,三棵树也完成构建
总结
本文以简单的Demo仅仅浅析了一下应用组件第一次构建的流程:它是以Element为核心,不断向下解析其关联的Widget来形成Element树和Render树,其中Render树是目的,用于布局和绘制。如果Widget树复杂一点,层次就深一些,但流程基本是相同的,最后总结一张图
- 纵向的updateChild解构Widget树
- 横向mount用以构建Element、Render树