上一篇我们从源码角度分析了 setState 的过程,从而了解到为什么 setState 方法被调用的时候会重新构建整个 Widget 树。但是,Widget 树的重新构建并不意味着渲染元素树也需要重新构建,事实上渲染树只是做了更新,而不一定是移除后在渲染。
但是,我们的 ModelBinding 类也是使用了 setState 进行状态更新的,为什么它的子组件没有重新构建,而只是更新了依赖于状态的子组件的 build 方法呢?除了使用了内部的 InheritedWidget包裹了子组件外,其他和普通的 StatefulWidget 没什么区别。如前面两篇分析 从InheritedWidget了解状态管理一样,差别就是在这个 InheritedWidget上。本着技术人刨根问底的精神,本篇就来看一下 InheritedWidget 在调用 setState 的时候究竟有什么不同。
知其然,知其所以然。在阅读本篇文章前,如果对 Flutter 的状态管理不是特别清楚的,建议阅读本人前几篇文章了解一下背景。
首先,InheritedWidget 和 StatefulWidget 的继承链不同,对比如下。
InheritedWidget继承自 ProxyWidget,之后才是 Widget,而 StatefulWidget 直接继承 Widget。其二是创建的渲染元素类不同,InheritedWidget 的 createElement 返回的是InheritedElement,而 StatefulWidget 的 createElement 返回的是StatefulElement。
我们在上一篇已经知道,实际的渲染控制是有 Element 类来完成的,实际上Widget 的createElement 方法就是将 Widget 对象传给 Element 对象,由 Element 对象根据 Widget 的组件配置来决定如何渲染。
InhretiedWidget 的定义很简单,如下所示:
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({Key? key, required Widget child})
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
updateShouldNotify方法用于 InheritedWidget 的子类实现,已决定是否通知其子组件(widget)。例如,如果数据没有发生改变(典型的如下拉刷新没有新的数据),那么就可以返回 false,从而无需更新子组件,减少性能消耗。之前我们的 ModelBinding 例子中是直接返回了 true,也就是每次发生变化都会通知子组件。接下来就看 InheritedElement 和 StatefulElement 的区别了。
上一篇我们已经分析过 StatefulElement 了,他在 setState 后会调用重建方法 performRebuild 。performRebuild 方法在父类Component 中实现的。核心是当 Widget 树发生改变后,根据新的 Widget 树调用 updateChild 方法来更新子元素。
而上一篇的 ModelBinding 调用 setState 的时候,因为它自身是一个 StatefulWidget,毫无疑问它也会调用到 updateChild来更新子元素。从执行结果来看,由于 ModelBinding 的例子中没有出现重新构建 Widget 树的情况,因此应该是在 updateChild 前的处理不同。 在 updateChild 之前会调用组件的 build 方法来获取新的 Widget 树。是这里不同吗?继续往下看。
与 InheritedWidget 对应,InheritedElement上面还多了一层继承,那就是 ProxyElement。而恰恰在 ProxyElement 我们找到了build 方法。与 StatefulElement不同,这里的 build 方法没有调用对应 Widget 对象的 build 方法,而是直接返回了 widget.child。
// ProxyElement的 build 方法
@override
Widget build() => widget.child;
// StatefulElement 的 build 方法
@override
Widget build() => state.build(this);
// StatelessElement 的 build方法
@override
Widget build() => widget.build(this);
由此我们就知道了为什么 InheritedWidget在状态更新的时候为什么没有重新构建其子组件树了,这是因为在ProxyElement中直接就返回了已经构建的子组件树,而不是重建。你是不是以为真相大白了?说好的刨根问底呢?难道我们不应该问问如果子组件树发生了改变,ProxyElement 是如何感知的?比如插入了一个新的元素,或者某个元素的渲染参数变了(颜色,字体,内容等),渲染层是怎么知道的?继续继续!
先看一下 InheritedElement 的类结构。
classDiagram
Element <-- ComponentElement
ComponentElement <-- ProxyElement
ProxyElement <-- InheritedElement
class Element {
-dependOnInheritedWidgetOfExactType()
-dependOnInheritedElement()
}
class InheritedElement {
-Map<Element, Object?> _dependents
-void _updateInheritance()
-getDependencies(Element dependent)
setDependencies(Element dependent, Object? value)
updateDependencies(Element dependent, Object? aspect)
notifyDependent(covariant InheritedWidget oldWidget, Element dependent)
updated(InheritedWidget oldWidget)
notifyClients(InheritedWidget oldWidget)
}
class ProxyElement {
-build()
-update(ProxyWidget newWidget)
-updated(covariant ProxyWidget oldWidget)
-notifyClients(covariant ProxyWidget oldWidget)
}
从类结构上看也不复杂,这是因为大部分渲染的管理已经在父类的 ComponentElement 和 Element 中完成了。build 方法我们已经讲过了,重点来看一下在 InheritedWidget 的父组件调用 setState 后的过程。我们在子组件需要获取状态管理的时候,使用的方法是:
ModelBindingV2.of<FaceEmotion>(context)这个方法实际调用的是:
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
这里的dependOnInheritedWidgetOfExactType方法在 BuildContext定义,但实际上是Element 实现。这里会访问一个HashMap 对象_inheritedWidgets,从数组中找到对应类型的InheritedElement。
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor,
{Object? aspect}) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@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;
}
这个数组实际上是在 mount 方法中调用_updateInheritance 中完成初始化的。而在InheritedElement 中重载了 Element 的这个方法。也就是在创建 InheritedWidget 的时候,在 mount 中就将 InheritedElement 与对应的组件运行时类型进行了关联。
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.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;
}
首先这个方法会将父级的全部 InheritedWidgets 延续下来,然后在将自己(InheritedElement)存入到这个 HashMap 中,以便后续能够找到该元素。
因此,当在子组件中使用dependOnInheritedWidgetOfExactType的时候,实际上执行的是 dependOnInheritedElement 方法,传递的参数是通过类型找到的 InheritedElement 元素和指定的 InheritedWidget 类型参数 aspect,这里就是我们的_ModeBindScope<T>,然后会将当前的渲染元素(Element 子类)与其绑定,告知 InheritedElement对象这个组件会依赖于它的InheritedWidget。我们从调试的结果可以看到,在_dependents 中存在了这么一个对象。就这样,InheritedElement 就和组件对应的渲染元素建立了联系。
接下来就是看 setState 后,怎么获取新的组件树和更新组件了。我们已经知道了setState 的时候会调用 performRebuild 方法,在 performRebuild 中会调用 Element 的 updateChild 方法,现在来看InheritedElement 的updateChild 做了什么事情。实际上 updateChild 会调用 child.update(newWidget)方法:
else if (hasSameSuperclass &&
Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
child.update(newWidget);
//...
newChild = child;
}
// ...
return newChild;
而在 ProxyElement 中,重写了 update 方法。
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget != null);
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
这里的 newWidget 是 setState 的时候构建的新的组件配置,因此和 oldWidget 并不相同。对于 InheritedWidget,它会先调用updated(oldWidget),这个方法实际上就是通知依赖 InheirtedWidget 的组件更新:
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
// InheritedElement类
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget);
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
}
实际上最终调用了依赖 InheritedWidget 组件渲染元素的 didChangeDependencies 方法,我们在这个方法打印出来看一下。
在元素的 didChangeDependencies 中就会调用 markNeedsBuild将元素标记为需要更新,然后后续的过程就和 StatefulElement 的一样了。而对于没有依赖状态的元素,因为没有在_dependent 中,因此不会被更新。而 ModelBinding 所在的组件是 StatelessWidget,因此最初的这个 Widget 配置树一旦创建就不会改变,而子组件树如果要 改变的话只有两种情况:1、子组件是 StatefulWidget,通过setState 改变,那这不属于 InheritedWidget 的范畴了,而是通过 StatefulWidget 的更新方式完成——当然,这种做法不推荐。2、子组件的组件树改变依赖于状态吗,那这个时候自然会在状态改变的时候更新。
由此,我们终于弄明白了InheritedWidget的组件树的感知和通知子组件刷新过程。
从 InheritedWidget 实现组件渲染的过程来看,整个过程分为下面几个步骤:
mount 阶段将组件树运行时类型与对应的 InheritedElement绑定,存入到 _inheritedWidgets 这个 HashMap 中;Element 元素与InheritedElement(具体的 Element 对象从_inheritedWidgets中获取)进行了绑定,存入到了_dependents 这个 HashMap 中;InheritedElement 直接使用旧的组件配置通知子元素的依赖发生了改变,这是通过调用Element 的 didChangeDependencies 方法完成的。Element的didChangeDependencies将元素标记为需要更新,等待下一帧刷新。_dependent 中,因此不会被通知刷新,进而提高性能。状态管理的原理性文章讲了好几篇了,通过这些文章希望能够达到知其然,知其所以然的目的。实际上,Flutter 的组件渲染的核心就在于如何选择状态管理来实现组件的渲染,这个对性能影响很大。