前言
Flutter中构建页面, 需要用到Widget组件, Widget又分为无状态组件StatelessWidget, 有状态组件StatefulWidget。
无状态组价StatelessWidget,就是说一旦这个Widget创建完成,状态就不允许再变动。
有状态组件StatefulWidget,就是说当前Widget创建完成之后,还可以对当前Widget做更改,可以通过setState函数来刷新当前Widget来达到有状态。
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 {
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