InheritedWidget获取数据和更新的原理

513 阅读9分钟
  1. 基本使用

    InheritedWidget是一个用来共享数据的Widget,他没有界面,用于从顶向下实现数据的共享,先通过一个小例子来解释使用方式:

    class MyInheritedWidget extends InheritedWidget {
      final int data;
      MyInheritedWidget({
        @required this.data, //待共享的数据
        Widget child,
      }) : super(child: child);
    ​
      //获取当前MyInheritedWidget的实例
      static MyInheritedWidget? of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
      }
    ​
      @override
      bool updateShouldNotify(MyInheritedWidget oldWidget) {
        // 新旧数据不一致时,返回true,通知依赖本widget的子widget,此时子widget中的didChangeDependencies方法会被调用
        return oldWidget.data != data;
      }
    }
    
    • 首先,我们继承了InheritedWidget定义了一个MyInheritedWidget类
    • 在类中我们定义需要共享的数据data,让其通过构造函数传入
    • 我们必须实现updateShouldNotify方法,在这个方法中判断什么时候需要通知子Widget依赖的数据data已经发生变化了,当需要通知的时候,返回true
    • 添加了一个静态的of方法,用于从某个BuildContext中获取本InheritedWidget,方法内部是调用了BuildContext对象的dependOnInheritedWidgetOfExactType方法。这个方法的作用是从Element树中获取离自己最近的MyInteritedWidget对象,并且由于MyInheritedWidget发生变化的时候,传入的BuildContext对应的build方法会被调用,所以建议在build方法里面调用此方法,这样就可以获取到最新值。
    class DependWidget extends StatefulWidget {
      @override
      DependWidgetState createState() {
        return new DependWidgetState();
      }
    }
    ​
    class DependWidgetState extends State<DependWidget> {
      @override
      Widget build(BuildContext context) {
        print('build..............');
        // 使用依赖的InheritedWidget中的数据
        return Text(MyInheritedWidget.of(context).data.toString());
      }
    ​
      // 当依赖的InheritedWidget中的数据发生变化时,调用此方法
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print('didChangeDependencies.....');
      }
    ​
      @override
      void didUpdateWidget(DependWidget oldWidget) {
        super.didUpdateWidget(oldWidget);
        print("didUpdateWidget");
      }
    }
    
    • DependWidget中我们在build里面使用了刚才定义的静态of方法,获取上层的MyInheritedWidget对象,并从里面取出了data来使用
    • 当InheritedWidget发生改变的时候,依赖它的Widget的didChangeDependencies方法会被调用
    class InheritedWidgetTest extends StatefulWidget {
      @override
      _InheritedWidgetTestState createState() => _InheritedWidgetTestState();
    }
    
    class _InheritedWidgetTestState extends State<InheritedWidgetTest> {
      int count = 0;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("InheritedWidgetTest"),
          ),
          body: Center(
            child: MyInheritedWidget(
              data: count, // setState后  共享数据变化
              child: Center(
                child: DependWidget(), // 共享数据变化,影响该widget
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(
                () {
                  ++count; // 数据变化
                },
              );
            },
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
  2. 原理

    • dependOnInheritedWidgetOfExactType获取数据的原理

      从上面我们知道这个方法是用来获取Element树中的指定类型的InheritedWidget的,其源码在Element中,如下:

      T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
        assert(_debugCheckStateIsActiveForAncestorLookup());
        final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
        if (ancestor != null) {
          return dependOnInheritedElement(ancestor, aspect: aspect) as T;
        }
        _hadUnsatisfiedDependencies = true;
        return null;
      }
      
      • 方法首先判断_inheritedWidgets是否为空,如果为空,则InheritedElement类型的ancestor变量为null,否则就从_inheritedWidgets中直接以泛型参数获取ancestor。_inheritedWidgets是一个私有成员变量,其类型如下:

        Map<Type, InheritedElement>? _inheritedWidgets;
        

        是一个Map,并且key为Type,value为InheritedElement。

        这个私有成员会在Element的mount方法内初始化,如下:

        void mount(Element? parent, Object? newSlot) {
          _parent = parent;
          _slot = newSlot;
          _lifecycleState = _ElementLifecycle.active;
          _depth = _parent != null ? _parent!.depth + 1 : 1;
          if (parent != null) {
            // Only assign ownership if the parent is non-null. If parent is null
            // (the root node), the owner should have already been assigned.
            // See RootRenderObjectElement.assignOwner().
            _owner = parent.owner;
          }
          assert(owner != null);
          final Key? key = widget.key;
          if (key is GlobalKey) {
            owner!._registerGlobalKey(key, this);
          }
          _updateInheritance();
        }
        ​
        void _updateInheritance() {
          _inheritedWidgets = _parent?._inheritedWidgets;
        }
        

        可以发现是从父Element的_inheritedWidgets中直接获取的。但是注意,这只是Element中的实现,InheritedElement覆盖了这个方法,其实现如下:

        @override
        void _updateInheritance() {
          assert(_lifecycleState == _ElementLifecycle.active);
          final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
          if (incomingWidgets != null)
            _inheritedWidgets = HashMap<Type, InheritedElement>.of(incomingWidgets);
          else
            _inheritedWidgets = HashMap<Type, InheritedElement>();
          _inheritedWidgets![widget.runtimeType] = this;
        }
        

        增加了一步把自己添加到这个Map的操作,也就是说如果是InheritedElement的话,会在继承之后把自己添加进这个Map,如果是普通Element的话,则只会直接继承。并且Map的初始化也放在了InheritedElement里面,这样做的好处是只会在有InheritedElement的时候才会去初始化这个Map。

        此外我们可以推断,如果ElementTree中有两个相同类型的InheritedElement,那么后面的会覆盖前面的。

      • 如果ElementTree树中有指定类型的InheritedElement,则ancestor变量就不会为null,因此将会调用:

        return dependOnInheritedElement(ancestor, aspect: aspect) as T;
        

        其源码如下:

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

        _dependencies也是一个Element内的私有成员变量,其类型如下:

        Set<InheritedElement>? _dependencies;
        

        也就是说会将InheritedElement添加到这个Set中。添加完成之后,会调用这个InheritedElement的updateDependencies方法:

        void updateDependencies(Element dependent, Object? aspect) {
          setDependencies(dependent, null);
        }
        ​
        void setDependencies(Element dependent, Object? value) {
          _dependents[dependent] = value;
        }
        ​
        final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
        

        这里的dependent参数是this,也就是调用dependOnInheritedWidgetOfExactType的对象,也就是build方法中的buildContext,也就是说在这里会将依赖InheritedElement的Element添加到_dependents这个Map里面。

        之后,方法将InheritedElement对应的widget返回,方法调用完成。

      • 总结一下,dependOnInheritedWidgetOfExactType所做的主要有以下几步:

        • 从自己的_inheritedWidgets这个Map中获取到对应的InheritedElement。

          • 这个变量是通过ElementTree继承而来的,如果某个Element是InheritedElement类型,会将自己添加到这个Map中,并不断的继承给子Element,所以获取InheritedElement的过程是不需要进行树的遍历操作的
          • 添加到_inheritedWidgets这个过程是在Element调用mount,mount调用updateInheritance方法来添加的
        • 往自己的_dependencies这个Set中添加这个InheritedElement

        • 往InheritedElement的_dependant中添加自己(至此,既可以通过Widget拿到InheritedWidget,也可以通过InheritedElement获取到Widget)

        • 将InheritedElement对应的InheritedWidget返回

    • setState触发build以及didChangeDependencies的原理

      • 我们通过setState方法更改了count的值,这会导致InheritedWidgetTest的build方法被调用,我们深入一下setState方法:

        void setState(VoidCallback fn) {
          // 省略一些assert
          final Object? result = fn() as dynamic;
          // 省略一些assert
          _element!.markNeedsBuild();
        }
        

        setState所做的就是调用传入的回调,并且调用当前Element对象的markNeedsBuild方法。并且根据注释,setState中的回调不可以返回一个Future,因为Future是一个异步任务,其返回值并不一定会在build中得到反馈。

        void markNeedsBuild() {
          if (_lifecycleState != _ElementLifecycle.active)
            return;
          if (dirty)
            return;
          _dirty = true;
          owner!.scheduleBuildFor(this);
        }
        

        Element的markNeedBuild方法将自己的_dirty设置为true,然后调用了BuildOwner的scheduleBuildFor方法并以自己作为参数。

        BuildOwner是一个管理类,他用来记录那些需要rebuild的Widgets以及处理一些其他需要应用到WidgetTree的任务,比如说管理不活跃的Element列表以及在我们debug阶段利用hot reload的时候触发reassemble方法。

        scheduleBuildFor如下:

        void scheduleBuildFor(Element element) {
          if (element._inDirtyList) {
            _dirtyElementsNeedsResorting = true;
            return;
          }
          if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
            _scheduledFlushDirtyElements = true;
            onBuildScheduled!();
          }
          _dirtyElements.add(element);
          element._inDirtyList = true;
        }
        
        • 如果element已经在dirtyList中了,那么标记当前dirtyList需要重新排序之后直接返回
        • 如果传入了onBuildSchedule函数,那就会在第一个element加入到dirtyList的时候调用这个函数
        • 将Element加入到dirtyList,并将Element的_inDirtyList标记为true

        被加入到dirtyList的Element会在WidgetBindingdrawFrame方法中被rebuild

        WidgetBinding是一个mixin,他是Widget层和Flutter引擎之间的粘合剂(Glue),用于处理两者之间的各种调用。

        drawFrame方法本身是被handleDrawFrame方法调用的,而handleDrawFrame方法是被Flutter引擎调用的,drawFrame方法如下:

        void drawFrame() {
        ​
          TimingsCallback? firstFrameCallback;
          if (_needToReportFirstFrame) {
            assert(!_firstFrameCompleter.isCompleted);
        ​
            firstFrameCallback = (List<FrameTiming> timings) {
              assert(sendFramesToEngine);
              if (!kReleaseMode) {
                // Change the current user tag back to the default tag. At this point,
                // the user tag should be set to "AppStartUp" (originally set in the
                // engine), so we need to change it back to the default tag to mark
                // the end of app start up for CPU profiles.
                developer.UserTag.defaultTag.makeCurrent();
                developer.Timeline.instantSync('Rasterized first useful frame');
                developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
              }
              SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
              firstFrameCallback = null;
              _firstFrameCompleter.complete();
            };
            // Callback is only invoked when FlutterView.render is called. When
            // sendFramesToEngine is set to false during the frame, it will not be
            // called and we need to remove the callback (see below).
            SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
          }
        ​
          try {
            if (renderViewElement != null)
              buildOwner!.buildScope(renderViewElement!);// 这里
            super.drawFrame();
            buildOwner!.finalizeTree();
          } finally {
            assert(() {
              debugBuildingDirtyElements = false;
              return true;
            }());
          }
          if (!kReleaseMode) {
            if (_needToReportFirstFrame && sendFramesToEngine) {
              developer.Timeline.instantSync('Widgets built first useful frame');
            }
          }
          _needToReportFirstFrame = false;
          if (firstFrameCallback != null && !sendFramesToEngine) {
            // This frame is deferred and not the first frame sent to the engine that
            // should be reported.
            _needToReportFirstFrame = true;
            SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
          }
        }
        

        drawFrame的重点是会调用buildOwnerbuildScope方法

        void buildScope(Element context, [ VoidCallback? callback ]) {
          if (callback == null && _dirtyElements.isEmpty)
            return;
          try {
            _scheduledFlushDirtyElements = true;
            // 省略一些代码
            _dirtyElements.sort(Element._sort);
            _dirtyElementsNeedsResorting = false;
            int dirtyCount = _dirtyElements.length;
            int index = 0;
            while (index < dirtyCount) {
              final Element element = _dirtyElements[index];
              // 省略一些代码
              try {
                element.rebuild();// 这里
              } catch (e, stack) {
                // 省略一些代码
              }
              index += 1;
             
            }
          } finally {
            for (final Element element in _dirtyElements) {
              assert(element._inDirtyList);
              element._inDirtyList = false;
            }
            _dirtyElements.clear();
            _scheduledFlushDirtyElements = false;
            _dirtyElementsNeedsResorting = null;
          }
        }
        

        重点是依次调用Element的rebuild方法,这个方法会调用perfromRebuild方法,由于InheritedWidgetTest是一个StatefulElement,我们来看StatefulElement的该方法:

        void performRebuild() {
          if (_didChangeDependencies) {
            state.didChangeDependencies();
            _didChangeDependencies = false;
          }
          super.performRebuild();
        }
        

        didChangeDependecies默认为false,也就是此时不会进入if的判断,所以不会调用state.didChangeDependencies()。此时会调用super.perforRebuild

        StetefulElement继承自ComponentElement,所以我们来看一下他的performRebuild方法:

        void performRebuild() {
          Widget? built;
          try {
            built = build();
          } catch (e, stack) {
            //...
          } finally {
            // We delay marking the element as clean until after calling build() so
            // that attempts to markNeedsBuild() during build() will be ignored.
            _dirty = false;
            assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
          }
          try {
            _child = updateChild(_child, built, slot);
          } catch (e, stack) {
            // ...
          }
        }
        ​
        Widget build() => state.build(this);
        

        我们发现,先是调用了自己的build方法,StatefulElement的build方法其实就是调用State对象的build,其生成自己的Widget对象。

        然后performRebuild会调用updateChild方法。注意此时会走原理1中的流程,导致DependentWidget可以获取到MyInheritedWidget中的值。

        updateChild的源码如下:

        Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
          if (newWidget == null) {
            if (child != null)
              deactivateChild(child);
            return null;
          }
        ​
          final Element newChild;
          if (child != null) {
            bool hasSameSuperclass = true;
            // hot reload相关的省略
            if (hasSameSuperclass && child.widget == newWidget) {
              if (child.slot != newSlot)
                updateSlotForChild(child, newSlot);
              newChild = child;
            } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
              if (child.slot != newSlot)
                updateSlotForChild(child, newSlot);
              child.update(newWidget);
              newChild = child;
            } else {
              deactivateChild(child);
              newChild = inflateWidget(newWidget, newSlot);
            }
          } else {
            newChild = inflateWidget(newWidget, newSlot);
          }
        ​
          return newChild;
        }
        

        由于newWidget是build方法返回的新的Widget,如果这个Widget不是const类型的,那么child.widget == newWidget的判断就是false的,此时会判断Widget.canUpdate(child.widget, newWidget),还记得这个方法吗,没错,这个方法就是我们熟悉的判断Element是否可以直接更新而不需要重新inflate的那个方法:

        static bool canUpdate(Widget oldWidget, Widget newWidget) {
          return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
        }
        

        显然,此时判断为true,Widget的runtimeType在此时并没有发生变化,因此会调用child.update方法,update方法很简单:

        // StatefulElement的update方法
        void update(StatefulWidget newWidget) {
          super.update(newWidget);
          final StatefulWidget oldWidget = state._widget!;
          _dirty = true;
          state._widget = widget as StatefulWidget;
          state.didUpdateWidget(oldWidget);
          rebuild();
        }
        ​
        // StatelessElement的update方法
        void update(StatelessWidget newWidget) {
          super.update(newWidget);
          _dirty = true;
          rebuild();
        }
        

        可以发现,无论是StatefulElement还是StatelessElement,都会调用自己继承自ComponentElement的rebuild的方法,因此会从外到内一次进行rebuild -> performRebuild -> build -> child.update这样的递归调用。

        那么StatefulElement的_didChangeDependencies究竟是什么时候变成true的呢?我们打个断点调试一下:

didChangeDependencies.png

didChangeDependencies2.png

发现InheritedElement的父类ProxyElement也重写了update方法,其源码如下:

void update(ProxyWidget newWidget) {
  final ProxyWidget oldWidget = widget;
  super.update(newWidget);
  updated(oldWidget);
  _dirty = true;
  rebuild();
}
​
void updated(InheritedWidget oldWidget) {
  if (widget.updateShouldNotify(oldWidget))
    super.updated(oldWidget);
}

ProxyElement的update依然会调用rebuild,但是在这之前,它还调用updated方法。

updateShouldNotify是我们的InheritedWidget里面重写的方法,如果data发生变化的话,改方法返回true,因此会调用super.updated

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

这个方法会调用notifyClients,此方法是抽象方法,其实现还是要在InheritedElement里面看:

void notifyClients(InheritedWidget oldWidget) {
  for (final Element dependent in _dependents.keys) {
    notifyDependent(oldWidget, dependent);
  }
}
​
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}

其所做的就是遍历自己的_dependents这个Map取出Element,然后调用他们的didChangeDependencies方法。StatefulElement的该方法实现如下:

void didChangeDependencies() {
  super.didChangeDependencies();
  _didChangeDependencies = true;
}

他会调用super的同名方法,再将_didChangeDependencies设置为true。ComponentElement并没有覆盖这个方法,所以其实现是在Element里面:

void didChangeDependencies() {
  markNeedsBuild();
}

这个方法我们很眼熟,不就是setState之后调用的那个方法吗!所以方法会在下一次drawFrame的时候重新走一遍流程,但是此时由于_didChangeDependencies是true了,所以StatefulElement的performRebuild会调用state的didChangeDependencies……

void performRebuild() {
  if (_didChangeDependencies) {// 终于可以进入了!
    state.didChangeDependencies();
    _didChangeDependencies = false;
  }
  super.performRebuild();
}
  • 梳理一下,setState方法主要会经由以下流程来更新:

    • StatefulWidget的setState方法首先会将自己对应StatefulElement标记为dirty,并加入BuildOwner的dirtyList
    • BuildOwner会在Flutter引擎调用drawFrame的时候,调用dirtyList中的所有Element的rebuild方法
    • rebuild方法会调用performRebuild,perfromRebuild会调用build构建child,然后调用updateChild方法。
    • updateChild中先判断canUpdate,这个方法是根据runtimeType和key是否都相同进行判断的。如果可以,则调用child.update,然后child的update又会调用自己的rebuild,构成递归调用,直到Element树的叶子节点
    • InheritedElement的update方法除了rebuild,还会根据updateShouldNotify方法判断下是否需要notify,如果需要则将自己的再次标记为dirty,并将_didChangeDependencies标记为true,这样下次build的时候State的didChangeDependencies方法就会被调用了