我们在开发Flutter的时候经常会有以下疑问:
- mounted什么时候为true
- dispose什么时候调用
- ChangeNotifier为什么会调用dispose方法。为什么ChangeNotifier有时候又不会调用dispose方法
概念
Flutter中有三棵树: Widget, Element以及RenderObject,它们之间的关系如下图所示
widget:
Widget 描述UI数据的组件,作为一个生产者创建Element和RenderObject。相当于是一个配置文件,为构建Element树提供模板
Element:
Element UI真正的节点,主要管理Widget和State,通过Widget和State创建Element树,同时也管理RenderObject的构建
RenderObject:
RenderObject就是真正渲染到屏幕上的组件。只有当Element是RenderObjectElement的时候才会通过RenderObjectWidget创建RenderObject,因此Element树跟RenderObject树并不是一一对应的
BuildOwner:
这里另外单独提下BuildOwner,这是一个调度中心。由它发起树构建和更新以及树的销毁。
element树的构建过程
element树的构建过程先从runApp说起:
owner.buildScope(element!, () {
element!.mount(null, null);
});
所以真正开始构建是从buildScope开始,buildScope传入了两个参数:
- element就是最顶层element节点即RenderObjectToWidgetElement。
- callback会在buildScope里面调用,这里考虑正常情况下,会先执行callback方法。callback里面开始第一个element的mount操作。
RenderObjectToWidgetElement
@override
void mount(Element? parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
RenderObjecToWidgetElement由于是最顶层的父节点,因此parent传的是null。
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
}
由于RenderObjectToWidgetElement继承的是RootRenderObjectElement,而RootRenderObjectElement又是继承的RenderObjectElement。所以会在mount的时候调用createRenderObject方法,创建对应的RenderObject。
接下来会调用rebuild方法:
void _rebuild() {
try {
//widget.child就是runApp时候传入的我们APP的根布局
_child = updateChild(_child, widget.child, _rootChildSlot);
...
} catch (exception, stack) {
....
}
}
会执行updateChild方法
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
//如果当前widget是空的,但是element不是空的,那么就把element设置为inactive状态
//这里仅仅是变成inactive状态,并不会马上执行dispose方法,稍后会讲到什么情况下执行dispose方法
deactivateChild(child);
return null;
}
final Element newChild;
if (child != null) {
//如果element和widget都不为空,那么下面根据不同的条件会做增删改查操作
bool hasSameSuperclass = true;
...
if (hasSameSuperclass && child.widget == newWidget) {
//如果当前element的widget和传入的新widget是同一个
//那么只判断slot是否一致,如果不一致更新下slot信息
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
//如果当前element的widget和传入的新widget不是同一个,但是属于同一个Widget类
if (child.slot != newSlot)
//先判断slot是否一致,不一致的话先更新slot信息
updateSlotForChild(child, newSlot);
//更新element持有的element对象
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
//如果element所持有的widget和新传入的widget不属于同一个Widget类,那么将当前的Element先deactive掉
//同理这个地方也不会立刻执行dispose方法
deactivateChild(child);
assert(child._parent == null);
//然后会inflateWidget方法,后面再详细讲这个方法,这里会创建一个element的对象
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//如果Widget不为空,但是element是空,那么也要创建一个element的对象
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
updateChild是一个比较核心的方法,这里提供一个流程图:
由于是第一次构建,RootRenderObjectElement是没有子Element的,所以这里会直接走inflateWidget的方法。
Element inflateWidget(Widget newWidget, dynamic newSlot) {
assert(newWidget != null);
final Key? key = newWidget.key;
if (key is GlobalKey) {
//这里先从inactiveElement列表中找对应的缓存,如果存在。那么将element重新取出来变成active状态
//(这也就是为什么Element在deactivie掉以后,不一定会指定dispose方法销毁。inactiveElement也是有可能会被再次使用的)
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
//更新element的slot信息
...
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild!;
}
}
//如果没有符合条件的inactiveElement,调用createElement方法创建element
final Element newChild = newWidget.createElement();
assert(() {
_debugCheckForCycles(newChild);
return true;
}());
//将element挂载到element树上
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
return newChild;
}
接下来我们可以看下Widget的createElement方法和Element的mount方法
由于Widget的createElement方法比较简单,Widget会产生对应的Element(一般情况下Widget和Element的名字是类似的),这里就不详细展开了。
继承Element的类比较多,我们详细展开讲下
Element
ComponentElement
mount方法:
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_firstBuild();
...
}
在执行ComponentElement的mount方法的时候会先将Element挂载到树上,然后执行_firstBuild方法,最终会执行performRebuild方法:
void performRebuild() {
...
Widget? built;
try {
assert(() {
_debugDoingBuild = true;
return true;
}());
built = build();
assert(() {
_debugDoingBuild = false;
return true;
}());
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
...
} finally {
...
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
...
}
...
}
会先执行build()方法,build方法由ComponentElement子类实现,然后执行updateChild方法(就是Element的updateChild方法,从而进行Element的增删改操作)
StatefulElement
当执行到firstBuild的时候,StatefulElement会重写这个方法,先执行state的initState方法,然后执行didChangeDependencies方法。
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
...
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
...
state.didChangeDependencies();
...
super._firstBuild();
}
之后执行父类的firstBuild方法,随后就是rebuild->performRebuild->build()。
StatefulElement会通过State的build方法构建Widget, 而state是在StatefulElement的构造方法中初始化的。而StatefulElement是在StatefulWidget的createElement方法中创建的
Widget build() => state.build(this);
StatelessElement
StatelessElement比较简单,就直接调用Widget的build方法。同理这个Widget就是StatelessWidget。
Widget build() => widget.build(this);
ProxyElement
ProxyElement以及其子类ParentDataElement,InheritedElement会获取对应Widget的child。不过这几个类并不是本篇重点,就不再赘述
Widget build() => widget.child;
RenderObjectElement
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
在执行RenderObjectElement的时候,先将Element挂载到Element树上,然后执行RenderObjectWidget的createRenderObject方法创建renderObject。然后将RenderObject附着到最近的RenderObjectElement祖先节点上。至于为什么要用祖先节点来绑定RenderObject之后再做解释。
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
//这里先获取第一个祖先节点的RenderObjectElement
//(因为Element树不一定是RenderObjectElement,而RenderObject必须附着到RenderObjectElement上)
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
//绑定对应的RenderObject,这个方法是个空实现,主要是交给了子RenderObjectElement来做。
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
这里我们看到RenderObjectElement并没有对子节点有处理,是因为RenderObjectElement子类既存在没有孩子节点的Element也存在多个孩子节点的Element,所以对于子节点的处理交给各自的子类来实现。
RootRenderObjectElement
这个Element就是作为最顶层Element节点存在,所以没有特殊的处理逻辑(文章最开始讲的RenderObjectToWidgetElement就是继承的RootRenderObjectElement)
LeafRenderObjectElement
顾名思义,叶子节点的Element,那么这个Element就不存在子节点。因此不会有子节点的处理
SingleChildRenderObjectElement
单孩子节点的Element
mount方法:
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
首先指定父类的mount方法,将Element挂载到Element的树上,然后执行updateChild方法。开始往下遍历增删改孩子节点。
insertRenderObjectChild方法:
void insertRenderObjectChild(RenderObject child, dynamic slot) {
//找到当前Element持有RenderObject对象
final RenderObjectWithChildMixin renderObject = this.renderObject as RenderObjectWithChildMixin;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
//把子RenderObject挂载到RenderObject树上
renderObject.child = child;
assert(renderObject == this.renderObject);
}
这里也可以解释刚才的一个问题:「为什么RenderObject附着到最近的RenderObjectElement祖先节点」, 原因是RenderObjectElement祖先节点会有一个对应的RenderObject,这个RenderObject已经挂在了RenderObject树上面,当前RenderObjectElement的RenderObject需要挂到RenderObject树上就必须先找到最近的RenderObjectElement祖先节点。
MultiChildRenderObjectElement
多孩子节点的Element
mount方法:
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
final List children = List.filled(widget.children.length, _NullElement.instance, growable: false);
Element? previousChild;
for (int i = 0; i < children.length; i += 1) {
//遍历孩子数组,创建对应的Element。
final Element newChild = inflateWidget(widget.children[i], IndexedSlot(i, previousChild));
children[i] = newChild;
previousChild = newChild;
}
_children = children;
}
inserRenderObjectChild方法:
void insertRenderObjectChild(RenderObject child, IndexedSlot slot) {
//获取当前RenderObjectElement对应的RenderObject
final ContainerRenderObjectMixin> renderObject = this.renderObject;
assert(renderObject.debugValidateChild(child));
//根据当前RenderObjectElement的位置信息挂载到对应位置的RenderObject树中
renderObject.insert(child, after: slot.value?.renderObject);
assert(renderObject == this.renderObject);
}
总结:
至此,Element树以及RenderObject树的构建过程全部结束。
element树的更新流程
树更新流程
更新的源头需要从platform_dispatcher说起:当硬件发出VSync信号时,会调用platformDispatcher的onDrawFrame
发现最后还是会执行buildScope方法进行更新。
_dirtyElements[index].rebuild();
发现buildScope在更新的时候会获取_dirtyElements来进行遍历执行rebuild。_dirtyElements列表里面存放着的是Element的对象。
void rebuild() {
...
performRebuild();
...
}
最终还是会执行performRebuild()方法。
ComponentElement会重新build Widget,RenderObjectElement则会执行updateRenderObject更新RenderObject。
在执行完buildScope方法以后,会执行finalizeTree方法。 这个方法会将_inactiveElements的所有element对象都执行一遍unmount方法。StatefulElement会重载这个方法,调用state的dispose方法,并且将state的element置空
dirtyElement数组
setState
最常见的要属setState方法,这个方法是State类的方法,主要配合StatefulWidget进行一个更新
void setState(VoidCallback fn) {
...
final dynamic result = fn() as dynamic;
...
_element!.markNeedsBuild();
}
这个方法传入一个callback,会先执行这个callback,然后执行element的markNeedsBuild()方法
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
将当前Element的_dirty属性设置为true,然后执行BuildOwner的scheduleBuildFor
void scheduleBuildFor(Element element) {
...
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
最终会将需要更新的element加入到_dirtyElements的列表中
Provider
主要是ChangeNotifierProvider、ChangeNotifer、Consumer。
ChangeNotifer
ChangeNotifier就是比较常规的监听器, 实现了Listenable接口,增加了notifyListener的方法。
class ChangeNotifier implements Listenable {
LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();
...
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners!.add(_ListenerEntry(listener));
}
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
for (final _ListenerEntry entry in _listeners!) {
if (entry.listener == listener) {
entry.unlink();
return;
}
}
}
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_listeners = null;
}
@protected
@visibleForTesting
void notifyListeners() {
...
final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);
for (final _ListenerEntry entry in localListeners) {
try {
if (entry.list != null)
//包装了对应的Callback。在执行notifyListeners的时候会触发对应的callback方法
entry.listener();
} catch (exception, stack) {
...
}
}
}
}
ChangeNotifierProvider
ChangeNotifierProvider继承于ListenableProvider
class ListenableProvider extends InheritedProvider {
/// Creates a [Listenable] using [create] and subscribes to it.
///
/// [dispose] can optionally passed to free resources
/// when [ListenableProvider] is removed from the tree.
///
/// [create] must not be `null`.
ListenableProvider({
Key? key,
required Create create,
Dispose? dispose,
bool? lazy,
TransitionBuilder? builder,
Widget? child,
}) : super(
key: key,
startListening: _startListening,
create: create,
dispose: dispose,
lazy: lazy,
builder: builder,
child: child,
);
....
static VoidCallback _startListening(
InheritedContext e,
Listenable? value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
}
ListenableProvider会将_startListening方法传入到父类里面。这个方法里面是给Listenable(一般情况下都是ChangeNotifier)的对象添加了callback,这个callback会存放到ChangeNotifier的_listeners。所以一旦这个value调用了notifierListeners的方法,就会调用这个callback方法即e.markNeedsNotifyDependents
@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}
markNeedsBuild();
_shouldNotifyDependents = true;
}
这个方法就会触发markNeedsBuild()方法,当前element就会被加入到dirtyElement数组当中。
再来看下_startListening的链路,ListenableProvider继承于InheritedProvider,InheritedProvider又有对应的delegate,最终_startListening会被传入到_CreateInheritedProvider当中。
class _CreateInheritedProviderState
extends _DelegateState> {
VoidCallback? _removeListener;
bool _didInitValue = false;
bool _didSucceedInit = false;
T? _value;
_CreateInheritedProvider? _previousWidget;
@override
T get value {
if (_didInitValue && !_didSucceedInit) {
throw StateError(
'Tried to read a provider that threw during the creation of its value.\n'
'The exception occurred during the creation of type $T.',
);
}
bool? _debugPreviousIsInInheritedProviderCreate;
bool? _debugPreviousIsInInheritedProviderUpdate;
...
if (!_didInitValue) {
_didInitValue = true;
if (delegate.create != null) {
assert(debugSetInheritedLock(true));
try {
...
//create方法创建对应的ChangeNotifier赋值给value
//因此_CreateInheritedProviderState就会持有ChangeNotifier的对象
_value = delegate.create!(element!);
_didSucceedInit = true;
} finally {
...
}
...
}
...
element!._isNotifyDependentsEnabled = false;
//此处会执行startListening方法,将value(即ChangeNotifier)传入
_removeListener ??= delegate.startListening?.call(element!, _value as T);
element!._isNotifyDependentsEnabled = true;
assert(delegate.startListening == null || _removeListener != null);
return _value as T;
}
@override
void dispose() {
super.dispose();
_removeListener?.call();
if (_didInitValue) {
delegate.dispose?.call(element!, _value as T);
}
}
}
发现是在获取value的时候会执行startListening,从而将callback注册到ChangeNotifier当中。
另外我们可以看到只有当_didInitValue为true的时候才会在dispose的时候调用ChangeNotifier的dispose方法,而_didInitValue这个只有在value的get方法调用过后才会置true。
Consumer
class Consumer extends SingleChildStatelessWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider]
/// {@endtemplate}
Consumer({
Key? key,
required this.builder,
Widget? child,
}) : super(key: key, child: child);
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return builder(
context,
Provider.of(context),
child,
);
}
}
consumer逻辑相对比较简单,继承于SingleChildStatelessWidget。所以在build的时候会执行buildWithChild方法。这里面的重点是Provider.of
static T of(BuildContext context, {bool listen = true}) {
...
//此方法会获取遍历获取对应的element
final inheritedElement = _inheritedElementOf(context);
if (listen) {
// bind context with the element
// We have to use this method instead of dependOnInheritedElement, because
// dependOnInheritedElement does not support relocating using GlobalKey
// if no provider were found previously.
context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope>();
}
final value = inheritedElement?.value;
if (_isSoundMode) {
if (value is! T) {
throw ProviderNullException(T, context.widget.runtimeType);
}
return value;
}
return value as T;
}
每个InheritedElement都会在_updateInheritance方法执行的时候获取parent的_inheritedWidgets的Map,将parent的map赋值给自己,然后再将自己也存入到map中,这样就可以获取到对应的InheritedElement。也因此在此处会调用startListening方法将callback注册到ChangeNotifier当中。
总结
现在我们可以回答开头的三个问题
- mounted什么时候为true
由于构建开始的时候就会调用buildScope方法,因此可以认为只要sync信号过来,那么创建好的Widget就会执行mount方法,然后执行inflateWidget的时候会创建对应的Element,此时mounted即为true。在下一次sync信号过来的时候如果当前element不在使用,那么在dispose调用完成之后就会变成false - dispose什么时候调用
在下一次sync信号来临时,在updateChild方法中,当前element与对应的Widget不再匹配时,那么会调用deactiveChild方法将element放入到InactiveElements数组中,等待执行finalizeTree的时候将InactiveElements的所有Element都unmount掉。 - ChangeNotifier为什么会调用dispose方法。为什么Widget树发生了变化,ChangeNotifier有时候又不会调用dispose方法
- 首先Provider本身就是个Widget,那么也会创建对应的Element,只要是Element一旦与当前Widget树不匹配,就会调用unmount,而ChangeNotifierProvider对应的_InheritedProviderScopeElement在执行unmount的时候会调用dispose方法,从而最终会执行ChangeNotifier的dispose方法。
- 因为如果当前的ChangeNotifier没有主动调用过Provider.of或者是使用过Consumer,那么ChangeNotifier就不会被调用dispose方法