Flutter -深入理解 InheritedWidget使用与实现机制

InheritedWidget 使用&原理

简介

在Tree中从上往下高效传递数据的基类widget , 定义为:abstract class InheritedWidget extends ProxyWidget

作用&特点:

  1. 在子widget对应的Element内部,可获取从当前Element节点到根Element节点路径上的所有InheritedElement实例,进而获取相应的InheritedWidget实例中的数据. 同时也可以对InheritedWidget进行依赖监听
  2. InheritedWidget 可以做到局部刷新,即:仅刷新对其进行依赖监听的widget集合

使用

常见的使用方式有两种

一、InheritedWidget外部再包一层父widget,当InheritedWidget中的数据需要更新时,在父widget中调用setState方法

定义继承InheritedWidget的子类, 因为InheritedWidget为抽象类

class DataInheritedWidget<T> extends InheritedWidget {
  //持有的数据源,由父widget赋值
   T data;
  //父widget的更新方法
 Function? update;

  DataInheritedWidget(Widget child, this.data, this.update, {Key? key})
      : super(child: child, key: key);

  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
    //此处可以选择是否通知依赖InheritedWidget的子widget集合进行更新,
    return true;
  }
}

定义持有该InheritedWidget的工具父widget

class InheritedParentWidget<T> extends StatefulWidget {
  final Widget child;
  final T data;

  const InheritedParentWidget(
      {required this.child, required this.data, Key? key})
      : super(key: key);

//仅获取InheritedParentWidget中的数据,
  static T? ofData<T>(BuildContext context) {
  //context Element实现了BuildContext抽象类,此处context就是InheritedParentWidget子widget对应的Element,
    return (context
            .getElementForInheritedWidgetOfExactType<DataInheritedWidget<T>>()
            ?.widget as DataInheritedWidget<T>?)
        ?.data;
  }

//仅获取InheritedParentWidget中的数据,并对InheritedParentWidget进行依赖监听
//context 同上
  static T? of<T>(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<DataInheritedWidget<T>>()
        ?.data;
  }

  static void update<T>(BuildContext context) {
    (context
            .getElementForInheritedWidgetOfExactType<DataInheritedWidget<T>>()
            ?.widget as DataInheritedWidget<T>?)
        ?.update?.call();
  }

  @override
  State<StatefulWidget> createState() {
    return InheritedParent<T>();
  }
}

class InheritedParent<T> extends State<InheritedParentWidget> {
  void update() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return DataInheritedWidget<T>(widget.child, widget.data, update);
  }
}

InheritedParentWidget接受一个需要显示的子widget以及一个数据源,并将其传递给InheritedWidget

使用如下:

class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
   return TestWidgetState();
  }
}

class TestWidgetState extends State<TestWidget> {
  var model = Model();
  @override
  Widget build(BuildContext context) {
    return InheritedParentWidget(
        data: model,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
          //这里使用StatefulBuilder 模拟StatefulWidget实例,主要是方便观察
            StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                print("StatefulBuilder--tip");
                return Text(
                    "当前数值:${InheritedParentWidget.of<Model>(context)?.value}");
              },
            ),
            //这里使用StatefulBuilder 模拟StatefulWidget实例,主要是方便观察
            StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                print("StatefulBuilder--click");
                return TextButton(
                    onPressed: () {
                      var model = InheritedParentWidget.ofData<Model>(context);
                      model?.value = model.value + 1;
                      InheritedParentWidget.update<Model>(context);
                    },
                    child: Text("--点击--:${InheritedParentWidget.ofData<Model>(context)?.value}"));
              },
            ),
          ],
        ));
  }
}

点击底部的TextButton时,只会调用上面的Text重建,而不是调用底部的TextButton重建,即是
只打印: StatefulBuilder–tip,

二:自定义InheritedWidget,在其Elemet的build方法中调用notifyClients方法,如:官方实现的InheritedNotifier

此处定义实现:

class DataListen<T extends Listenable> extends InheritedNotifier {
  final T listenable;

  static T? of<T extends Listenable>(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<DataListen<T>>()
        ?.listenable;
  }

  static T? ofData<T extends Listenable>(BuildContext context) {
    return (context
            .getElementForInheritedWidgetOfExactType<DataListen<T>>()
            ?.widget as DataListen<T>?)
        ?.listenable;
  }

 const DataListen({required Widget child, required this.listenable,Key? key})
      : super(child: child, notifier: listenable,key: key);
}

代码使用:

 //此处需要继承 Listenable 实现自动更新 
class Test2Model extends ChangeNotifier {
  int value = 1;
  void increase(){
    value++;
    notifyListeners();
  }
}

class Test2Widget extends StatefulWidget {
  Test2Widget({Key? key}) : super(key: key);

  @override
  _Test2WidgetState createState() => _Test2WidgetState();
}

class _Test2WidgetState extends State<Test2Widget> {
  Test2Model model = Test2Model();

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return DataListen<Test2Model>(
        listenable: model,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                print("StatefulBuilder--tip");
                return Text(
                    "当前数值:${DataListen.of<Test2Model>(context)?.value}");
              },
            ),
            StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
                print("StatefulBuilder--click");
                return TextButton(
                    onPressed: () {
                      DataListen.ofData<Test2Model>(context)?.increase();
                    },
                    child: Text(
                        "--点击--:${DataListen.ofData<Test2Model>(context)?.value}"));
              },
            ),
          ],
        ));
  }
}

实现的效果和”方式一”一致

InheritedWidget 实现机制

分析:
  1. 子widget是如何获取从当前widget到根widget路径中的所有InheritedWidget实例
  2. InheritedWidget 是如何做到局部刷新的
  3. 在实现的第一种方式中,为什么在InheritedWidget外部需要包一层父widget才可以实现局部刷新
  4. 系统提供的InheritedNotifier是如何在不包裹父widget实现 InheritedWidget局部刷新机制的
问题一分析

flutter视图的创建过程大致为:

  1. 创建widget
  2. 调用widget 的createElement() 方法创建Element
  3. 调用Element的mount()方法
  4. 在Element的mount方法中,遍历子widget,创建和其对应的Element然后调用其mount()方法

flutter通过上述流程递归完成整个Element树的创建和挂载.
我们可以看到Element的挂载方法mount(),是Element树创建的核心方法也是其必调的方法,而Element代码规定其所有子类也必须调用 父类的挂载方法mount(),如下为mount的简化方法

  // @mustCallSuper 标明子类必须调用父类方法
  @mustCallSuper
  void mount(Element? parent, Object? newSlot) {
    ....
    //InheritedWidget数据传递 的核心实现.
    _updateInheritance();
    ...
  }

每个widget所对应的Element 在被添加到Element树中时(调用mount()方法),都会调用updateInheritance()方法,而其实现如下:

 Map<Type, InheritedElement>? _inheritedWidgets;
void _updateInheritance() {
    //将父Element _parent的 _inheritedWidgets实例赋值给子类
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

每个Element都会有一个 Map<Type, InheritedElement>引用指向父类的Map<Type, InheritedElement>,也就是说:没有重写_updateInheritance方法的子Element和父Element共享同一个 Map<Type, InheritedElement>对象,其内部存储:InheritedWidget对应的InheritedElement的类型和其实例,
上面的_updateInheritance方法只有赋值引用,但是并没有创建Map<Type, InheritedElement>实例, 那么是什么地方创建的呢
在整个Element体系中,只有InheritedWidget对应的InheritedElement重写了_updateInheritance方法,并创建相应实例,如下:

 @override
  void _updateInheritance() {
    final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
    //如果父类incomingWidgets 不为空,则将父类数据拷贝进一个新创建的HashMap实例
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets![widget.runtimeType] = this;
  }

即: InheritedElement 在父类Map<Type, InheritedElement>的基础上 创建新的Map,并将自身也添加到该实例中,

结论: 所有InheritedElement的子Element共享同一个Map<Type, InheritedElement>实例,而且每一个子Element都持有该引用.

我们回头看,子widget获取InheritedWidget数据的实现:

  static T? ofData<T>(BuildContext context) {
    //获取DataInheritedWidget对应的Elemnt实例,
    return (context
            .getElementForInheritedWidgetOfExactType<DataInheritedWidget<T>>()
            ?.widget as DataInheritedWidget<T>?)
        ?.data;
  }

  static T? of<T>(BuildContext context) {
    //获取DataInheritedWidget对应的Elemnt实例后并对DataInheritedWidget进行依赖监听
    return context
        .dependOnInheritedWidgetOfExactType<DataInheritedWidget<T>>()
        ?.data;
  }

上述方法都是在子widget中调用的,也就是context是子widget实例中的context,我们知道widget中的BuildContext就是Element, 上述方法在Element中的实现如下:

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

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

我们先看,仅获取数据而不依赖监听的 getElementForInheritedWidgetOfExactType方法,其内部实现为:
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];

而: _inheritedWidgets即是:前面分析的 InheritedElement所有子Element所共享的 Map<Type, InheritedElement>? _inheritedWidgets,

这里也就是解决了第一个疑问, 子widget是如何获取从当前widget到根widget路径中的所有InheritedWidget :
即是:(1)子widget内部对应的Element持有一个 缓存从当前节点到根节点路径上的所有InheritedElement的map引用: Map<Type, InheritedElement>? _inheritedWidgets
(2)我们可以通过调用Elemnt的widget方法获取和其对应的Widget实例

问题二、三 分析

问题一中getElementForInheritedWidgetOfExactType是仅获取InheritedWidget的数据而不对InheritedWidget进行依赖监听, 而dependOnInheritedWidgetOfExactType是获取InheritedWidget数据后并对InheritedWidget进行依赖监听,对比两个方法实现中: dependOnInheritedWidgetOfExactTypegetElementForInheritedWidgetOfExactType多调用了一行dependOnInheritedElement(ancestor, aspect: aspect),

dependOnInheritedElement(ancestor, aspect: aspect)的实现如下:

abstract class Element {
 ....
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    //如果dependencies为null 则初始化
    _dependencies ??= HashSet<InheritedElement>();
    //将依赖对象增加到依赖对象合集中
    _dependencies!.add(ancestor);
    //更新InheritedElement的被依赖对象集合
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
....
}

当我们调用dependOnInheritedWidgetOfExactType方法时,我们会找到相对应的InheritedElement, 将其添加到自己的依赖集合中,并将自己添加到InheritedElement的被依赖合集了,当InheritedWidget进行更新时,便能定位到所有依赖自己的子widget,

为了分析InheritedWidget的局部刷新,我们需要先大致了解下widget的刷新机制

  1. 调用Element->markNeedsBuild 将自己标记为脏数据 即_dirty = true,然后调用BuildOwner->scheduleBuildFor(Element element)方法
  2. 在scheduleBuildFor方法做了两件事,将当前需要更新的widget的Element添加进 List<Element> _dirtyElements 集合中并调用刷新屏幕的方法 scheduleFrame()
  3. 当下一帧刷新到来时 WidgetsBinding->drawFrame(),然后BuildOwner->buildScope(Element context, [ VoidCallback? callback ]),
  4. 在buildScope的内部,遍历_dirtyElements集合,并调用_dirtyElements[index].rebuild();
  5. Element->rebuild() => Element->performRebuild()

这里是widget刷新的大致流程,针对不同的Element,其performRebuild方法会有区别,

InheritedWidget对应的InheritedElement 对上的继承关系为
InheritedElement ->ProxyElement ->ComponentElement -> Element ,整个继承链中,只有ComponentElement重写了 performRebuild()方法,去除无用代码后如下:

  void performRebuild() {
    Widget? built;
    try {
      //创建Widget
      built = build();
    } catch (e, stack) {
    } finally {
      _dirty = false;
    }
    try {
      //根据新创建的widget 更新 Element? _child;
      _child = updateChild(_child, built, slot);
    } catch (e, stack) {
      _child = updateChild(null, built, slot);
    }
   }

而InheritedElement的父类ProxyElement 重写了build()方法

Widget build() => widget.child;

即,创建的widget,是传递给ProxyWidget 的子widget参数, InheritedElement继承
ProxyElement但是没有重写build(),所以其方法也是如此,(如果子widget实例不变,build()返回的对象永远相同)

对于updateChild()更新方法, InheritedElement的继承链都没重写,下面是Element类的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) {
      //在刷新时 child不为空,所以走此处
      bool hasSameSuperclass = true;
      if (hasSameSuperclass && child.widget == newWidget) {
        //1,注意此处,如果新创建的子widget和之前持有的子Element的widget相同则不更新
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        //2,如果两个widget的runtimeType和key不同,则进行更新
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        //如果上述情况都不满足,则重新构建widget和element,并进行挂载
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
    }

注意上述 1 处,当InheritedWidget自身进行更新时,其刷新流程是:

  1. InheritedElemnt->rebuild()
  2. InheritedElemnt->performRebuild()
  3. InheritedElemnt->updateChild()

在performRebuild方法中 如我们上述所说,InheritedElemnt调用 Widget build() => widget.child; 返回的外部传递的widget,所以如果更新流程是从InheritedWidget自身开始且传递的子widget不变,则build()返回的值恒定. => 在updateChild()方法中触发上述 1 处标明的条件,即其子Widget不会更新,(如果传递更新节点比较高,导致传递进来的widget对象发生变化,则会像正常的widget一样刷新)

上述,我们得到一个结论,如果仅调用InheritedElement的更新方法,进行更新时,其内部会因为返回的widget实例相同,而不更新子widget

那么当我们在InheritedWidget外部包装一个(非ProxyWidget类型)父widget,并调用其内部的setState方法时,
因为build() 返回的widget实例,是新创建的,所以和当前Element的widget不同,所以会走
2 处, 调用子Element的update(covariant Widget newWidget) 方法

在InheritedElement的继承链中 ProxyElement 重写了该方法

 @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    super.update(newWidget);
    //调用ProxyElement-> updated()
    updated(oldWidget);
    _dirty = true;
    //调用父类Element的rebuild方法 => Element->performRebuild()=>ComponentElement-> performRebuild()=>回到上述我们分析的InheritedElement的更新流程
    rebuild();
  }

所以当 InheritedWidget的父widget 调用其 子Element(即是InheritedElement)的update方法最终调用了 ProxyElement-> updated()方法

ProxyElement-> updated()方法详情

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

InheritedElement分别重写了这两个方法

  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }
  @override
  void notifyClients(InheritedWidget oldWidget) {
    for (final Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
  }
  
   @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

可以看到

  1. InheritedElement->updated()=>
  2. InheritedElement->notifyClients()=>
  3. InheritedElement->notifyDependent()=>
  4. Element-> didChangeDependencies

我们在前面分析问题一的时候,当调用 dependOnInheritedElement方法时,会将当前的Element填充进InheritedWidget 的dependents集合中,所以InheritedWidget再notifyClients方法中遍历其被依赖的集合并调用其didChangeDependencies方法

而Element的didChangeDependencies方法 会调用markNeedsBuild方法 更新自己

  @mustCallSuper
  void didChangeDependencies() {
    markNeedsBuild();
  }

结论:经过以上分析,得到第二第三个问题结论:
如果仅更新InheritedWidget自身的话,因为传递给InheritedWidget的子widget实例不变,所以在更新子widget的过程,创建的widget实例都相同,所以不刷新子widget, 同时因为从子节点开始更新的话 不会调用的 void update() 方法.所以也不会更新InheritedWidget被监听依赖的Widget集合

当InheritedWidget的父widget的 调用进行刷新时,调用child.update() 方法,即是InheritedWidget会调用notifyClients 遍历其被依赖的集合Element,调用其didChangeDependencies方法进行更新,而InheritedWidget本身因为build()方返回的widget实例是同一个,故其本身不刷新, 即是实现了局部刷新

问题四分析

经过问题二三的分析,我们知道InheritedWidget 本身刷新时,因为没有调用notifyClients方法,所以被依赖的对象没有进行刷新,那如果我们手动调用 是不是就实现了 InheritedWidget的自动刷新机制呢 ,

官方的InheritedNotifier就是通过手动调用notifyClients方法来实现更新的

InheritedNotifierElement 手动调用notifyClients方法

class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
  _InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) {
    widget.notifier?.addListener(_handleUpdate);
  }
  @override
  InheritedNotifier<T> get widget => super.widget as InheritedNotifier<T>;

  bool _dirty = false;

  @override
  void update(InheritedNotifier<T> newWidget) {
    final T? oldNotifier = widget.notifier;
    final T? newNotifier = newWidget.notifier;
    if (oldNotifier != newNotifier) {
      oldNotifier?.removeListener(_handleUpdate);
      newNotifier?.addListener(_handleUpdate);
    }
    super.update(newWidget);
  }

  @override
  Widget build() {
    //手动调用
    if (_dirty)
      notifyClients(widget);
    return super.build();
  }

  void _handleUpdate() {
    _dirty = true;
    markNeedsBuild();
  }

  @override
  void notifyClients(InheritedNotifier<T> oldWidget) {
    super.notifyClients(oldWidget);
    _dirty = false;
  }

  @override
  void unmount() {
    widget.notifier?.removeListener(_handleUpdate);
    super.unmount();
  }
}

总结:

1. 子widget内部对应的Element持有一个 缓存从当前节点到根节点路径上的所有InheritedElement的map引用: Map<Type, InheritedElement>? _inheritedWidgets Element可以通过Type获取相应的InheritedElement实例进而获取对应的InheritedWidget

2. Element在调用dependOnInheritedWidgetOfExactType方法时,会将自己添加进InheritedElement的Map<Element, Object?> _dependents集合中,

3. InheritedElement刷新调用updateChild()方法时,因为build()返回的widget实例不变,所以导致不会调用 Elemnt->update()方法,既:不会更新子widget

4. 当InheritedElement的父Elemnt 刷新执行 child.update(newWidget),方法会调用 InheritedElement->updated() => InheritedElement->notifyClients(),在notifyClients的方法中会遍历 _dependents 集合并调用 Element->didChangeDependencies()方法,而didChangeDependencies方法中会调用 markNeedsBuild()方法,进而实现了InheritedWidget被依赖对象的刷新

综上 InheritedWidget实现了 子widget可以获取父InheritedWidget的实例,并实现了局部刷新

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值