前言
Flutter中构建页面, 需要用到Widget组件, Widget又分为无状态组件StatelessWidget, 有状态组件StatefulWidget。
无状态组件StatelessWidget,就是说一旦这个Widget创建完成,状态就不允许再变动。
有状态组件StatefulWidget,就是说当前Widget创建完成之后,还可以对当前Widget做更改,可以通过setState函数来刷新当前Widget来达到有状态。
从Flutter启动runApp经历了啥文章中, 我们知道, runApp拿到开发者自己的MyApp做为rootWidget, 生成根Widget、根Element、根RenderObject。在程序创建上面三个根节点的同时, 也创建了rootWidget的节点, 因为rootWidget节点可能是无状态的StatelessWidget, 也有可能是有状态的StatefulWidget 。接下来, 我们一起看看这两种状态Widget的加载流程, 特别是根据Widget首次生成Element的过程和build(BuildContext context)的加载时机
。
StatelessWidget
那么, 无状态组件StatelessWidget的创建流程是怎样的呢?StatelessWidget的源码如下:
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key? key }) : super(key: key);
/// Creates a [StatelessElement] to manage this widget's location in the tree.
///
/// It is uncommon for subclasses to override this method.
@override
StatelessElement createElement() => StatelessElement(this);
considerations.
@protected
Widget build(BuildContext context);
}
由StatelessWidget源码, 我们可以知道:
- StatelessWidget是个抽象类, 继承Widget。
- 提供了一个构造器方法
const StatelessWidget({ Key? key }) : super(key: key)。 - 重写了创建Element方法
createElement。 - 提供了一个开发者常见的
Widget build(BuildContext context)方法。
StatelessElement
widget实例调用createElement, 传入当前widget对象, 创建elment对象。接下来, 我们看一下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
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
从上面的源码注释、源码、及继承关系可以看出:
- An [Element] that uses a [StatelessWidget] as its configuration. 这个使用
StatelessWidget对象提供的信息做为StatelessElement的配置。 StatelessElement继承ComponentElement。StatelessElement的初始化方法, 接收外部传进来的StatelessWidget对象。- 当前
StatelessElement对象持有外部传进来的widget对象 - 重写了
ComponentElement提供的build方法。当element对象执行这个build方法时, 实际上外部传进来的widget对象, 调用了widget.build(this);方法, 也就是我们在StatelessWidget需要实现的Widget build(BuildContext context)方法。 - 重写了
Element提供的update方法, 更新widget时, 调用。
从上面的源码分析知道一个非常重点的知识, 即我们在StatelessWidget类中必须实现的Widget build(BuildContext context)方法, 其中BuildContext context就是element对象。那么, 我常见的build方法是什么时机触发的呢?带着问题, 我们一起分析下ComponentElement源码。
ComponentElement
abstract class ComponentElement extends Element {
/// Creates an element that uses the given widget as its configuration.
ComponentElement(Widget widget) : super(widget);
Element? _child;
bool _debugDoingBuild = false;
@override
bool get debugDoingBuild => _debugDoingBuild;
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_lifecycleState == _ElementLifecycle.active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
rebuild();
}
/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
///
/// Called automatically during [mount] to generate the first build, and by
/// [rebuild] when the element needs updating.
@override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget? built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build();
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
_debugDoingBuild = false;
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () sync* {
yield DiagnosticsDebugCreator(DebugCreator(this));
},
),
);
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () sync* {
yield DiagnosticsDebugCreator(DebugCreator(this));
},
),
);
_child = updateChild(null, built, slot);
}
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();
@override
void visitChildren(ElementVisitor visitor) {
if (_child != null)
visitor(_child!);
}
@override
void forgetChild(Element child) {
assert(child == _child);
_child = null;
super.forgetChild(child);
}
}
ComponentElement继承Element, 我们可以知道如下几个重点知识:
ComponentElement对象在mount时, element对象的生命周期即处于_ElementLifecycle.active活跃状态, 之后调用了_firstBuild方法。_firstBuild调用了Element提供的rebuild方法。ComponentElement重写了Element提供的performRebuild方法。- 从
Element的源码可知, Element的生命周期处于_ElementLifecycle.active活跃状态后, Element对象会去执行performRebuild() performRebuild方法, 执行了build方法。build方法调用了StatelessWidget提供的widget.build(this)方法, 返回widget对象。也就是开发者常打交道的方法。
由上面重点知识, 我们了解了StatelessWidget中build方法的执行过程, 并且知道build(BuildContext context)中context就是element对象。
特别地, 我们需要重中之重地关注ComponentElement重写了void performRebuild()方法。这个方法的官方注释, 清晰可见地说明, 它会去取唤醒StatelessWidget对象中的build方法、State.build中的build方法, 去更新它们的tree。同时, 它在element对象mount, 并处于活跃状态状态后, 会自动地产生第一次build过程。以后当elemnt需要更新时, 调用rebuild, 会执行这个performRebuild方法。
Element
由此观之, 源码的调用顺序是, Element.rebuild() -> ComponentElement.performRebuild() -> ComponentElement.build(),那么 Element.build() 是谁调用的?
这个在它的注释中有写: Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor],也就是由 BuildOwner 调用的(BuildOwner 在 element tree 中是唯一的,它保存在 WidgetsBinding.instance.buildOwner 中)。
那么这个 BuildOwner.scheduleBuildFor(..) 是如何触发的呢?答案在Element.markNeedsBuild(..)
我们看下Element源码:
abstract class Element extends DiagnosticableTree implements BuildContext {
/// Update the given child with the given new configuration.
///
/// This method is the core of the widgets system. It is called each time we
/// are to add, update, or remove a child based on an updated configuration.
///
/// The `newSlot` argument specifies the new value for this element's [slot].
///
/// If the `child` is null, and the `newWidget` is not null, then we have a new
/// child for which we need to create an [Element], configured with `newWidget`.
///
/// If the `newWidget` is null, and the `child` is not null, then we need to
/// remove it because it no longer has a configuration.
///
/// If neither are null, then we need to update the `child`'s configuration to
/// be the new configuration given by `newWidget`. If `newWidget` can be given
/// to the existing child (as determined by [Widget.canUpdate]), then it is so
/// given. Otherwise, the old child needs to be disposed and a new child
/// created for the new configuration.
///
/// If both are null, then we don't have a child and won't have a child, so we
/// do nothing.
///
/// The [updateChild] method returns the new child, if it had to create one,
/// or the child that was passed in, if it just had to update the child, or
/// null, if it removed the child and did not replace it.
///
/// The following table summarizes the above:
///
/// | | **newWidget == null** | **newWidget != null** |
/// | :-----------------: | :--------------------- | :---------------------- |
/// | **child == null** | Returns null. | Returns new [Element]. |
/// | **child != null** | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
///
/// The `newSlot` argument is used only if `newWidget` is not null. If `child`
/// is null (or if the old child cannot be updated), then the `newSlot` is
/// given to the new [Element] that is created for the child, via
/// [inflateWidget]. If `child` is not null (and the old child _can_ be
/// updated), then the `newSlot` is given to [updateSlotForChild] to update
/// its slot, in case it has moved around since it was last built.
///
/// See the [RenderObjectElement] documentation for more information on slots.
@protected
@pragma('vm:prefer-inline')
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
// When the type of a widget is changed between Stateful and Stateless via
// hot reload, the element tree will end up in a partially invalid state.
// That is, if the widget was a StatefulWidget and is now a StatelessWidget,
// then the element tree currently contains a StatefulElement that is incorrectly
// referencing a StatelessWidget (and likewise with StatelessElement).
//
// To avoid crashing due to type errors, we need to gently guide the invalid
// element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
// returns false which prevents us from trying to update the existing element
// incorrectly.
//
// For the case where the widget becomes Stateful, we also need to avoid
// accessing `StatelessElement.widget` as the cast on the getter will
// cause a type error to be thrown. Here we avoid that by short-circuiting
// the `Widget.canUpdate` check once `hasSameSuperclass` is false.
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
/// 惊天秘密!!! 首次进入的时候, newChild为空。这里调用了inflateWidget方法, 根据外部传进来的widget(Myapp), 创建widget的elment节点并赋值给_child, 然后返回给根RenderObjectToWidgetElement持有。
newChild = inflateWidget(newWidget, newSlot);
}
....
return newChild;
}
/// Add this element to the tree in the given slot of the given parent.
///
/// The framework calls this function when a newly created element is added to
/// the tree for the first time. Use this method to initialize state that
/// depends on having a parent. State that is independent of the parent can
/// more easily be initialized in the constructor.
///
/// This method transitions the element from the "initial" lifecycle state to
/// the "active" lifecycle state.
///
/// Subclasses that override this method are likely to want to also override
/// [update], [visitChildren], [RenderObjectElement.insertRenderObjectChild],
/// [RenderObjectElement.moveRenderObjectChild], and
/// [RenderObjectElement.removeRenderObjectChild].
///
/// Implementations of this method should start with a call to the inherited
/// method, as in `super.mount(parent, newSlot)`.
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
assert(slot == null);
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// Only assign ownership if the parent is non-null. If parent is null
// (the root node), the owner should have already been assigned.
// See RootRenderObjectElement.assignOwner().
_owner = parent.owner;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
......
/// Create an element for the given widget and add it as a child of this
/// element in the given slot.
///
/// This method is typically called by [updateChild] but can be called
/// directly by subclasses that need finer-grained control over creating
/// elements.
///
/// If the given widget has a global key and an element already exists that
/// has a widget with that global key, this function will reuse that element
/// (potentially grafting it from another location in the tree or reactivating
/// it from the list of inactive elements) rather than creating a new element.
///
/// The `newSlot` argument specifies the new value for this element's [slot].
///
/// The element returned by this function will already have been mounted and
/// will be in the "active" lifecycle state.
@protected
@pragma('vm:prefer-inline')
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
/// 惊天秘密!!! 拿到外部传进来的widget(MyApp), 创建widget(MyApp)的element, 并返回。
final Element newChild = newWidget.createElement();
/// 惊天秘密!!! 生成newChild后, 立即执行elment的mount方法, 挂载节点。
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
void markNeedsBuild() {
assert(_lifecycleState != _ElementLifecycle.defunct);
if (_lifecycleState != _ElementLifecycle.active)
return;
assert(owner != null);
assert(_lifecycleState == _ElementLifecycle.active);
.......
if (dirty)
return;
// 记脏, 也就是需要更新
_dirty = true;
// 可以看到把 element 自身加入到了 [BuildOwner] 中
owner!.scheduleBuildFor(this);
}
}
Element的markNeedsBuild方法里面, 执行调用了owner!.scheduleBuildFor(this);方法, 将element对象添加到了owner中。
BuildOwner
接下来, 我们看下BuildOwner的源码:
class BuildOwner {
BuildOwner({ this.onBuildScheduled, FocusManager? focusManager }) :
focusManager = focusManager ?? (FocusManager()..registerGlobalHandlers());
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
assert(element != null);
assert(element.owner == this);
......
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
// 表明element已经在_dirtyElements列表了
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
// 加入到 dirty 列表
_dirtyElements.add(element);
element._inDirtyList = true;
}
void buildScope(Element context, [ VoidCallback callback ]) {
// ...
// 会先排序 _dirtyElements,排序规则是先判断深度
// 深度浅的在前,深度深的在后。
// 深度相同的判断是否有脏标记,有脏标记的在前,没有的在后
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
// 依次遍历 _dirtyElements,调用 rebuild()
while (index < dirtyCount) {
// 这里调用 rebuild
_dirtyElements[index].rebuild();
index += 1;
// ...
}
}
}
到这里为止, markNeedsBuild(..)会让 element 自身加入到 BuildOwner 的 _dirtyElements 列表中。
根据 scheduleBuildFor 的注释,这个 _dirtyElement 是在 WidgetsBinding.drawFrame 触发 BuildOwner.buildScope(..)的时候消费的。
接下来我们来看下 buildScope(..) 函数,这个函数会先把 _dirtyElements 排下序,排序好之后依次调用 element.rebuild()。
buildScope会调用 element 的 rebuild()
现在我们已经知道 Element.markNeedsBuild element 变脏,然后在下一帧 drawFrame时候 rebuild。
StatefulWidget中的setState()
那么 Element.markNeedsBuild 什么时候会触发呢?它触发的地方比较多,但是最常见的地方就是我们 StatefulWidget 中的 setState((){}):
abstract class State<T extends StatefulWidget> {
@protected
void setState(VoidCallback fn) {
//...
// 这里最后调用了 state 对应的 element,然后 markNeedsBuild
_element.markNeedsBuild();
}
}
以上源码的时序图如下:
总结
了解了StatelessWidget的源码, 我们可以知道, StatelessWidget实现的build(BuildContext context),这个context就是StatelessElement对象。
StatefulWidget组件中, setState时会触发Element.markNeedsBuild