InheritedWidget内部实现原理浅析

使用示例

阅读原理之前请先体验InheritedWidget的使用demo:数据传递/状态管理 一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);
}

InheritedWidget是一个继承自 ProxyWidget 的抽象类。除了实现了一个 createElement 方法之外,还定义了一个 updateShouldNotify() 接口。 updateShouldNotify就是使用InheritedWidget要实现的接口,决定InheritedWidget变化时,要不要通知子widget。

再看下ProxyWidget是什么鬼:

abstract class ProxyWidget extends Widget {
  /// Creates a widget that has exactly one child widget.
  const ProxyWidget({ Key key, @required this.child }) : super(key: key);

  /// The widget below this widget in the tree.
  ///
  /// {@template flutter.widgets.child}
  /// This widget can only have one child. To lay out multiple children, let this
  /// widget's child be a widget such as [Row], [Column], or [Stack], which have a
  /// `children` property, and then provide the children to that widget.
  /// {@endtemplate}
  final Widget child;
}

ProxyWidget里面没什么特别的东西

InheritedWidget 共享数据传递机制

既然在InheritedWidget定义上没有收获,我们就从InheritedWidget使用上入手,每个自定义InheritedWidget都会实现一个 of 静态方法,这个of静态方法是提供给InheritedWidget的子widget来访问自定义InheritedWidget的,先看下我们示例中of方法怎么实现:

  static ShareDataInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType();
  }

of方法直接返回context的dependOnInheritedWidgetOfExactType,看下dependOnInheritedWidgetOfExactType里面是什么:
dependOnInheritedWidgetOfExactType在源码中两处,一处是BuildContext,一处是Element:


image.png

BuildContext中dependOnInheritedWidgetOfExactType其实只是定义:

 T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect });

真正实现dependOnInheritedWidgetOfExactType是在Element (其实BuildContext就是Element的引用,这里就不作分析了) :

@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);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

 @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

可以看到,子widget通过_inheritedWidgets和我们自定义的InheritedWidget拿到对应的InheritedElement,再从InheritedElement中拿到我们自定义的InheritedWidget;

_inheritedWidgets定义如下:

Map<Type, InheritedElement> _inheritedWidgets;

_inheritedWidgets是一个Map类型,以Type为key,定义在Element中,也就是每个Elemnet都有一个_inheritedWidgets;

这里插一段小曲:
Type定义如下:

/**
 * Runtime representation of a type.
 */
abstract class Type {}

可以在Object中找到它:

class Object {
...
  /**
   * A representation of the runtime type of the object.
   */
  external Type get runtimeType;
...
}

Object本身应该就实现Type,类继承自Object,所以类也是Object,可以直接传给上面Map类型为Type的 key,也就是传给Map Type key其实传的是类的runtimeType,经验证,多次以相同类名作为key保存value到Map,最后以这个类名为key取出的value为最后存储的value,类名作为key跟定义一个字符串作为key作用类似;

回到正题,我们看下_inheritedWidgets数据怎么来,因为_inheritedWidgets在Element中定义,所以在Element中先找:

void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

发现_inheritedWidgets在_updateInheritance方法里初始化,_updateInheritance是在widget mounted或activate调用;知道_inheritedWidgets的数据来自父widget,那父widget的_inheritedWidgets从哪都是在来呢?
在InheritedWidget的定义中,发现InheritedWidget有自己的Element->InheritedElement

abstract class InheritedWidget extends ProxyWidget {
...
  @override
  InheritedElement createElement() => InheritedElement(this);
...

再看下InheritedElement源码:

/// An [Element] that uses an [InheritedWidget] as its configuration.
class InheritedElement extends ProxyElement {
  /// Creates an element that uses the given widget as its configuration.
  InheritedElement(InheritedWidget widget) : super(widget);

  @override
  InheritedWidget get widget => super.widget;

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

可以看到InheritedElement重写了_updateInheritance方法,当父widget的_inheritedWidgets为空时,实例化_inheritedWidgets;当父widget不为空时,也实例化_inheritedWidgets,并把父widget的_inheritedWidgets加进来,这里有个细节,为什么不直接用父widget的_inheritedWidgets呢,这个后面会分析;最后关键的一步,就是将当前自定义InheritedWidget对应的Element保存到以自定义InheritedWidget类别作为key的_inheritedWidgets里,这样_inheritedWidgets就保存了我们自定义InheritedWidget的数据了;InheritedWidget的_inheritedWidgets传给子widget,子widget就可以通过_inheritedWidgets拿到我们自定义InheritedWidget的数据了;

到这里我们可以知道:(1)、每个widget对应的Element都有一个_inheritedWidgets,widget树自上而下,一级一级将自己的_inheritedWidgets传给下一级,并且当前级别为InheritedWidget时,就会将当前自定义的InheritedWidget保存在_inheritedWidgets;
(2)、通过_inheritedWidgets一级一级的向下传递,这样每个子widget的_inheritedWidgets就拥有上级父widget的所有类别的InheritedWidget,就可以通过自身的_inheritedWidgets访问到对应的InheritedWidget;

InheritedWidget 数据更新通知机制

我们自定义的变化InheritedWidget是怎么知道到子widget的didChangeDependencies呢?
子widget跟父InheritedWidget使用关联只有of静态方法,所以我们再从of方法里仔细分析:

 @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

发现在子widget的dependOnInheritedElement方法里,InheritedWidget的element->InheritedElement,也就是ancestor,调用了updateDependencies方法将子widget的element传递过去;
来看下InheritedElement的updateDependencies的方法:

class InheritedElement extends ProxyElement {
...
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }
  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

InheritedElement的updateDependencies方法将子widget对应的element作为key,保存在InheritedElement的_dependents中,这样我们自定义的InheritedWidget就可以通过自身的InheritedElement,找到关联的子widget的element了;
我们定义的的InheritedWidget有了需要通知的子widget,再看看InheritedWidget如何在变化时通知子widget的;
在自定义InheritedWidget时,我们需要实现接口方法:updateShouldNotify,先找下updateShouldNotify在哪里调用到:


来自ProxyElement:
  @override
  void update(ProxyWidget newWidget) {
    ...
    updated(oldWidget);
    ...
  }

来自InheritedElement:
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

来自ProxyElement:
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  } 

来自InheritedElement:
  @override
  void notifyClients(InheritedWidget oldWidget) { 
    for (Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
  }
}

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

这里比较绕,需要仔细理解InheritedElement 和 ProxyElement关系(InheritedElement extends ProxyElement),update和updated方法区别;
由此可知:

  • 在InheritedWidget更新变化时,通过updateShouldNotify返回值决定要不要通知相关的子widget;
  • 如果updateShouldNotify返回false,则相关子widget 的didChangeDependencies不会调用到;
  • 如果返回true,则会遍历_dependents,找到子widget相关的element,并调用element的didChangeDependencies方法;

自定义of静态方法内部实现用getElementForInheritedWidgetOfExactType为何子widget的didChangeDependencies就不会接收到InheritedWidget更新通知?

我们知道,自定义of静态方法内部实现用dependOnInheritedWidgetOfExactType子widget的didChangeDependencies就会接收到InheritedWidget更新,所以先看下两个方法内部实现的差异:
dependOnInheritedWidgetOfExactType:

  @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);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

getElementForInheritedWidgetOfExactType:

  @override
  InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    return ancestor;
  }

从两个方法内部代码实现对比可知:

  • dependOnInheritedWidgetOfExactType内部实现比getElementForInheritedWidgetOfExactType内部实现多调用了dependOnInheritedElement方法;
  • 从上面分析可知,dependOnInheritedElement注册子widget和InheritedWidget依赖关系,InheritedWidget变化时通过这层依赖关系通知子widget的didChangeDependencies;
  • 所以dependOnInheritedWidgetOfExactType() 和 getElementForInheritedWidgetOfExactType()的区别就是前者子widget和InheritedWidget会注册依赖关系,而后者不会,所以用dependOnInheritedWidgetOfExactType实现的of方法对应的InheritedWidget变化时会通知子widget的didChangeDependencies方法,而用getElementForInheritedWidgetOfExactType则InheritedWidget变化不会通知子widget;

Widget树上有多个相同类别的InheritedWidget,为何子widget只会找到最近的父InheritedWidget?

从上面分析我们知道,每个子widget的_inheritedWidgets数据初始化是在_updateInheritance 方法进行的,其实InheritedElement重写了_updateInheritance方法,如下:,


image.png

普通widget的Element的_updateInheritance方法:

 void _updateInheritance() {
    assert(_active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

InheritedWidget的InheritedElement的_updateInheritance方法:

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

对比上面代码,我们可以看到,普通widget只是把父widget的_inheritedWidgets直接赋值到自身的_inheritedWidgets;而InheritedWidget是先创建新的_inheritedWidgets,再把父widget的_inheritedWidgets的数据加到自己创建的_inheritedWidgets中,同时以自己的类别名作为key,将自己的element实例保存到自己创建的_inheritedWidgets;
为什么InheritedWidget要自己创建_inheritedWidgets呢?
这样做的好处是:
(1)、因为如果直接用父的_inheritedWidgets,当将自身的element加到_inheritedWidgets时,如果父的_inheritedWidgets数据中有和自身相同类别的InheritedWidget数据,就会把父_inheritedWidgets和自身相同类别的InheritedWidget的数据替换掉,也就是一级传一传的_inheritedWidgets,最终只有一种类别的InheritedWidget,这样传递数据就会混乱;
(2)、这也是为什么子widget只会找到最近的父InheritedWidget的原因:因为在这层的InheritedWidget创建新的_inheritedWidgets,并以自身的类别作为key,将自己的element实例保存到自己创建的_inheritedWidgets,如果_inheritedWidgets中有和自身相同类别的InheritedWidget数据,替换的也只是自身创建的_inheritedWidgets中数据,不会替换掉父_inheritedWidgets中和自己相同类别保存的数据;然后再将自身的_inheritedWidgets传给子widget,这样子widget通过of静态方法拿到的InheritedWidget就是离它最近的父InheritedWidget的数据了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354