我们在写Flutter第一行代码的时候应该就接触过这StatelessWidget StatefulWidget两个抽象类,而我们在用Flutter开发应用的时候也会经常和这两个类打交道。 那么这两个类到底是用来干什么的呢?让我们来通过源码分析一下。
StatelessWidget
StatelessWidget是无状态的Widget的抽象类,所有继承它的都是无状态的Widget。那么无状态是什么意思呢?意思是不需要管理内部状态。额外的补充一下:我们所看到的Flutter的UI界面是当前一帧显示出来的,当我们更新UI的时候会为我们显示下一帧的UI式样。所以Flutter显示的style是通过每一帧来显示的。简单可以理解为从上一帧更新到下一帧StatelessWidget是不能修改和保存状态的。比如通过按钮修改某个Widget的颜色,进行刷新到下一帧显示出来,这个是无法用StatelessWidget去实现的,它只能展示无状态的Widget。那么有状态的Widget如何实现呢?是通过StatefulWidget来实现的,下一篇文章会详细讲解。我们来看一下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);
@protected
Widget build(BuildContext context);
}
很简单的一个抽象类,继承它的子类只需要实现Widget build(BuildContext context) 这个方法即可。在StatelessWidget这个抽象类里边看到了已经为我们创建好了一个StatelessElement(this)的Element,会将当前的Widget对象传入进去。那么StatelessElement是用来干什么的呢?
Flutter中Widget是以树的结构进行渲染的,而实际上Element才是树的节点,所有对Widget进行添加,删除,修改等等操作实际上是对Element进行操作的,而Widget其实是Element的配置项而已。
StatelessElement
先来看一下StatelessElement的继承关系:Element -> ComponentElement -> StatelessElement, StatefulElement
StatefulElement同样也是StatefulWidget创建出来的Element。在截图上我们可以看到有这样一个RenderObjectElement的Element,这里简单说一下Flutter中还有一个特别的对象就是RenderObject,它是用来进行组件的paint,layout的。总结一下:ComponentElement是用来对子组件排版的,RenderObjectElement是用来进行布局和绘制的。有关RenderObject的源码分析会在接下里的文章进行讲解。先来看一下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();
}
}
也是一个很简单的一个类,总共实现了如下几个步骤:
- 持有了Widget对象的引用。
- 实现build的方法去调用widget的build方法。
- 重写update的方法,添加标记肮脏"_dirty"的字段,之后调用rebuild的方法。
StatelessWidget执行流程
我们来看一下StatelessWidget的执行流程:首先Flutter的根节点默认会执行inflateWidget(Widget newWidget, dynamic newSlot),需要传入的参数是一个widget和一个插槽slot。
abstract class Element extends DiagnosticableTree implements BuildContext {
....
@protected
Element inflateWidget(Widget newWidget, dynamic 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;
}
}
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
newChild.mount(this, newSlot);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
return newChild;
}
....
}
代码中首先先判断widget的当前的key是否为GlobalKey。如果对widget的key设置为GlobalKey,它会通过当前的key进行静态的GlobalKey的Map里边去查找对应的Element,如果找到了会调用updateChild(newChild, newWidget, newSlot);这个方法更新Element并得到更新好的节点Element,没有的话进行接下来的createElement的过程。至于如何将key和对应的Element存入到Map的时机是在mount这个方法中进行的,后文会讲述。
Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
final Element element = key._currentElement;
if (element == null)
return null;
if (!Widget.canUpdate(element.widget, newWidget))
return null;
assert(() {
if (debugPrintGlobalKeyedWidgetLifecycle)
debugPrint('Attempting to take $element from ${element._parent ?? "inactive elements list"} to put in $this.');
return true;
}());
final Element parent = element._parent;
if (parent != null) {
assert(() {
if (parent == this) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary("A GlobalKey was used multiple times inside one widget's child list."),
DiagnosticsProperty<GlobalKey>('The offending GlobalKey was', key),
parent.describeElement('The parent of the widgets with that key was'),
element.describeElement('The first child to get instantiated with that key became'),
DiagnosticsProperty<Widget>('The second child that was to be instantiated with that key was', widget, style: DiagnosticsTreeStyle.errorProperty),
ErrorDescription('A GlobalKey can only be specified on one widget at a time in the widget tree.'),
]);
}
parent.owner._debugTrackElementThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans(
parent,
key,
);
return true;
}());
parent.forgetChild(element);
parent.deactivateChild(element);
}
assert(element._parent == null);
owner._inactiveElements.remove(element);
return element;
}
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
factory GlobalKey({ String debugLabel }) => LabeledGlobalKey<T>(debugLabel);
const GlobalKey.constructor() : super.empty();
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
static final Set<Element> _debugIllFatedElements = HashSet<Element>();
Element get _currentElement => _registry[this];
之后会执行创建新的子节点Element,调用当前节点的mount挂载方法将当前的父节点Element和插槽Slot传入进去。
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
来看一下mount方法到底干了些什么呢?源码如下:
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
assert(slot == null);
assert(depth == null);
assert(!_active);
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
final Key key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
assert(() {
_debugLifecycleState = _ElementLifecycle.active;
return true;
}());
}
其实就是将父节点和插槽保存下来,看到了如果key是GlobalKey的话将当前的Element对象注册到GlobalKey的Map中保存。
_updateInheritance(); 这个方法是为了将所有父节点的InheritedElement以Map的形式保存在当前子节点上,暴露ancestorInheritedElementForWidgetOfExactType, getElementForInheritedWidgetOfExactType这两个方法可以获取到对应父节点的InheritedElement。如果对InheritedElement不熟悉的同学可以先了解一下InheritedWidget。
Element继承关系:Element -> ComponentElement -> ProxyElement -> InheritedElement
会在接下来文章里边详细的说ProxyWidget,这里就不在提及了。
abstract class Element
...
@override
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type
, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
Map<Type, InheritedElement> _inheritedWidgets;
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
return ancestor;
}
@override
· assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;
}
...
}
顶级父类Element的mount方法看完了,我们来看一下它的子类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, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_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
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);
}
}
它重写了父类Element的mount的方法,在调用完父类的mount方法又调用了_firstBuild();这个方法,这个方法里边就一行代码就是调用了父类Element的rebuild方法。
void _firstBuild() {
rebuild();
}
abstract class Element extends DiagnosticableTree implements BuildContext {
...
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
/// called to mark this element dirty, by [mount] when the element is first
/// built, and by [update] when the widget has changed.
void rebuild() {
assert(_debugLifecycleState != _ElementLifecycle.initial);
if (!_active || !_dirty)
return;
assert(() {
if (debugOnRebuildDirtyWidget != null) {
debugOnRebuildDirtyWidget(this, _debugBuiltOnce);
}
if (debugPrintRebuildDirtyWidgets) {
if (!_debugBuiltOnce) {
debugPrint('Building $this');
_debugBuiltOnce = true;
} else {
debugPrint('Rebuilding $this');
}
}
return true;
}());
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(owner._debugStateLocked);
Element debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = owner._debugCurrentBuildTarget;
owner._debugCurrentBuildTarget = this;
return true;
}());
performRebuild();
assert(() {
assert(owner._debugCurrentBuildTarget == this);
owner._debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
}());
assert(!_dirty);
}
...
}
rebuild方法里边它首先回去判断if (!_active || !_dirty)是不是肮脏标记,还记得StatelessElement的update方法中会将_dirty设置为true,而_active用来表示该节点的生命状态,在mount,activate方法中是设置为true,在deactivate设置为false。所以要想执行performRebuild方法的话需要满足两个条件。1、标记肮脏。2、该节点还挂载上边并在当前视图上。那么继续向下分析performRebuild方法。先看一下它的源码:
abstract class ComponentElement extends Element {
...
/// 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
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();
}
...
}
performRebuild这个方法在顶级父类Element是一个抽象方法,这个方法的作用就是实现子类的布局,所以需要让子类ComponentElement去实现相应的逻辑。这个方法主要做了三件事:
- 执行子类重写的build的方法获取到Widget。
- 肮脏的标记改成false。
- 调用updateChild这个方法,更新Element。
关于updateChild这个方法很重要,我们从中可以发现Flutter对Element的优化。那么先来看一下它的源码:
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
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(newWidget, newSlot);
}
assert(() {
if (child != null)
_debugRemoveGlobalKeyReservation(child);
final Key key = newWidget?.key;
if (key is GlobalKey) {
key._debugReserveFor(this, newChild);
}
return true;
}());
return newChild;
}
一、先判断当前child.widget和newWidget是不是同一个对象,如果是的话直接返回当前节点即可。
二、调用Widget.canUpdate(child.widget, newWidget)这个方法比较Widget是否需要更新,比较的内容就是oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;,同时满足的话调用child.update(newWidget);更新一下当前的widget既可。
三、以上都不满足的话就只能将它销毁掉重新执行inflateWidget创建Element了。
当前更新child的逻辑会将更新时候性能的销毁降到最低,因为频发的创建或者销毁Element会带来一定的性能开销。
到这为止就将StatelessWidget挂载的流程已经完成了。