这两天学习了一下 Flutter 中的 InheritedWidget 的使用方法, 顺便查看一下相关源码了解了其底层实现机制. 特地记录一下.
Prerequirements
由于本文主要是从源码的角度分析 InheritedWidget 的工作原理, 所以对阅读本文的小伙伴的 Flutter 知识有一定的要求. 主要有以下几点, 如果其中某部分你还不太清楚, 请先阅读相关链接:
了解 Flutter 的基本用法.
下面开始正文.
InheritedWidget 的使用方法
先看一个 InheritedWidget 最简单的使用示例:
- import 'package:flutter/material.dart';
- void main() => runApp(new MyApp());
- class MyWelcomeInfo extends InheritedWidget {
- MyWelcomeInfo({Key key, this.welcomeInfo, Widget child})
- : super(key: key, child: child);
- final String welcomeInfo;
- @override
- bool updateShouldNotify(InheritedWidget oldWidget) {
- return oldWidget.welcomeInfo != welcomeInfo;
- }
- }
- class MyNestedChild extends StatelessWidget {
- @override
- build(BuildContext context) {
- final MyWelcomeInfo widget =
- context.inheritFromWidgetOfExactType(MyWelcomeInfo);
- return Text(widget.welcomeInfo);
- }
- }
- class MyApp extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return new MaterialApp(
- title: 'Flutter InheritWidget',
- home: MyWelcomeInfo(
- welcomeInfo: 'hello flutter',
- child: Center(
- child: MyNestedChild(),
- )),
- );
- }
- }
复制代码
可以看出我们使用 InheritedWidget 时涉及到的工作量主要有 2 部分:
创建一个继承自 InheritedWidget 的类, 并将其插入 Widget 树
通过 BuildContext 对象提供的
inheritFromWidgetOfExactType
方法查找 Widget 树中最近的一个特定类型的 InheritedWidget 类的实例
这里还暗含了一个逻辑, 那就是当我们通过
inheritFromWidgetOfExactType
查找特定类型 InheritedWidget 的时候, 这个 InheritedWidget 的信息是由父元素层层向子元素传递下来的呢? 还是
inheritFromWidgetOfExactType
方法自己层层向上查找的呢?
接下来让我们从源码的角度分别看看 Flutter 框架对以上几部分的实现.
实现原理分析
InheritedWidget 定义
首先看一下 InheritedWidget 的定义:
- abstract class InheritedWidget extends ProxyWidget {
- const InheritedWidget({ Key key, Widget child })
- : super(key: key, child: child);
- @override
- InheritedElement createElement() => new InheritedElement(this);
- @protected
- bool updateShouldNotify(covariant InheritedWidget oldWidget);
- }
复制代码
它是一个继承自 ProxyWidget 的抽象类. 内部没什么逻辑, 除了实现了一个 createElement 方法之外, 还定义了一个
updateShouldNotify()
接口. 每次当 InheritedElement 的实例更新时会执行该方法并传入更新之前对应的 Widget 对象, 如果该方法返回 true 那么依赖该 Widget 的 (在 build 阶段通过
inheritFromWidgetOfExactType
方法查找过该 Widget 的子 widget) 实例会被通知进行更新; 如果返回 false 则不会通知依赖项更新. 这个机制和 React 框架中的
shouldComponentUpdate
机制很像.
InheritedWidget 相关信息的传递机制
每个 Element 实例上都有一个 _inheritedWidgets 属性. 该属性的类型为:
HashMap<Type, InheritedElement>
复制代码
其中保存了祖先节点中出现的 InheritedWidget 与其对应 element 的映射关系. 在 element 的 mount 阶段 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L2754 和 active 阶段 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L3019 , 会执行
_updateInheritance()
方法更新这个映射关系.
对于普通 Element 实例,
_updateInheritance()
只是单纯把父 element 的 _inheritedWidgets 属性保存在自身 _inheritedWidgets 里 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L3251-L3254 . 从而实现映射关系的层层向下传递.
- void _updateInheritance() {
- assert(_active);
- _inheritedWidgets = _parent?._inheritedWidgets;
- }
复制代码
由 InheritedWidget 创建的 InheritedElement 重写了该方法 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L4039-L4047 :
- void _updateInheritance() {
- assert(_active);
- final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
- if (incomingWidgets != null)
- _inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets);
- else
- _inheritedWidgets = new HashMap<Type, InheritedElement>();
- _inheritedWidgets[widget.runtimeType] = this;
- }
复制代码
可以看出 InheritedElement 实例会把自身的信息添加到 _inheritedWidgets 属性中, 这样其子孙 element 就可以通过前面提到的 _inheritedWidgets 的传递机制获取到此 InheritedElement 的引用.
InheritedWidget 的更新通知机制
首先让我们回答一个小问题, 前文提到 _inheritedWidgets 属性存在于 Element 实例上, 而我们代码中调用的
inheritFromWidgetOfExactType
方法则存在于 BuildContext 实例之上. 那么 BuildContext 是如何获取 Element 实例上的信息的呢? 答案是不需要获取. 因为每一个 Element 实例也都是一个 BuildContext 实例. 这一点可以从 Element 的定义 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L2496 中得到:
- abstract class Element extends DiagnosticableTree implements BuildContext {
- }
复制代码
而每次 Element 实例执行 Widget 实例的 build 方法时传入的 context 就是该 Element 实例自身, 以 StatelessElement https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L3700-L3717 为例:
- class StatelessElement extends ComponentElement {
- ...
- @override
- Widget build() => widget.build(this);
- ...
- }
复制代码
大家也可以在 IDE 中通过断点验证这一点.
既然可以拿到 InheritedWidget 的信息了, 那接下让我们通过源码看看更新通知机制的具体实现.
首先看一下
inheritFromWidgetOfExactType
的实现 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L3230-L3242 :
- InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
- assert(_debugCheckStateIsActiveForAncestorLookup());
- final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
- if (ancestor != null) {
- assert(ancestor is InheritedElement);
- _dependencies ??= new HashSet<InheritedElement>();
- _dependencies.add(ancestor);
- ancestor._dependents.add(this);
- return ancestor.widget;
- }
- _hadUnsatisfiedDependencies = true;
- return null;
- }
复制代码
首先在 _inheritedWidget 映射中查找是否有特定类型 InheritedWidget 的实例. 如果有则将该实例添加到自身的依赖列表中, 同时将自身添加到对应的依赖项列表中. 这样该 InheritedWidget 在更新后就可以通过其 _dependents 属性知道需要通知哪些依赖了它的 widget.
每当 InheritedElement 实例更新 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L3914-L3923 时, 会执行实例上的 notifyClients 方法 https://github.com/flutter/flutter/blob/v0.5.7/packages/flutter/lib/src/widgets/framework.dart#L4070-L4086 通知依赖了它的子 element 同步更新. notifyClients 实现如下:
- void notifyClients(InheritedWidget oldWidget) {
- if (!widget.updateShouldNotify(oldWidget))
- return;
- assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
- for (Element dependent in _dependents) {
- 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));
- dependent.didChangeDependencies();
- }
- }
复制代码
首先执行相应 InheritedWidget 上的 updateShouldNotify 方法判断是否需要通知, 如果该方法返回 true 则遍历 _dependents 列表中的 element 并执行他们的
didChangeDependencies()
方法. 这样 InheritedWidget 中的更新就通知到依赖它的子 widget 中了.
写在最后
本文简单的从源码的角度讨论了 InheritedWidget 的实现原理. 由于 Flutter 框架本身也是使用 Dart 语言开发的, 在熟悉了 Dart 语言后其实阅读 Flutter 源码并没有想象中的那么难, 大家也可以根据自己感兴趣的点去有选择的阅读部分源码.
由于作者也是 Flutter 的初学者, 文中可能存在描述不准确甚至是错误的地方. 欢迎大家一起讨论.
从 0 到 1: 我的 Flutter 技术实践 | 掘金技术征文, 征文活动正在进行中
来源: https://juejin.im/post/5b4f1f686fb9a04fcd584e6e