-
基本使用
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), ), ); } } -
原理
-
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方法来添加的
- 这个变量是通过ElementTree继承而来的,如果某个Element是InheritedElement类型,会将自己添加到这个Map中,并不断的继承给子Element,所以获取
-
往自己的
_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会在
WidgetBinding的drawFrame方法中被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的重点是会调用buildOwner的buildScope方法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的呢?我们打个断点调试一下:
-
-
发现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方法就会被调用了