注:本文代码基于Flutter SDK 3.13.5

一、子Widget多层嵌套之状态管理问题

Flutter开发中,如果父Widget想要控制子Widget的状态,最常见的办法是把子Widget的状态提升到父Widget中,当父Widget修改该状态并且执行了setState方法之后,子Widget就会发生重建,而子Widget可通过构造方法传入父Widget的状态,然后在build方法中使用该状态即可。

例如下面这个计数器示例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyPage(title: '计数器'),
    );
  }
}

class MyPage extends StatefulWidget {
  const MyPage({super.key, required this.title});

  final String title;

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: MyChild(counter: _counter),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        tooltip: 'increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class MyChild extends StatelessWidget {
  final int counter;

  const MyChild({super.key, required this.counter});

  @override
  Widget build(BuildContext context) {
    debugPrint('build');
    return Center(
      child: Text(
        '$counter',
        style: const TextStyle(
          fontSize: 100,
          color: Colors.black,
        ),
      ),
    );
  }
}

程序运行UI效果为:

点击FloatingActionButton时日志打印如下:

1
I/flutter ( 7101): build

但是,如果考虑到父Widget与子Widget之间嵌套很深的情况时,上面的方案其实就不是那么好使了,因为状态的传递需要通过子Widget的构造方法一层一层地传入,这样维护起来非常麻烦且不够优雅,那么Flutter官方有提供什么解决办法呢?是有的,它就是InheritedWidget

二、什么是InheritedWidget

遇事不决,先看InheritedWidget的注释。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/// 有效地沿树传播信息的widgets的基类。
/// Base class for widgets that efficiently propagate information down the tree.
///
/// 要从构建上下文获取特定类型的inherited widget的最近实例,请使用BuildContext.dependOnInheritedWidgetOfExactType。
/// To obtain the nearest instance of a particular type of inherited widget from
/// a build context, use [BuildContext.dependOnInheritedWidgetOfExactType].
///
/// 当以这种方式引用Inherited widgets时,当Inherited widgets本身更改状态时,将导致使用者重建。
/// Inherited widgets, when referenced in this way, will cause the consumer to
/// rebuild when the inherited widget itself changes state.
///
/// 以下是名为FrogColor的inherited widget的骨架
/// The following is a skeleton of an inherited widget called `FrogColor`:
///
/// ```dart
/// class FrogColor extends InheritedWidget {
///   const FrogColor({
///     super.key,
///     required this.color,
///     required super.child,
///   });
///
///   final Color color;
///
///   static FrogColor? maybeOf(BuildContext context) {
///     return context.dependOnInheritedWidgetOfExactType<FrogColor>();
///   }
///
///   static FrogColor of(BuildContext context) {
///     final FrogColor? result = maybeOf(context);
///     assert(result != null, 'No FrogColor found in context');
///     return result!;
///   }
///
///   @override
///   bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color;
/// }
/// ```
/// {@end-tool}
///
/// ## 实现“of”和“maybeOf”方法
/// ## Implementing the `of` and `maybeOf` methods
///
/// 约定是在InheritedWidget上提供两个静态方法of和maybeOf ,它们调用BuildContext.dependOnInheritedWidgetOfExactType。
/// 这允许类定义自己的后备逻辑,以防范围内没有widget
/// The convention is to provide two static methods, `of` and `maybeOf`, on the
/// [InheritedWidget] which call
/// [BuildContext.dependOnInheritedWidgetOfExactType]. This allows the class to
/// define its own fallback logic in case there isn't a widget in scope.
///
/// `of` 方法通常返回一个不可为 null 的实例,并在未找到 [InheritedWidget] 时断言,而 `maybeOf` 方法则返回一个可为 null 的实例,并在未找到 [InheritedWidget] 时返回 null。 
/// “of”方法通常通过内部调用“maybeOf”来实现。
/// The `of` method typically returns a non-nullable instance and asserts if the
/// [InheritedWidget] isn't found, and the `maybeOf` method returns a nullable
/// instance, and returns null if the [InheritedWidget] isn't found. The `of`
/// method is typically implemented by calling `maybeOf` internally.
///
/// 有时,“of”和“maybeOf”方法返回一些数据,而不是inherited widget本身;例如,在这种情况下,它可能返回一个 [Color] 而不是“FrogColor”小部件。
/// Sometimes, the `of` and `maybeOf` methods return some data rather than the
/// inherited widget itself; for example, in this case it could have returned a
/// [Color] instead of the `FrogColor` widget.
///
/// 有时,inherited widget是另一个类的实现细节,因此是私有的。
/// 在这种情况下,“of”和“maybeOf”方法通常在公共类上实现。
/// 例如,[Theme]被实现为[StatelessWidget],它构建了一个私有inherited widget; [Theme.of] 使用 [BuildContext.dependOnInheritedWidgetOfExactType] 查找inherited widget,然后返回其中的 [ThemeData]。
/// Occasionally, the inherited widget is an implementation detail of another
/// class, and is therefore private. The `of` and `maybeOf` methods in that case
/// are typically implemented on the public class instead. For example, [Theme]
/// is implemented as a [StatelessWidget] that builds a private inherited
/// widget; [Theme.of] looks for that private inherited widget using
/// [BuildContext.dependOnInheritedWidgetOfExactType] and then returns the
/// [ThemeData] inside it.
///
/// ## 调用“of”或“maybeOf”方法
/// ## Calling the `of` or `maybeOf` methods
///
/// 使用“of”或“maybeOf”方法时,“context”必须是 [InheritedWidget] 的后代,这意味着它必须位于树中 [InheritedWidget]“下方”。
/// When using the `of` or `maybeOf` methods, the `context` must be a descendant
/// of the [InheritedWidget], meaning it must be "below" the [InheritedWidget]
/// in the tree.
///
/// 在此示例中,使用的“context”是来自 [Builder] 的context,它是“FrogColor” widget的子级,因此这是可行的。
/// In this example, the `context` used is the one from the [Builder], which is
/// a child of the `FrogColor` widget, so this works.
///
/// ```dart
/// // continuing from previous example...
/// class MyPage extends StatelessWidget {
///   const MyPage({super.key});
///
///   @override
///   Widget build(BuildContext context) {
///     return Scaffold(
///       body: FrogColor(
///         color: Colors.green,
///         child: Builder(
///           builder: (BuildContext innerContext) {
///             return Text(
///               'Hello Frog',
///               style: TextStyle(color: FrogColor.of(innerContext).color),
///             );
///           },
///         ),
///       ),
///     );
///   }
/// }
/// ```
/// 在此示例中,使用的“context”是来自“MyOtherPage” widget的上下文,该widget是“FrogColor”widget的父级,因此这不起作用,并且会在调用“FrogColor.of”时断言。
/// In this example, the `context` used is the one from the `MyOtherPage`
/// widget, which is a parent of the `FrogColor` widget, so this does not work,
/// and will assert when `FrogColor.of` is called.
///
/// ```dart
/// // continuing from previous example...
///
/// class MyOtherPage extends StatelessWidget {
///   const MyOtherPage({super.key});
///
///   @override
///   Widget build(BuildContext context) {
///     return Scaffold(
///       body: FrogColor(
///         color: Colors.green,
///         child: Text(
///           'Hello Frog',
///           style: TextStyle(color: FrogColor.of(context).color),
///         ),
///       ),
///     );
///   }
/// }
/// ```

可以看到,注释详细地描述了InheritedWidget的特性以及贴心地给出了相应的示例,根据InheritedWidget的注释,可以总结下它的特性:

1、InheritedWidget是沿着Widget树传播信息的基类。

2、可以使用BuildContext.dependOnInheritedWidgetOfExactType获取特定类型的InheritedWidget的最近实例。

3、当InheritedWidget本身更改状态时,将导致使用者重建。

4、使用ofmaybeOf方法时,context必须是InheritedWidget的后代,这意味着它必须位于树中InheritedWidget下方。

5、…

这里给出了InheritedWidget的部分特性,主要是想让大家对InheritedWidget有一个初级认知。

继续看下InheritedWidget的源码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class InheritedWidget extends ProxyWidget {

  const InheritedWidget({ super.key, required super.child });

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

  /// 框架是否应该通知继承自此widget的widgets
  /// Whether the framework should notify widgets that inherit from this widget.
  ///
  /// 当这个widget被重建时,有时我们需要重建从这个widget继承的widgets,但有时我们不需要。
  /// 例如,如果这个widget保存的数据与“oldWidget”保存的数据相同,那么我们不需要重建继承“oldWidget”保存数据的widgets。
  /// 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`.
  ///
  /// 框架通过使用先前占据树中此位置的widget作为参数来调用此函数来区分这些情况。
  /// 保证给定的widget与该对象具有相同的 [runtimeType]。
  /// 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);
}

可以发现,InheritedWidget继承自ProxyWidgetInheritedWidget的构造方法需要传入一个childcreateElement方法返回了一个InheritedElement实例,并且在父类ProxyWidget的基础上新增了一个updateShouldNotify方法,从该方法的注释也可以了解到,它用于判断是否应该通知继承InheritedWidgetWidget

三、InheritedWidget示例演示

使用InheritedWidget改造之前的计数器示例。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyPage(title: '计数器'),
    );
  }
}

class MyPage extends StatefulWidget {
  const MyPage({super.key, required this.title});

  final String title;

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: MyParent(
        counter: _counter,
        child: const MyChild(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        tooltip: 'increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class MyParent extends InheritedWidget {
  final int counter;

  const MyParent({super.key, required this.counter, required super.child});

  static MyParent? maybeOf(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<MyParent>();

  static MyParent of(BuildContext context) {
    final MyParent? result = maybeOf(context);
    assert(result != null, 'No MyParent found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(covariant MyParent oldWidget) => counter != oldWidget.counter;
}

class MyChild extends StatefulWidget {
  const MyChild({super.key});

  @override
  State<MyChild> createState() => _MyChildState();
}

class _MyChildState extends State<MyChild> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    debugPrint('didChangeDependencies');
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('build');
    return Center(
      child: Text(
        '${MyParent.of(context).counter}',
        style: const TextStyle(
          fontSize: 100,
          color: Colors.black,
        ),
      ),
    );
  }
}

程序运行UI效果和之前的计数器示例一样,可以参考上面gif图。

InheritedWidget示例中,点击FloatingActionButton时日志打印如下:

1
2
I/flutter ( 7101): didChangeDependencies
I/flutter ( 7101): build

在上面的InheritedWidget示例中,可以发现存在3个疑点:

1、对于之前的计数器示例,每次点击FloatingActionButton时仅会触发build方法,而对于InheritedWidget示例,如果目标子WidgetStatefulWidget,那么还会触发StatedidChangeDependencies方法,这是为什么呢?

2、如果把InheritedWidget示例中const MyChild()的const修饰去掉,会发生什么?

3、我们知道,子Widget通过MyParent.of方法获取InheritedWidget的实例,使用的是contextdependOnInheritedWidgetOfExactType方法,除此之外还有一个getInheritedWidgetOfExactType方法也可以获取InheritedWidget的实例,那这两个方法之间有什么区别?

关于这3个疑点,等下分析源码时再进行讲解。

四、分析InheritedWidget源码

4.1、InheritedElement注册阶段

程序启动后,当执行到MyChild关联的Statebuild方法时,就会调用MyParent.of方法,而InheritedElement注册阶段在于contextdependOnInheritedWidgetOfExactType方法,具体实现在Element,点击进去看下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  // 分析1
  final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
  // 分析2
  if (ancestor != null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  // 分析3
  _hadUnsatisfiedDependencies = true;
  return null;
}

分析1:这里涉及Element的成员变量_inheritedElements,代码如下:

1
PersistentHashMap<Type, InheritedElement>? _inheritedElements;

可以看到_inheritedElements的类型是PersistentHashMap,它是一个键值对的集合,其中KeyType,这里的TypeInheritedWidgetInheritedWidget的子类,ValueInheritedElement

在分析1中,首先判断_inheritedElements == null,如果不为null,那么以InheritedWidget类型为Key获取InheritedElement

那么_inheritedElements是在哪里赋值的?对于非InheritedElement来说,它是在Element_updateInheritance()方法中赋值,看下它的源码。

1
2
3
4
void _updateInheritance() {
  assert(_lifecycleState == _ElementLifecycle.active);
  _inheritedElements = _parent?._inheritedElements;
}

而对于InheritedElement来说,它是在InheritedElement_updateInheritance()方法中赋值,看下它的源码。

1
2
3
4
5
6
7
@override
void _updateInheritance() {
  assert(_lifecycleState == _ElementLifecycle.active);
  final PersistentHashMap<Type, InheritedElement> incomingWidgets =
      _parent?._inheritedElements ?? const PersistentHashMap<Type, InheritedElement>.empty();
  _inheritedElements = incomingWidgets.put(widget.runtimeType, this);
}

可以看到,如果父类的_inheritedElementsnull,那么就会创建一个空的PersistentHashMap实例,赋值给局部变量incomingWidgets,然后以InheritedWidgetruntimeTypeKey,把当前的InheritedElement作为Value存入该HashMap中。

那么_updateInheritance()方法是在哪里触发的?它是在Elementmount方法中触发的,也就是当InheritedElement添加到Element树时,就会将InheritedElement存入到这个_inheritedElements

以及在Elementactivate方法中触发的,也就是当InheritedElement重新合并到Element树时,就会将InheritedElement存入到这个_inheritedElements

现在知道了为什么通过dependOnInheritedWidgetOfExactType方法能够获取距离目标子Widget最近的一个InheritedWidget子类的实例了?

因为InheritedWidget子类的runtimeType作为Key是唯一的,对于相同类型的Key,每次添加InheritedElement_inheritedElements这个HashMap时会覆盖掉之前的InheritedElement

分析2:如果以InheritedWidget类型为KeyHashMap中获取的InheritedElement不为null,那么就会执行dependOnInheritedElement方法,看下它的源码。

1
2
3
4
5
6
7
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
  _dependencies ??= HashSet<InheritedElement>();
  _dependencies!.add(ancestor);
  ancestor.updateDependencies(this, aspect);
  return ancestor.widget as InheritedWidget;
}

这里涉及Element的成员变量_dependencies,代码如下:

1
Set<InheritedElement>? _dependencies;

可以看到_dependencies的类型是HashSet,用来存放InheritedElement,为什么用一个Set集合来存放这些InheritedElement呢?

因为当前Element(比如MyChild所关联的StatefulElement)所依赖的InheritedWidget可能不值一个,并且比如多次调用MyParent.of时,Set确保同一个InheritedElement只能添加一次。

dependOnInheritedElement方法中,如果_dependenciesnull,就会创建一个空的HashSet,然后把从_inheritedElements![T]获取的ancestor存入该HashSet

紧接着执行ancestor(也就是InheritedElement)的updateDependencies方法。

1
2
3
4
@protected
void updateDependencies(Element dependent, Object? aspect) {
  setDependencies(dependent, null);
}

InheritedElementupdateDependencies方法中,执行了setDependencies方法。

1
2
3
4
@protected
void setDependencies(Element dependent, Object? value) {
  _dependents[dependent] = value;
}

这里涉及InheritedElement的成员变量_dependents,代码如下:

1
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

可以看到_dependents的类型是HashMap,也就是一个键值对的集合,其中KeyElementValueObject

将当前Element(此处是MyChild所关联的StatefulElement)作为Key,此时value为null,存入了该HashMap

目前所讲源码中涉及了3个集合_inheritedElements_dependencies_dependents,它们之间的关系如下图。

分析3:回到dependOnInheritedWidgetOfExactType方法,如果_inheritedElements == null成立,那么该方法返回null,表示查找不到指定类型的InheritedWidget

到这里先解释下之前的疑点3:

我们知道,子Widget通过MyParent.of方法获取InheritedWidget的实例,使用的是context的dependOnInheritedWidgetOfExactType方法,除此之外还有一个getInheritedWidgetOfExactType方法也可以获取InheritedWidget的实例,那这两个方法之间有什么区别?

看下getInheritedWidgetOfExactType方法源码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@override
T? getInheritedWidgetOfExactType<T extends InheritedWidget>() {
  return getElementForInheritedWidgetOfExactType<T>()?.widget as T?;
}

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

可以看到,相比于dependOnInheritedWidgetOfExactType方法,是少了InheritedElement注册部分,所以如果InheritedWidget的状态发生改变,是无法通知子Widget进行重建的。

1
2
3
if (ancestor != null) {
  return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}

4.2、InheritedElement通知更新阶段

当点击FloatingActionButton时,就会执行setState方法,那么示例中_MyPageStatebuild方法就会执行。

关于setState方法的分析请参考解读Flutter源码之setState一文,InheritedElement通知更新阶段从BuildOwnerbuildScope方法开始讲起。

BuildOwnerbuildScope方法的While循环中,当前正在执行element.rebuild()方法,此处element正是MyPage所关联的StatefulElement,注意BuildOwner中的一个布尔值成员变量_scheduledFlushDirtyElements,因为While循环还没执行完,所以不会执行到finally语句(它在finally语句中修改为false),所以布尔值为trueOK,知道这些信息就行。

MyPage的重建一直到MyParent的重建,中间嵌套太多层了,这部分不会进行讲解,而是从MyParent的重建开始讲起。

当执行到MyParent的重建时,就会执行ElementupdateChild方法,因为MyParent不是const修饰,所以每次都会创建一个MyParent新实例,那么 if (hasSameSuperclass && child.widget == newWidget)是不成立的,就会执行下面的else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)),这里MyParent是没有传入Key的,所以满足else if条件。

接着执行child.update(newWidget),也就是执行Elementupdate方法。

又因为MyParent所关联的ElementInheritedElement,而InheritedElement的父类为ProxyElement,所以执行了ProxyElementupdate方法。

1
2
3
4
5
6
7
8
9
@override
void update(ProxyWidget newWidget) {
  final ProxyWidget oldWidget = widget as ProxyWidget;
  assert(widget != newWidget);
  super.update(newWidget);
  assert(widget == newWidget);
  updated(oldWidget);
  rebuild(force: true);
}

ProxyElementupdate方法中,一共做了3件事情:

1、执行super.update(newWidget),更新InheritedElement所关联的Widget,也就是MyParent

2、执行updated(oldWidget)方法,这里是执行了InheritedElementupdated方法,看下它的源码。

1
2
3
4
5
6
@override
void updated(InheritedWidget oldWidget) {
  if ((widget as InheritedWidget).updateShouldNotify(oldWidget)) {
    super.updated(oldWidget);
  }
}

InheritedElementupdated方法中,先执行updateShouldNotify方法,也就是我们在MyParent中实现的方法,用于判断是否应该通知继承InheritedWidgetWidget

如果updateShouldNotify方法返回true,那么执行super.updated(oldWidget),也就是执行父类ProxyElementupdated方法。

1
2
3
4
@protected
void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}

ProxyElementupdated方法中,执行了notifyClients方法,这个方法由子类实现,这里看InheritedElementnotifyClients方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@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);
  }
}

InheritedElementnotifyClients方法中,for循环遍历了_dependentskeys,然后在for循环中执行了notifyDependent方法。

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

InheritedElementnotifyDependent方法中,执行了ElementdidChangeDependencies方法。因为之前注册的就是我们自己的MyChild所关联的StatefulElement,所以执行的StatefulElementdidChangeDependencies方法。

1
2
3
4
5
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  _didChangeDependencies = true;
}

StatefulElementdidChangeDependencies方法中,执行了super.didChangeDependencies(),也就是执行了父类ElementdidChangeDependencies方法,然后把_didChangeDependencies赋值为true

1
2
3
4
5
6
@mustCallSuper
void didChangeDependencies() {
  assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}

ElementdidChangeDependencies方法中,执行了ElementmarkNeedsBuild方法,markNeedsBuild方法是老朋友了,它会将Element标记为脏并将其添加到Widget的全局列表中以在下一帧中重建,这里的ElementMyChild所关联的StatefulElement

ElementmarkNeedsBuild方法中,执行了BuildOwnerscheduleBuildFor方法。

BuildOwnerscheduleBuildFor方法中,执行if (!_scheduledFlushDirtyElements && onBuildScheduled != null),还记得之前讲的成员变量_scheduledFlushDirtyElements的值为true吗,这是因为MyParent及其子Widget还没重建完成。

所以if条件不成立,onBuildScheduled!()不会执行,这里只是把MyChild所关联的StatefulElement添加进_dirtyElements

整体上看MyPage的重建已经包括了MyChild的重建,所以这里MyChild的重建是不需要单独放在下一帧去处理了,还是放在_MyPageState最初执行setState时的那一帧进行重建。

此时需要While循环处理的_dirtyElements如下。

3、回到ProxyElementupdate方法中,接着执行rebuild(force: true),也就是执行Elementrebuild方法,这个方法是老朋友了,在它的内部会执行performRebuild方法。

因为ComponentElementProxyElement的父类,所以这里执行了ComponentElementperformRebuild方法。

ComponentElementperformRebuild方法中,执行了MyParent所关联的Elementbuild方法,也就是ProxyElementbuild方法,这个build方法就是用来构建MyChild这个Widget的。

ComponentElementperformRebuild方法中,接着执行了ElementupdateChild方法。因为MyChild使用const修饰,它相当于一个常量,所以满足if (hasSameSuperclass && child.widget == newWidget),然后直接返回之前的Element,也就是MyParent所关联的Element

到这里就解释了之前的疑点2:

如果把InheritedWidget示例中const MyChild()的const修饰去掉,会发生什么?

OK,回到最初在_MyPageState中执行setState方法时,它会触发BuildOwnerbuildScope方法,在第一轮While循环中,任务是重建MyPage,现在已经循环完了,然后index+1。

下面进入第二轮While循环来重建MyChild,执行了Elementrebuild方法。

之前讲过,在Elementrebuild方法中会执行performRebuild方法,此时执行的就是StatefulElementperformRebuild方法。

1
2
3
4
5
6
7
8
@override
void performRebuild() {
  if (_didChangeDependencies) {
    state.didChangeDependencies();
    _didChangeDependencies = false;
  }
  super.performRebuild();
}

因为之前_didChangeDependencies已经赋值为true,所以这里执行了state.didChangeDependencies(),触发了StatedidChangeDependencies方法。

到这里就解释了之前的疑点1:

对于之前的计数器示例,每次点击FloatingActionButton时仅会触发build方法,而对于InheritedWidget示例,如果目标子Widget是StatefulWidget,那么还会触发State的didChangeDependencies方法,这是为什么呢?

接着执行super.performRebuild(),调用父类ComponentElementperformRebuild方法,这个方法也是老朋友了,最终会触发MyChildbuild方法。

4.3、InheritedElement取消注册阶段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@mustCallSuper
void deactivate() {
  assert(_lifecycleState == _ElementLifecycle.active);
  assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
  if (_dependencies != null && _dependencies!.isNotEmpty) {
    for (final InheritedElement dependency in _dependencies!) {
      dependency._dependents.remove(this);
    }
    // 为了方便起见,我们实际上并没有清除此处的列表,尽管它不再代表我们注册的内容。
    // 如果我们永远不会被重用,那也没关系。
    // 如果这样做,那么我们将在 activate() 中清除该列表。
    // 这样做的好处是,它允许 Element 的 activate() 实现根据我们这里是否有依赖关系来决定是否重建。
    // For expediency, we don't actually clear the list here, even though it's
    // no longer representative of what we are registered with. If we never
    // get re-used, it doesn't matter. If we do, then we'll clear the list in
    // activate(). The benefit of this is that it allows Element's activate()
    // implementation to decide whether to rebuild based on whether we had
    // dependencies here.
  }
  _inheritedElements = null;
  _lifecycleState = _ElementLifecycle.inactive;
}

InheritedElement取消注册是在Elementdeactivate方法,比如当MyChild所关联的Element(也就是StatefulElement)执行了deactivate方法,如果_dependencies不为null且不为空,那么将当前ElementInheritedElement_dependents中移除。

下次在InheritedElementnotifyClients方法中for循环遍历_dependentskeys时,就无法再通知MyChild所关联的Element

1
2
3
4
5
6
@override
void notifyClients(InheritedWidget oldWidget) {
  for (final Element dependent in _dependents.keys) {
    notifyDependent(oldWidget, dependent);
  }
}

然后执行_inheritedElements = null,把_inheritedElements也置为null

但是,_dependenciesdeactivate方法中是没有被清除的,通过deactivate方法中的注释可以知道,如果Element永远不会被重用,那也没关系。

可以之后在activate方法中清除该列表,这样做的好处是,它允许Elementactivate方法实现根据我们这里是否有依赖关系来决定是否重建。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@mustCallSuper
void activate() {
  assert(_lifecycleState == _ElementLifecycle.inactive);
  assert(owner != null);
  // 是否有依赖关系
  final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
  _lifecycleState = _ElementLifecycle.active;
  // 我们在 deactivate 中注销了我们的依赖项,但从未清除过列表
  // We unregistered our dependencies in deactivate, but never cleared the list.
  // 由于我们将被重用,所以现在让我们清除我们的列表。
  // Since we're going to be reused, let's clear our list now.
  _dependencies?.clear();
  _hadUnsatisfiedDependencies = false;
  _updateInheritance();
  attachNotificationTree();
  if (_dirty) {
    owner!.scheduleBuildFor(this);
  }
  // 如果有有依赖关系,执行didChangeDependencies进行重建
  if (hadDependencies) {
    didChangeDependencies();
  }
}

五、InheritedWidget扩展

除了上面讲的InheritedWidget,其实InheritedWidget还存在一些特定的子类,如下图。

5.1、InheritedModel介绍

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/// 一种 [InheritedWidget],旨在用作模型的基类,其依赖项可能仅依赖于整个模型的一部分或“方面”。
/// An [InheritedWidget] that's intended to be used as the base class for models
/// whose dependents may only depend on one part or "aspect" of the overall
/// model.
///
/// 当inherited widget根据 [InheritedWidget.updateShouldNotify] 发生更改时,inherited widget的依赖项将无条件重建。
/// 此widget类似,只是依赖项不会无条件重建。
/// An inherited widget's dependents are unconditionally rebuilt when the
/// inherited widget changes per [InheritedWidget.updateShouldNotify]. This
/// widget is similar except that dependents aren't rebuilt unconditionally.
///
/// 依赖于 [InheritedModel] 的Widgets用一个值来限定其依赖关系,该值指示它们所依赖的模型的“方面”。
/// 当重建模型时,依赖项也将被重建,但前提是模型中存在与它们提供的方面相对应的更改。
/// Widgets that depend on an [InheritedModel] qualify their dependence with a
/// value that indicates what "aspect" of the model they depend on. When the
/// model is rebuilt, dependents will also be rebuilt, but only if there was a
/// change in the model that corresponds to the aspect they provided.
///
/// 类型参数“T”是模型方面对象的类型。
/// The type parameter `T` is the type of the model aspect objects.
/// 
/// Widgets使用静态方法创建对 [InheritedModel] 的依赖:[InheritedModel.inheritFrom]。
/// 此方法的“context”参数定义模型更改时将重建的子树。
/// 通常,“inheritFrom”方法是从特定于模型的静态“maybeOf”或“of”方法调用的,这是许多查找事物的 Flutter 框架类中存在的约定。例如:
/// Widgets create a dependency on an [InheritedModel] with a static method:
/// [InheritedModel.inheritFrom]. This method's `context` parameter defines the
/// subtree that will be rebuilt when the model changes. Typically the
/// `inheritFrom` method is called from a model-specific static `maybeOf` or
/// `of` methods, a convention that is present in many Flutter framework classes
/// which look things up. For example:
///
/// ```dart
/// class MyModel extends InheritedModel<String> {
///   const MyModel({super.key, required super.child});
///
///   // ...
///   static MyModel? maybeOf(BuildContext context, [String? aspect]) {
///     return InheritedModel.inheritFrom<MyModel>(context, aspect: aspect);
///   }
///
///   // ...
///   static MyModel of(BuildContext context, [String? aspect]) {
///     final MyModel? result = maybeOf(context, aspect);
///     assert(result != null, 'Unable to find an instance of MyModel...');
///     return result!;
///   }
/// }
/// ```
///
/// 调用 `MyModel.of(context, 'foo')` 或 `MyModel.maybeOf(context, 'foo')` 意味着仅当 `MyModel` 的 `foo` aspect发生更改时才应该重建 `context`。
/// 如果“aspect”为空,则模型支持所有aspects。
/// Calling `MyModel.of(context, 'foo')` or `MyModel.maybeOf(context,
/// 'foo')` means that `context` should only be rebuilt when the `foo` aspect of
/// `MyModel` changes. If the `aspect` is null, then the model supports all
/// aspects.
///
/// {@tool snippet}
/// 重建inherited model时, [updateShouldNotify] 和 [updateShouldNotifyDependent] 方法用于决定应该重建什么。
/// 如果 [updateShouldNotify] 返回 true,则针对每个依赖项及其依赖的aspect对象集测试inherited model的 [updateShouldNotifyDependent] 方法。
/// [updateShouldNotify Dependent] 方法必须将aspectj依赖项集与模型本身的更改进行比较。例如:
/// When the inherited model is rebuilt the [updateShouldNotify] and
/// [updateShouldNotifyDependent] methods are used to decide what should be
/// rebuilt. If [updateShouldNotify] returns true, then the inherited model's
/// [updateShouldNotifyDependent] method is tested for each dependent and the
/// set of aspect objects it depends on. The [updateShouldNotifyDependent]
/// method must compare the set of aspect dependencies with the changes in the
/// model itself. For example:
///
/// ```dart
/// class ABModel extends InheritedModel<String> {
///   const ABModel({
///    super.key,
///    this.a,
///    this.b,
///    required super.child,
///   });
///
///   final int? a;
///   final int? b;
///
///   @override
///   bool updateShouldNotify(ABModel oldWidget) {
///     return a != oldWidget.a || b != oldWidget.b;
///   }
///
///   @override
///   bool updateShouldNotifyDependent(ABModel oldWidget, Set<String> dependencies) {
///     return (a != oldWidget.a && dependencies.contains('a'))
///       || (b != oldWidget.b && dependencies.contains('b'));
///   }
///
///   // ...
/// }
/// ```
/// {@end-tool}
///
/// 在前面的示例中,[updateShouldNotify Dependent] 检查的依赖项只是传递给“dependOnInheritedWidgetOfExactType”的aspect字符串。
/// 它们被表示为一个 [Set],因为一个 Widget 可以依赖于模型的多个aspect。
/// 如果一个widget依赖于模型但没有指定某个aspect,那么模型中的更改将导致widget无条件地重建。
/// In the previous example the dependencies checked by
/// [updateShouldNotifyDependent] are just the aspect strings passed to
/// `dependOnInheritedWidgetOfExactType`. They're represented as a [Set] because
/// one Widget can depend on more than one aspect of the model. If a widget
/// depends on the model but doesn't specify an aspect, then changes in the
/// model will cause the widget to be rebuilt unconditionally.
///
/// {@tool dartpad}
/// 此示例演示如何实现 [InheritedModel] 以基于合格的依赖关系重建widget。
/// 当点击“调整logo”按钮时,仅重建logo widget,而背景widget不受影响。
/// This example shows how to implement [InheritedModel] to rebuild a widget
/// based on a qualified dependence. When tapped on the "Resize Logo" button
/// only the logo widget is rebuilt while the background widget remains
/// unaffected.
///
/// ** See code in examples/api/lib/widgets/inherited_model/inherited_model.0.dart **
abstract class InheritedModel<T> extends InheritedWidget

通过InheritedModel的注释可以知道,InheritedModel相比于InheritedWidget可以更细粒度地控制依赖于InheritedModel的子Widget的重建。

还有一点,我们经常用的媒体查询MediaQuery也是继承自InheritedModel

5.2、InheritedNotifier介绍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/// [Listenable] [notifier] 的inherited widget,当触发 [notifier] 时更新其依赖项。
/// An inherited widget for a [Listenable] [notifier], which updates its
/// dependencies when the [notifier] is triggered.
///
/// 这是 [InheritedWidget] 的变体,专门用于 [Listenable] 的子类,例如 [ChangeNotifier] 或 [ValueNotifier]。
/// This is a variant of [InheritedWidget], specialized for subclasses of
/// [Listenable], such as [ChangeNotifier] or [ValueNotifier].
///
/// 每当[notifier]发送通知或每当[notifier]的身份发生变化时,依赖项都会收到通知。
/// Dependents are notified whenever the [notifier] sends notifications, or
/// whenever the identity of the [notifier] changes.
///
/// 多个通知被合并,因此即使 [notifier] 在两帧之间触发多次,依赖项也仅重建一次。
/// Multiple notifications are coalesced, so that dependents only rebuild once
/// even if the [notifier] fires multiple times between two frames.
///
/// 通常,该类是一个类的子类,该类提供一个“of”静态方法,该方法使用该类调用 [BuildContext.dependOnInheritedWidgetOfExactType]。
/// Typically this class is subclassed with a class that provides an `of` static
/// method that calls [BuildContext.dependOnInheritedWidgetOfExactType] with that
/// class.
///
/// [updateShouldNotify] 方法也可以被重写,以在 [notifier] 本身发生更改的情况下更改逻辑。
/// 如果 [notifier] 发生更改,则使用旧的 [notifier] 调用 [updateShouldNotify] 方法。
/// 当它返回 true 时,依赖项被标记为需要在该框架中重建。
/// The [updateShouldNotify] method may also be overridden, to change the logic
/// in the cases where [notifier] itself is changed. The [updateShouldNotify]
/// method is called with the old [notifier] in the case of the [notifier] being
/// changed. When it returns true, the dependents are marked as needing to be
/// rebuilt this frame.
///
/// {@tool dartpad}
/// 此示例显示了三个旋转方块,它们使用祖先 [InheritedNotifier] (`SpinModel`) 上的notifier的值来进行旋转。
/// [InheritedNotifier] 不需要了解子级,并且 `notifier` 参数不需要是动画控制器,它可以是任何实现 [Listenable] 的东西(如 [ChangeNotifier])。
/// This example shows three spinning squares that use the value of the notifier
/// on an ancestor [InheritedNotifier] (`SpinModel`) to give them their
/// rotation. The [InheritedNotifier] doesn't need to know about the children,
/// and the `notifier` argument doesn't need to be an animation controller, it
/// can be anything that implements [Listenable] (like a [ChangeNotifier]).
///
/// `SpinModel` 类可以轻松地侦听另一个 [Listenable] 对象(例如,保留输入或数据模型值的值的单独对象),并从中获取值。
/// 后代也不需要拥有 [InheritedNotifier] 的实例即可使用它,他们只需要知道他们的祖先中有一个实例即可。
/// 这有助于将widgets与其模型解耦。
/// The `SpinModel` class could just as easily listen to another object (say, a
/// separate object that keeps the value of an input or data model value) that
/// is a [Listenable], and get the value from that. The descendants also don't
/// need to have an instance of the [InheritedNotifier] in order to use it, they
/// just need to know that there is one in their ancestry. This can help with
/// decoupling widgets from their models.
///
/// ** See code in examples/api/lib/widgets/inherited_notifier/inherited_notifier.0.dart **
/// {@end-tool}
abstract class InheritedNotifier<T extends Listenable> extends InheritedWidget

通过InheritedNotifier的注释可以知道,它需要和Listenable一起使用,当触发 notifier时会更新其依赖项。

5.3、InheritedTheme介绍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// 一个 [InheritedWidget],定义 [child] 子树所依赖的视觉属性,例如颜色和文本样式。
/// An [InheritedWidget] that defines visual properties like colors
/// and text styles, which the [child]'s subtree depends on.
///
/// [captureAll] 和 [CapturedThemes.wrap] 使用 [wrap] 方法来构造一个widget,该widget将把子项包装在widget树的指定部分中存在的所有inherited themes中。
/// The [wrap] method is used by [captureAll] and [CapturedThemes.wrap] to
/// construct a widget that will wrap a child in all of the inherited themes
/// which are present in a specified part of the widget tree.
///
/// 在与其内置环境不同的上下文中显示的widget,就像新路由或overlay的内容一样,将能够看到它所构建的上下文的祖先inherited themes。
/// A widget that's shown in a different context from the one it's built in,
/// like the contents of a new route or an overlay, will be able to see the
/// ancestor inherited themes of the context it was built in.
///
/// {@tool dartpad}
/// 此示例演示了如何使用 InheritedTheme.capture() 来使用inherited themes来包装新route的内容,这些主题在构建route时存在,但在实际显示route时不存在。
/// This example demonstrates how `InheritedTheme.capture()` can be used
/// to wrap the contents of a new route with the inherited themes that
/// are present when the route was built - but are not present when route
/// is actually shown.
///
/// 如果相同的代码在没有 `InheritedTheme.capture() 的情况下运行,则新route的 Text widget将继承“omething must be wrong”反馈文本样式,而不是 MyApp 中定义的默认文本样式。
/// If the same code is run without `InheritedTheme.capture(), the
/// new route's Text widget will inherit the "something must be wrong"
/// fallback text style, rather than the default text style defined in MyApp.
///
/// ** See code in examples/api/lib/widgets/inherited_theme/inherited_theme.0.dart **
/// {@end-tool}
abstract class InheritedTheme extends InheritedWidget

通过InheritedTheme的注释可以知道,它和主题相关联,比如经常用的Theme.of(),它底层会通过dependOnInheritedWidgetOfExactType方法查询_InheritedTheme实例,而_InheritedTheme继承自InheritedTheme