美文网首页FlutterFlutter
Flutter小知识--InheritedWidget之庖丁解牛

Flutter小知识--InheritedWidget之庖丁解牛

作者: RidingWind2023 | 来源:发表于2019-07-16 08:30 被阅读8次

前面我们讲了InheritedWidget的用途和用法,还留了一个小问题。前文链接

Flutter framework是怎么知道子widget有没有依赖InheritedWidget的?

InheritedWidget 定义

首先看一下 InheritedWidget 的定义:

abstract class InheritedWidget extends ProxyWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  /// Whether the framework should notify widgets that inherit from this widget.
  ///
  /// When this widget is rebuilt, sometimes we need to rebuild the widgets that
  /// inherit from this widget but sometimes we do not. For example, if the data
  /// held by this widget is the same as the data held by `oldWidget`, then we
  /// do not need to rebuild the widgets that inherited the data held by
  /// `oldWidget`.
  ///
  /// The framework distinguishes these cases by calling this function with the
  /// widget that previously occupied this location in the tree as an argument.
  /// The given widget is guaranteed to have the same [runtimeType] as this
  /// object.
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

继承自 ProxyWidget, 包含createElement()updateShouldNotify 两个方法。updateShouldNotify的注释较为完整,
定义了是否需要通知子树,那么该如何通知呢?我们看一下createElement()返回的InheritedElement是什么:

class InheritedElement extends ProxyElement {
  ...

  final Map<Element, Object> _dependents = HashMap<Element, Object>();

  @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;
  }

  ....
}  

其中 _parent?._inheritedWidgets 又是什么? 继续跟踪发现, 这是Element拥有的属性:

Map<Type, InheritedElement> _inheritedWidgets;

其中保存了祖先节点中出现的 InheritedWidget 与其对应 element 的映射关系。在 element 的 mount 阶段active 阶段,会执行 _updateInheritance() 方法更新这个映射关系。

对于普通 Element 实例,_updateInheritance() 只是单纯把父 element 的 _inheritedWidgets 属性保存在自身 _inheritedWidgets 里。从而实现映射关系的层层向下传递。

@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;
}

此时我们再回过头来看,InheritedElement的 _updateInheritance的不同,InheritedElement 实例会把自身的信息添加到 _inheritedWidgets 属性中,这样其子孙 element 就可以通过前面提到的 _inheritedWidgets 的传递机制获取到此 InheritedElement 的引用。

铺垫的差不多,可以看InheritedWidget如何进行更新通知了。

InheritedWidget 更新通知机制

前文提到,想要获取"最近"的InheritedElement,需要调用 BuildContext.inheritFromWidgetOfExactType。那跟Element有什么关系呢?
其实,Element就是BuildContext。源码定义如下:

abstract class Element extends DiagnosticableTree implements BuildContext 

接下来看BuildContext.inheritFromWidgetOfExactType的源码:

@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
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
  assert(ancestor != null);
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies.add(ancestor);
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget;
}

首先在 _inheritedWidget 映射中查找是否有特定类型 InheritedWidget 的实例。如果有则将该实例添加到自身的依赖列表中,同时将自身添加到对应的依赖项列表中。这样该 InheritedWidget 在更新后就可以通过其 _dependents 属性知道需要通知哪些依赖了它的 widget。

每当 InheritedElement 实例更新时,会调用 InheritedWidget中的updated方法:

/// Calls [Element.didChangeDependencies] of all dependent elements, if
/// [InheritedWidget.updateShouldNotify] returns true.
///
/// Called by [update], immediately prior to [build].
///
/// Calls [notifyClients] to actually trigger the notifications.
@override
void updated(InheritedWidget oldWidget) {
  if (widget.updateShouldNotify(oldWidget))
    super.updated(oldWidget);
}

上面提到继承自 ProxyWidget, 我们来看 ProxyWidget的 updated 方法:

@protected
void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}

又回到了 InheritedWidget 中notifyClients :

@override
void notifyClients(InheritedWidget oldWidget) {
  assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
  for (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);
  }
}

@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}

终于看到熟悉的didChangeDependencies了,哈哈,终于可以解释 前文中 如果_TestWidget的build方法中没有使用ShareDataWidget的数据,那么它的didChangeDependencies()将不会被调用,因为它并没有依赖ShareDataWidget。

总结一下,首先执行相应 InheritedWidget 上的 updateShouldNotify 方法判断是否需要通知,如果该方法返回 true 则遍历 _dependents 列表中的 element 并执行他们的 didChangeDependencies() 方法。这样 InheritedWidget 中的更新就通知到依赖它的子 widget 中了。

源码分析结束了,后续可能会再讲一些 InheritedWidget的具体应用。


如果你觉得这篇文章对你有益,还请帮忙转发和点赞,万分感谢。

Flutter烂笔头

相关文章

网友评论

    本文标题:Flutter小知识--InheritedWidget之庖丁解牛

    本文链接:https://www.haomeiwen.com/subject/gynakctx.html