【老孟Flutter】源码分析系列之InheritedWidget

老孟导读:这是2021年源码系列的第一篇文章,其实源码系列的文章不是特别受欢迎,一个原因是原理性的知识非常枯燥,我自己看源码的时候特别有感触,二是想把源码分析讲的通俗易懂非常困难,自己明白 和 让别人听懂完全是两回事。不过我依然会坚持 Flutter 源码系列的文章,提高自己的技术水平的同时,也希望大家收获一些知识。

为了使源码系列的文章不那么枯燥,文章中会有很多流程图,流程图比纯文字更直观,一图胜千言。

我也是第一次写源码系列的文章,如果文章哪里有不对的地方请告诉我,虽然我也不一定听😄,开个玩笑。

希望大家来个 ,您的 是我写下去的巨大动力😄。

所有源码系列文章都会分享到我个人博客:http://laomengit.com/

正文

注意:使用的 Flutter 版本 和 Dart 版本如下:

Flutter 1.22.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 1aafb3a8b9 (6 weeks ago) • 2020-11-13 09:59:28 -0800
Engine • revision 2c956a31c0
Tools • Dart 2.10.4

不同的版本可能有所不同,请注意版本之间的区别。

首先, InheritedWidget 是一个非常重要非常重要非常重要的组件,重要的事情说3遍😄,系统中很多功能都是功能型组件都是通过 InheritedWidget 实现的,著名的 Provider 状态管理框架也是基于 InheritedWidget 实现的,因此不管是工作中,还是面试,InheritedWidget 组件的原理及使用场景都是考察的重点。

此篇文章包括如下几个部分:

  • InheritedWidget 组件简介,是什么场景下使用 InheritedWidget?
  • InheritedWidget 基本用法。
  • InheritedWidget 源码分析。

InheritedWidget 组件简介

InheritedWidget 组件是功能型组件,提供了沿树向下,共享数据的功能,即子组件可以获取父组件(InheritedWidget 的子类)的数据,通过BuildContext.dependOnInheritedWidgetOfExactType 获取。

InheritedWidget 组件的共享数据是沿着树从上到下,是否联想到 Notification,Notification 正好与 InheritedWidget 传递方向相反,Notification 是沿着树从下到上,两者功能的实现都是子节点主动发起,InheritedWidget 组件的子节点主动查找父节点上 InheritedWidget 共享的数据,Notification 也是子节点主动发起通知,沿着树向上通知。

Notification 也是 Flutter 中非常重要的,后面会有专门的文章详细介绍,此篇不做介绍。

那么什么样的场景适合使用 InheritedWidget 呢? 通常App会有登录用户信息,登录用户信息为全局共享信息,想象一下,子组件要如何获取登录用户信息?将上面的场景抽象一下,有一颗组件树,A、H 组件依赖同一数据,如下:

A、H 组件要如何获取到数据呢?

有一种实现方式是 通过构造函数透传,数据通过A传递给B,B传递给C、E,C和E在传递给F、H,如下图虚线的传递:

反应到代码上就是:

return A(
  data:data
  child:B(
    data:data
    child:C(
      data:data
      child:F(
        data:data
      )   
    )
  )
);

这样的实现缺点非常明显,B、C组件不需要 data 数据,如果组件树比较深的话,那将是噩梦。

为了处理此问题,Flutter Framework 提供了 InheritedWidget 组件,InheritedWidget 组件的子组件可以直接获取数据,如下图:

InheritedWidget 组件的所有子组件都可以直接通过 BuildContext.dependOnInheritedWidgetOfExactType 获取数据。

InheritedWidget 基本用法

上面分析了 InheritedWidget 组件的使用场景,下面用一个最简单的 demo 展示如何使用 InheritedWidget 组件。

定一个用户信息共享数据的实体类,任何子组件都可以获取用户信息,用户信息实体类:

class UserInfo {
  String name;
  int age;

  UserInfo({this.name, this.age});

  @override
  bool operator ==(Object other) {
    if (!(other is UserInfo)) {
      return false;
    }
    var old = other as UserInfo;
    return name == old.name && age == old.age;
  }
}

UserInfo 类重写了 == 操作符,是为了后面数据是否发生变化做判断。

定义共享 UserInfo 数据的 InheritedWidget 组件。

class MyInheritedWidget extends InheritedWidget {
  final UserInfo userInfo;

  MyInheritedWidget({this.userInfo, Widget child}):super(child: child);

  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  @override
  bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
    return oldWidget.userInfo != userInfo;
  }
}

这里有两个地方需要注意:

  1. 静态(static) of 方法,这个方法不是必须的,但一般都会添加此方法,方便其子组件调用,当然也可以直接在子组件中使用 context.dependOnInheritedWidgetOfExactType 方法,不加 of 方法,子组件调用如下:

    class F extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        var myInheritedWidget =
            context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
        return Text('name:${myInheritedWidget.userInfo.name}');
      }
    }
    

    添加静态(static) of 方法,用法如下:

    class F extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
      }
    }
    

    我们经常使用的 MediaQuery.of(context)Theme.of(context) 方法都是系统封装的此方法。

  2. updateShouldNotify 方法必须重写,此方法是判断新的共享数据和原数据是否一致,是否将通知传递给所有子组件(已注册)。重建此组件时,有时需要重建继承 InheritedWidget 组件的组件,但有时不需要。 例如,如果此组件所保存的数据与“ oldWidget”所保存的数据相同,则我们无需重建继承了“ oldWidget”所保存的数据的组件。

使用 MyInheritedWidget 组件:

class InheritedWidgetDemo extends StatefulWidget {
  @override
  _InheritedWidgetDemoState createState() => _InheritedWidgetDemoState();
}

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: UserInfo(name: '老孟', age: 18),
          child: A(
            child: F(),
          ),
        ),
      ),
    );
  }
}

A 组件代码:

class A extends StatelessWidget {
  final Widget child;

  const A({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: child,
    );
  }
}

F 组件代码:

class F extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
  }
}

上面代码构成的组件树为(无关的节点已忽略比如Scaffold、Center等):

注意: A 组件是为了表示树的深度,此 Demo 中将其简化了,仅仅设置了一层,也可以设多多层。

运行效果:

下面修改数据并刷新UI,下面的代码仅能用于Demo,是为了演示方便,千万不要用于实际项目,因为下面的写法有巨大的性能问题,因为重建了 InheritedWidget 组件下的所有子组件,重要的事情说3遍:下面演示Demo千万不要用于实际项目千万不要用于实际项目千万不要用于实际项目。文章最后我会给出正确用法。

修改 _InheritedWidgetDemoState

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: '老孟', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo = UserInfo(name: '老孟1', age: 18);
          });
        },
      ),
    );
  }
}

点击按钮的时候,UI刷新了,但请重点看右侧 rebuild stats 部分,每点击一次按钮,MyInheritedWidget 组件及其子组件全部 重新构建(rebuild),但 A 组件并不依赖于 MyInheritedWidget 共享数据,理想情况下不应该 rebuild,实际项目中,树的结构会比这个复杂的多,因此全部 rebuild 会造成性能问题,这也是开头说千万不要将此方式用于实际项目,网上充斥着大量此种用法的文章,后面会给出正确用法,正确用法比较复杂,而且涉及其他相关知识,所以此处的Demo仅用于学习 InheritedWidget。

大家是否还记得 Stateful 组件的生命周期 文章中介绍的 didChangeDependencies 生命周期,对其的介绍如下:

didChangeDependencies 方法在 initState 之后由 Framework 立即调用。另外,当此 State 对象的依赖项更改时被调用,比如其所依赖的 InheritedWidget 发生变化时, Framework 会调用此方法通知组件发生变化。

下面将 A 和 F 组件改为 StatefulWidget 组件:

class F extends StatefulWidget {
  @override
  _FState createState() => _FState();
}

class _FState extends State<F> {
  @override
  void initState() {
    super.initState();
    print('F initState');
  }

  @override
  Widget build(BuildContext context) {
    print('F build');
    return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('F didChangeDependencies');
  }

  @override
  void dispose() {
    super.dispose();
    print('F dispose');
  }
}

class A extends StatefulWidget {
  final Widget child;

  const A({Key key, this.child}) : super(key: key);

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

class _AState extends State<A> {
  @override
  void initState() {
    super.initState();
    print('A initState');
  }

  @override
  Widget build(BuildContext context) {
    print('A build');
    return Center(
      child: widget.child,
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('A didChangeDependencies');
  }

  @override
  void dispose() {
    super.dispose();
    print('A dispose');
  }
}

给各个生命周期添加日志打印,重新运行,点击按钮,输出日志如下:

flutter: A build
flutter: F didChangeDependencies
flutter: F build

因此,依赖 MyInheritedWidget 组件的 F 组件调用 didChangeDependencies 方法,而 A 组件没有调用 didChangeDependencies 方法,因为 A 没有依赖 MyInheritedWidget 组件。

下面再说一个非常容易忽略的地方 MyInheritedWidget.updateShouldNotify方法,一般这样写:

@override
bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
  return oldWidget.userInfo != userInfo;
}

这样写有什么问题吗?如果数据(userInfo)是自定义的实体类且未在 UserInfo 中重写 ==,那么极大概率出现有问题,因为不重写 == 操作符方法,使用 != 判断是否相等的时候判断的是两个对象的内存地址,下面将 UserInfo 中 == 方法去掉,

class UserInfo {
  String name;
  int age;

  UserInfo({this.name, this.age});
}

修改 _InheritedWidgetDemoState 类:

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: '老孟', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo = UserInfo(name: '老孟', age: 18);
          });
        },
      ),
    );
  }
}

修改 updateShouldNotify 方法,添加日志打印:

@override
bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
  bool flag = oldWidget.userInfo != userInfo;
  print('updateShouldNotify:$flag');
  return flag;
}

点击按钮,_userInfo 对象引用发生了变化,但其值( name 和 age)都没有发生变化,updateShouldNotify 应该返回false,但实际打印的结果:

flutter: updateShouldNotify:true
flutter: A build
flutter: F didChangeDependencies
flutter: F build

实际返回了 true,因为前后 _userInfo 对象引用发生了变化,在 UserInfo 中重写 ==,比较具体的name 和 age 是否相等:

@override
bool operator ==(Object other) {
  if (!(other is UserInfo)) {
    return false;
  }
  var old = other as UserInfo;
  return name == old.name && age == old.age;
}

再次运行,日志如下:

flutter: updateShouldNotify:false
flutter: A build
flutter: F build

还有一种错误写法:

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: '老孟', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo.name = '老孟1';
            // _userInfo = UserInfo(name: '老孟', age: 18);
          });
        },
      ),
    );
  }
}

重点看这部分修改:

floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo.name = '老孟1';
            // _userInfo = UserInfo(name: '老孟', age: 18);
          });
        },
      ),

_userInfo = UserInfo(name: '老孟', age: 18) 修改为 _userInfo.name = '老孟1',猜猜 updateShouldNotify 返回是 true or false?

运行日志:

flutter: updateShouldNotify:false
flutter: A build
flutter: F build

是不是感觉非常不可思议,两次的 name 值不一样啊?

那是因为 _userInfo.name = '老孟1' 也修改了 oldWidget 的_userInfo,前后两次都指向了同一个对象引用。

很多人应该会有这样一个疑问,假设设置 updateShouldNotify 返回false,点击的时候UI也会更改,因为整颗树都 rebuild 了,那么 updateShouldNotify 由什么意义呢?

肯定是有意义的,看如下场景,F 组件使用 InheritedWidget 的共享数据访问服务器接口,获取服务器数据并展示,如果 updateShouldNotify 返回 false,那么 F 组件 rebuild 时只会执行 build 函数,而访问服务器接口是一个耗时工作,考虑性能因素,不能将访问服务器接口放在 build 函数中,那么 InheritedWidget 数据的更新就无法更新其依赖的组件,而 updateShouldNotify 返回 true时, F 组件 rebuild 时会执行 didChangeDependenciesbuild 函数,此时可以将访问服务器接口放在 didChangeDependencies 函数中,这也是 didChangeDependencies 生命周期存在的意义。

下面重点来了,那么如何正确使用 InheritedWidget 组件,答案是 InheritedWidget + ValueNotifier,关于 ****的用法可以到我的个人博客中查看,地址:http://laomengit.com/flutter/widgets/ValueListenableBuilder.html ,这里不详细展开介绍。

修改 MyInheritedWidget 代码:

class MyInheritedWidget extends InheritedWidget {
  ValueNotifier<UserInfo> _valueNotifier;

  ValueNotifier<UserInfo> get valueNotifier => _valueNotifier;

  MyInheritedWidget(UserInfo userInfo, {Widget child}) : super(child: child) {
    _valueNotifier = ValueNotifier<UserInfo>(userInfo);
  }

  static MyInheritedWidget of(BuildContext context) {
    return context.getElementForInheritedWidgetOfExactType<MyInheritedWidget>().widget;
  }

  void updateData(UserInfo info) {
    _valueNotifier.value = info;
  }

  @override
  bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
    return false;
  }
}

主要的变化是:

  • 共享数据由 UserInfo 类型变为了 ValueNotifier<UserInfo>。

  • 增加了更新数据的方法 updateData

  • updateShouldNotify 方法直接返回了 false,因为数据的更新通过 ValueNotifier 实现。

  • 静态方法 of

    static MyInheritedWidget of(BuildContext context) {
      return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
    }
    

    改为

    static MyInheritedWidget of(BuildContext context) {
      return context.getElementForInheritedWidgetOfExactType<MyInheritedWidget>().widget;
    }
    

    区别是 dependOnInheritedWidgetOfExactType 注册了依赖关系,而 getElementForInheritedWidgetOfExactType 未注册,后面的源码部分会详细分析。

修改 F 组件的代码:

class F extends StatefulWidget {
  @override
  _FState createState() => _FState();
}

class _FState extends State<F> {
  @override
  void initState() {
    super.initState();
    print('F initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('F didChangeDependencies');
  }

  @override
  Widget build(BuildContext context) {
    print('F build');
    return ValueListenableBuilder(
      builder: (context, UserInfo value, child) {
        return Text('${value.name}');
      },
      valueListenable: MyInheritedWidget.of(context).valueNotifier,
    );
  }

  @override
  void dispose() {
    super.dispose();
    print('F dispose');
  }
}

变化:

  • 依赖共享数据的Text组件添加了 ValueListenableBuilder 组件,数据发生变化时,重新执行 builder 。

_InheritedWidgetDemoState 代码修改如下:

@override
Widget build(BuildContext context) {
  return MyInheritedWidget(UserInfo(name: '老孟', age: 18), child: Builder(
    builder: (context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('InheritedWidget Demo'),
        ),
        body: Center(
          child: A(
            child: F(),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            MyInheritedWidget.of(context)
                .updateData(UserInfo(name: '老孟${_clickCount++}', age: 18));
          },
        ),
      );
    },
  ));
}

运行效果:

重点看 rebuild 的组件,无关的组件(比如 A)没有 rebuild

当然也可以使用 Provider 实现子组件更新,增加 UserInfoModel

class UserInfoModel extends ChangeNotifier {
  UserInfoModel(this._userInfo);

  UserInfo _userInfo;

  UserInfo get userInfo => _userInfo;

  void update(UserInfo userInfo) {
    _userInfo = userInfo;
    notifyListeners();
  }
}

修改 _InheritedWidgetDemoState

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  int _clickCount =0;

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
            create: (_) => UserInfoModel(UserInfo(name: '老孟', age: 18))),
      ],
      builder: (context, child) {
        return Scaffold(
          appBar: AppBar(
            title: Text('InheritedWidget Demo'),
          ),
          body: Center(
            child: A(
              child: F(),
            ),
          ),
          floatingActionButton: Consumer<UserInfoModel>(
            builder: (ctx, userInfoModel, child) {
              return FloatingActionButton(
                child: child,
                onPressed: () {
                  userInfoModel.update(UserInfo(name: '老孟${_clickCount++}', age: 18));
                },
              );
            },
          ),
        );
      },
    );
  }
}

InheritedWidget 源码分析

分析源码的时候一定要先想想,如果是我来实现这个组件,要如何实现? InheritedWidget 组件主要实现了两个功能:

  • 如何实现绑定依赖它的子组件
  • 如何通知子组件自己发生了更改。

如何实现绑定依赖它的子组件

依赖 InheritedWidget 的子组件如何获取 InheritedWidget 组件的共享数据?首先查看获取共享数据的方法:

context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();

这段代码获取 MyInheritedWidget 实例,dependOnInheritedWidgetOfExactType 方法是 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) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

从上面的源代码可以看出,首先到 _inheritedWidgets 中查找指定的 InheritedElement,_inheritedWidgets 这个 Map 是哪里来的?什么时候被初始化的?看下 _inheritedWidgets 属性的定义:

Map<Type, InheritedElement> _inheritedWidgets;

查找其引用和赋值的源代码:

@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 中,但此方法在 Element 中也实现了:

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

上面的代码说明了非 InheritedElement 的 Element 中 _inheritedWidgets 等于父组件的 _inheritedWidgets,而 InheritedElement 会将自身添加到 _inheritedWidgets 中,系统通过此方式将组件和 InheritedWidgets 的依赖关系层层向下传递,每一个 Element 中都含有 _inheritedWidgets 集合,此集合中包含了此组件的父组件且是InheritedWidgets 组件的引用关系。

那么是什么时候执行的 _updateInheritance 方法的呢?通过查找其引用,发现在 mountactivate 中调用了 _updateInheritance 方法。关于 mountactivate 阶段可以查看 Stateful 组件的生命周期 文章。

下面查看 dependOnInheritedElement 方法,在查找到依赖的 InheritedElement 后,执行 dependOnInheritedElement 方法,源代码如下:

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

updateDependencies 方法源代码如下:

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

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

上面的代码就是向 _dependents 中添加注册, InheritedWidget 组件更新时可以更具此列表通知子组件。

再来看下的代码:

@Deprecated(
  'Use getElementForInheritedWidgetOfExactType instead. '
  'This feature was deprecated after v1.12.1.'
)
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
  return ancestor;
}

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

在 v1.12.1 版本以前使用 ancestorInheritedElementForWidgetOfExactType 方法,现在使用 getElementForInheritedWidgetOfExactType 方法,此方法和 dependOnInheritedWidgetOfExactType 方法的唯一却不就是 getElementForInheritedWidgetOfExactType 方法没有注册,也就是使用 getElementForInheritedWidgetOfExactType 方法获取共享数据的子组件,不会在 InheritedWidget 组件重建时调用 didChangeDependencies 方法。

下面看看为什么 InheritedWidget 组件数据方式变化,重建时会调用其 didChangeDependencies 方法?

当组件发生变化时会调用 update方法:

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

InheritedElement 重写了 updated 方法:

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

updateShouldNotify 返回 true时,执行更新操作。而其父类的 updated 方法如下:

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

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

遍历 _dependents,上面已经介绍,_dependents 是依赖它的子组件集合,遍历调用 notifyDependent 方法:

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

这里调用了 didChangeDependencies 方法,这也是 InheritedWidget 组件发生变化,重建时执行生命周期 didChangeDependencies

上面的代码都是在 InheritedElement 中的,在看下 InheritedWidget 的源代码:

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

这个类非常简单,创建了一个 InheritedElement,定义了一个 updateShouldNotify 方法(上面已经详细介绍此方法的作用),子类需要重写。

通过上面的源码解析,子组件获取共享数据时,实际是直接在 _inheritedWidgets 集合中匹配的,通过断点也可以查看其中的内容:

总结

通过上面的分析,InheritedWidget 组件流程如下:

说明:

  • 在当前组件的 mountactivate 阶段,系统调用 _updateInheritance 方法,将 InheritedWidget 类型的父组件添加到 _inheritedWidgets 集合中。
  • 子组件执行 dependOnInheritedWidgetOfExactType 方法时,从 _inheritedWidgets 集合中获取指定 InheritedWidget 类型的父组件,并将当前组件注册到 InheritedWidget 类型父组件的 _dependents 集合中。
  • InheritedWidget 组件数据发生变化(updateShouldNotify 方法返回true),重建时,InheritedWidget 组件遍历 _dependents 集合中所有依赖的子组件,执行子组件的 didChangeDependencies 的方法。

一点看法

那么为什么是在当前组件的中保存这样一个 Map 集合,而不是依次向上查找呢(我最开始的想法)?

下面是我个人的一点看法,如果你有不同的看法,欢迎一起讨论:

当前组件的中保存这样一个 Map 集合,获取共享数据时直接定位依赖的 InheritedWidget,复杂度 O(1) 。

而依次向上查找的复杂度是 O(n),树的结构越深,消耗时间越长,复杂度线性增长。

交流

博客中领取 《330个控件大全》和 《Flutter 实战》PDF。地址:http://laomengit.com

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

推荐阅读更多精彩内容