Flutter源码分析(三)Flutter灵魂组件ProxyWidget之InheritedWidget

2,328 阅读8分钟

在开始讲解这篇文章之前,如果没看过前几篇的文章的话,建议先阅读以下。

Flutter源码分析(一)先从StatelessWidget开始

Flutter源码分析(二)进入StatefulWidget世界

2020年个人完成了一个开源的Flutter的影视项目如下。

Flutter 基于Provider+MVVM的Flutter 视频App项目

         对ProxyWidget这个类不是很熟悉的同学,那么它的子类InheritedWidget应该是知道的吧。它是Flutter Widget中的灵魂设定之一,因为InheritedWidget常被用于做数据共享。比如常见的Theme/ThemeData, Text/DefaultTextStyle等等都是通过InheritedWidget 来实现的数据共享,并且Flutter中的状态管理框架也都是通过它实现的,例如最为知名其中之一的状态管理框架Provider

ProxyWidget

        ProxyWidget是InheritedWidget的父类,那么它是干什么的呢?ProxyWidget的说明,可以看到源码注释上写了这样一句话:A widget that has a child widget provided to it, instead of building a new 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这个抽象类足够的简单,它内部仅有一个child的属性。那么接着往下看它的继承关系。总共有两个子类InheritedWidget和ParentDataWidget分别是ProxyWidget的子类。阅读过前几篇文章的同学们都应该清楚了,说完widget就该说Element了,因为Element才是真正提供渲染的方法widget只是它的配置项而已。

ProxyElement

先来看一下它的源码:
/// An [Element] that uses a [ProxyWidget] as its configuration.
abstract class ProxyElement extends ComponentElement {
  /// Initializes fields for subclasses.
  ProxyElement(ProxyWidget widget) : super(widget);

  @override
  ProxyWidget get widget => super.widget as ProxyWidget;

  @override
  Widget build() => widget.child;

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

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

element.
  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}
   

可以看到总共就提供了三个方法,update, updated, notifyClients。
update方法先会调用updated方法之后标记肮脏执行rebuild方法。
updated方法会去调用notifyClient方法。一般子类会去重写它来实现自己的逻辑之后再调用父类的updated方法。(接下来分析子类的时候会提及到)
notifyClients方法是一个抽象方法,由子类去实现。(接下来分析子类的时候会提及到)

InheritedWidget

InheritedWidget是ProxyWidget实现的子类,在Flutter当中的地位也像上边说的那样举足轻重。那来先看一下源码如下:

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

内部相当的简单,首先必不可少的就是会创建它与之对应的Element,之后会提供一个抽象方法updateShouldNotify。这个方法是用来控制是否进行数据同步的,也就是说子组件在引用到InheritedWidget的父组件的值的时候,其值改变是否需要进行子组件的重新build。

InheritedElement

在文章开头我们就说过InheritedWidget的作用用来数据共享的。那么数据共享是什么意思呢?任意一个子组件都可以访问它的数据进行数据共享。当InheritedWidget内部数据进行更新的时候,可以实现所有访问它数据的子组件进行同步更新。那它是如何来实现上述功能的呢?我们来通过源码进行切入的了解一下: InheritedElement的源码如下:
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 as InheritedWidget;

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

  @override
  void debugDeactivated() {
    assert(() {
      assert(_dependents.isEmpty);
      return true;
    }());
    super.debugDeactivated();
  }

  @protected
  Object getDependencies(Element dependent) {
    return _dependents[dependent];
  }


  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }


  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }


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


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

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

我们会看到“_inheritedWidgets”这个map,它其实在父类Element里边的,我们在讲解 Flutter源码分析(一)先从StatelessWidget开始 就有说过。那么它在InheritedElement里使用是干什么呢?通过源码可以看出来就是为了保存当前InheritedWidget对象来着。为什么要保存呢?主要还是为了可以通过子组件的context来进行找到父类的InheritedWidget。

我们想使用InheritedWiget里边的数据的时候,flutter为我们他提供了几个方法,其中有一个方法就是“context.getElementForInheritedWidgetOfExactType()”。它就是需要通过_inheritedWidgets来获取到想要的父组件InheritedWidget对象的。

获取父组件InheritedWidget对象

获取父组件InheritedWidget对象,一般通常有两个获取方式。
一、dependOnInheritedWidgetOfExactType
二、getElementForInheritedWidgetOfExactType
它们两者的区别是什么呢?通过命名可以分析大概出来,一个是“依赖”,一个是“得到”。简明的说就是调用“dependOnInheritedWidgetOfExactType”来获取InheritedWidget对象的时候修改内部数据,这个被修改的数据被其他组件引用会自动被同步,使得被引用这个数据的其他子组件都可以保持一致最新的数据。而调用“getElementForInheritedWidgetOfExactType”来获取InheritedWidget对象的时候修改内部数据并不会同步更新。

先来看一下“getElementForInheritedWidgetOfExactType”源码:
  @override
  InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    return ancestor;
  }

其实就是像上边说的那样仅仅获取到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) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

可以看出它也是是先通过“_inheritedWidgets”来获取到InheritedWidget对象,但和“getElementForInheritedWidgetOfExactType”方法不同的是接着又调用“dependOnInheritedElement(ancestor, aspect: aspect) as T”方法。来看一下它的源码:

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

内部会调用InheritedElement的updateDependencies方法,将当前的elment传入进去。

  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }

该方法的内部接着会调用setDependencies方法将当前的Element接着传递下去。

  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

而setDependencies方法的内部就是将当前的elment通过_dependents已HashMap的形式保存下来。
final Map<Element, Object> _dependents = HashMap<Element, Object>();

InheritedWidget更新数据

先来看一下更新的时候InheritedWidget会执行“updated”方法
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

我们注意到在调用父类的updated方法之前需要进行调用“updateShouldNotify”方法进行判断。这个方法是InheritedWidget的抽象方法,需要它的子类来实现。而这个方法也同样控制了是否会进行数据的同步操作,这样通过操作就可以由子类来控制了。

接下来来看一下调用了父类ProxyElement的updated方法都干了什么?

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

父类的updated方法会调用“notifyClients”这个抽象方法,具体的内容需要由子类来实现,那么我们来看InheritedElement的notifyClients这个方法吧。

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

它会通过“_dependencies”当前的InheritedElement对象并调用“notifyDependent”方法。

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

而它内部就会调用了Element的“didChangeDependencies”的方法。这也就是为什么子组件在依赖了InheritedWidget数据的话,子组件的didChangeDependencies方法会被调用,而在它的方法里边会调用markNeedsBuild重新build。到此为止InheritedWidget的源码分析已经结束了。

计划

2021年新的开始,今年有计划开源一款Flutter应用。因个人的业余时间有限,先需要一枚有心学习Flutter并且有一定的代码功底的小伙伴来共同完成。我们可以在其中相互学习共同成长。有意向者可加wx:MS_miaoshuai