flutter provider全解析

Flutter Provider 与 InheritedWidget 源码原理

源码版本:Flutter 3.38.9,provider 6.1.5+1。
阅读入口:

  • Flutter: /Users/macongcong/fvm/versions/stable/packages/flutter/lib/src/widgets/framework.dart
  • Provider: /Users/macongcong/.pub-cache/hosted/pub.dev/provider-6.1.5+1/lib/src/

面试官问:“InheritedWidget 和 Provider 的原理是什么?”

你可以这样说:

InheritedWidget 是 Flutter 官方提供的依赖向下传递机制,但真正负责依赖登记和通知的不是 Widget,而是它对应的 InheritedElement。

当子组件通过 context.dependOnInheritedWidgetOfExactType() 获取数据时,Flutter 会找到最近的 InheritedElement,并把当前子组件的 Element 登记为它的依 赖者。之后如果这个 InheritedWidget 更新,并且 updateShouldNotify 返回 true,对应的 InheritedElement 就会通知这些依赖者调用 didChangeDependencies,最终 触发 rebuild。

Provider 本质上就是对 InheritedWidget / InheritedElement 的封装。context.watch() 底层相当于 Provider.of(context),会建立依赖;context.read() 相当于 Provider.of(context, listen: false),只读取不订阅。

如果是 ChangeNotifierProvider,它还会监听 ChangeNotifier.notifyListeners(),然后调用 Provider 内部 Element 的通知逻辑,让依赖它的组件重建。select 则是 在通知时先比较选中的字段,字段没变就不 rebuild。

这个回答已经比较完整。

3 分钟展开版

如果面试官继续问细节,可以这样展开:

我会把它分成两层看:Flutter 原生的 InheritedWidget 机制,以及 Provider 在它上面的封装。

原生机制里,InheritedWidget 本身只是一个配置类,它会创建 InheritedElement。真正保存依赖关系的是 InheritedElement,里面维护了一个 dependents 集合,记录 哪些后代 Element 依赖了自己。

子组件读取 inherited 数据有两种方式。 第一种是 dependOnInheritedWidgetOfExactType,这个方法不仅查找最近的 inherited 节点,还会建立依赖关系,所以之后 inherited 数据变化时,当前组件会被通知 重建。 第二种是 getElementForInheritedWidgetOfExactType,它只是查找,不建立依赖,所以后续变化不会触发当前组件 rebuild。

这也是 Provider 里 watch 和 read 的核心区别。watch 会订阅,read 只读一次。

当 InheritedWidget 被父组件重新构建时,Flutter 会调用 updateShouldNotify(oldWidget) 判断是否需要通知依赖者。如果返回 true,InheritedElement 就遍历之前 登记过的 dependent elements,调用它们的 didChangeDependencies,而 didChangeDependencies 内部会标记当前 Element 需要 rebuild。

Provider 的实现本质上还是这套机制。Provider 内部有一个类似 _InheritedProviderScope 的 InheritedWidget,以及自定义的 _InheritedProviderScopeElement。它 在原生 inherited 机制上增加了几个能力:对象创建、懒加载、自动 dispose、监听 Listenable / ChangeNotifier、以及 select 精细化更新。

例如 ChangeNotifierProvider 会在 value 第一次被读取时创建对象,并给这个 ChangeNotifier 添加 listener。当业务代码调用 notifyListeners() 时,Provider 内 部 listener 会触发 markNeedsNotifyDependents,再通知依赖它的组件 rebuild。

context.select 更精细一些。它不是订阅整个对象,而是保存一个 selector,比如只关心 user.name。当 provider 通知时,它会重新执行 selector,只有前后结果不 一样,才让这个组件 rebuild。

更有条理的回答模板

你可以记这个顺序:

  1. 先定义 InheritedWidget 是 Flutter 的依赖向下传递机制,Provider 是它的工程化封装。

  2. 再说核心对象 真正干活的是 Element,不是 Widget。

  3. 再说读取差异 dependOn = 读取并订阅 getElement = 只读取不订阅

  4. 再说更新链路 updateShouldNotify / notifyListeners -> notify dependents -> didChangeDependencies -> markNeedsBuild -> rebuild

  5. 最后说 Provider 增强 read / watch / select create / dispose / lazy ChangeNotifierProvider 监听 notifyListeners

面试里比较加分的一句话

我理解 Provider 不是魔法,它只是把 Flutter 原生的 InheritedElement 依赖登记机制,和 ChangeNotifier 这类状态变化通知机制连接起来了。

这句话很关键,能体现你知道 Provider 的边界: Provider 负责“暴露和订阅”,真正的状态变化来源可能是 ChangeNotifier、Stream、Future、ValueNotifier,或者父组件 rebuild。


1. 先建立一个正确心智模型

Flutter 的依赖传播不是发生在 Widget 对象之间,而是发生在 Element 树上。

Widget: 配置,短生命周期,可频繁创建
Element: Widget 在树上的实例位置,长期存在,BuildContext 本质就是 Element
InheritedElement: 记录哪些 Element 依赖了自己,并负责通知它们

所以 InheritedWidget 的本质不是“全局变量”,而是:

在 Element 树中放一个可被后代按类型查找的节点;
后代读取时可以选择是否登记依赖;
当该节点的数据变化时,只通知登记过依赖的后代 Element。

Provider 不是绕开这套机制,而是在这套机制上做工程化封装:

InheritedWidget / InheritedElement
  -> Provider.of / context.watch / context.read / context.select
  -> create / dispose / lazy / ChangeNotifier 监听 / Selector 过滤更新

2. InheritedWidget 源码:Widget 层只负责声明

Flutter 源码里 InheritedWidget 非常小:

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({super.key, required super.child});

  
  InheritedElement createElement() => InheritedElement(this);

  
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

关键点:

  • InheritedWidget 继承 ProxyWidget,本身只有一个 child
  • createElement() 创建的是 InheritedElement
  • updateShouldNotify(oldWidget) 只负责判断“新旧配置变化后,要不要通知依赖者”。

这说明 InheritedWidget 本身不保存订阅者。真正的依赖登记和通知在 InheritedElement

3. 查找为什么快:Element 上缓存了可见的 InheritedElement

Element 内部有一个按类型索引的 _inheritedElements

InheritedElement._updateInheritance() 的核心逻辑:

final incomingWidgets =
    _parent?._inheritedElements ?? const PersistentHashMap.empty();
_inheritedElements = incomingWidgets.put(widget.runtimeType, this);

含义是:

当前 Element 继承父节点可见的所有 InheritedElement
如果当前节点自己就是 InheritedElement,就用自己的 runtimeType 覆盖同类型入口

所以后代查找 ThemeMediaQueryProvider<User> 这类 inherited 信息时,不需要每次从当前节点一路向上遍历父链,而是直接查当前 Element 持有的 _inheritedElements[type]

这也是为什么同类型 provider 嵌套时,子树读到的是“最近的那个”:

Provider<User>(value: outerUser,
  child: Provider<User>(value: innerUser,
    child: Child(),
  ),
)

Child 看到的 _inheritedElements[Provider<User>对应的InheritedWidget类型] 已经被内层覆盖。

4. read 与 watch 的源码差别:是否登记依赖

Flutter 原生有两个关键 API。

4.1 只查找,不订阅

InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  return _inheritedElements?[T];
}

这只是拿到最近的 inherited element,不把当前 Element 记为依赖者。

所以它对应 Provider 里的 read

context.read<T>()
  -> Provider.of<T>(context, listen: false)
  -> getElementForInheritedWidgetOfExactType
  -> 不订阅,后续变化不 rebuild

4.2 查找,并订阅

T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
  final InheritedElement? ancestor = _inheritedElements?[T];
  if (ancestor != null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  return null;
}

继续看 dependOnInheritedElement

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

这里有两个方向的记录:

当前 Element._dependencies 添加 ancestor
ancestor._dependents 添加当前 Element

所以 watch 的本质是:

读取值 + 把当前 Element 登记到 provider 对应 InheritedElement 的依赖者列表里

5. InheritedElement 如何保存依赖者

InheritedElement 里有:

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

默认登记依赖时:

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

默认通知时:

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

Element.didChangeDependencies() 又会:

markNeedsBuild();

所以完整链路是:

子组件调用 dependOnInheritedWidgetOfExactType
  -> 当前 Element 被加入 InheritedElement._dependents

InheritedWidget 重新 build
  -> InheritedElement.updated(oldWidget)
  -> updateShouldNotify(oldWidget) 返回 true
  -> notifyClients(oldWidget)
  -> 遍历 _dependents
  -> dependent.didChangeDependencies()
  -> dependent.markNeedsBuild()
  -> 下一轮 build

这解释了两个现象:

  • 调了 dependOn... / watch 的组件会被通知。
  • 只调 getElement... / read 的组件不会被通知。

6. Provider.of 的源码:Provider 如何接入 InheritedWidget

Provider 的核心读取入口是:

static T of<T>(BuildContext context, {bool listen = true}) {
  final inheritedElement = _inheritedElementOf<T>(context);

  if (listen) {
    context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope<T?>>();
  }

  final value = inheritedElement?.value;
  return value as T;
}

它分三步:

1. _inheritedElementOf<T>(context)
   找到 _InheritedProviderScope<T?> 对应的 Element

2. listen == true 时
   调 dependOnInheritedWidgetOfExactType,建立依赖

3. 读取 inheritedElement.value
   触发 Provider 的懒创建、监听启动,然后返回值

Provider 的扩展方法只是语法糖:

T read<T>() => Provider.of<T>(this, listen: false);
T watch<T>() => Provider.of<T>(this);

所以:

read: 找 provider,取 value,不登记依赖
watch: 找 provider,取 value,登记依赖

7. Provider 真正放进树里的不是 Provider Widget,而是 _InheritedProviderScope

InheritedProvider<T>buildWithChild 会返回:

_InheritedProviderScope<T?>(
  owner: this,
  child: child,
)

_InheritedProviderScope<T> 继承 InheritedWidget

class _InheritedProviderScope<T> extends InheritedWidget {
  final InheritedProvider<T> owner;

  
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }

  
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}

注意这里 updateShouldNotify 永远返回 false

这看起来反直觉,但很关键:Provider 没有直接依赖 Flutter 默认的 InheritedWidget.updateShouldNotify。它把通知逻辑放到了自定义 Element:_InheritedProviderScopeElement

原因是 Provider 要支持更多能力:

  • select 只监听对象的一部分字段。
  • ChangeNotifier 通过 notifyListeners() 主动触发更新。
  • create 懒加载。
  • dispose 生命周期管理。
  • update / ProxyProvider 依赖其他 provider 更新。

8. Provider 的 Element:_InheritedProviderScopeElement

Provider 自己的 Element 继承自 InheritedElement

class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T> {
  bool _shouldNotifyDependents = false;
  late final _DelegateState<T, _Delegate<T>> _delegateState =
      widget.owner._delegate.createState()..element = this;
}

它实现了两个重要能力。

8.1 暴露 value

T get value => _delegateState.value;

Provider 的值不直接放在 widget 上,而是交给 _DelegateState 管。

8.2 主动通知依赖者

void markNeedsNotifyDependents() {
  markNeedsBuild();
  _shouldNotifyDependents = true;
}

然后在 build() 里:

if (_shouldNotifyDependents) {
  _shouldNotifyDependents = false;
  notifyClients(widget);
}

这意味着 Provider 可以绕过 updateShouldNotify,由外部事件主动触发通知,例如 ChangeNotifier.notifyListeners()

9. Provider 的懒创建:create 不是立刻执行

Provider 的 _CreateInheritedProviderState.value 里有:

if (!_didInitValue) {
  _didInitValue = true;
  _value = delegate.create!(element!);
}

_removeListener ??= delegate.startListening?.call(element!, _value as T);
return _value as T;

含义:

Provider(create: ...)
  -> 插入树时不一定立刻 create
  -> 第一次有人读 value 时才 create
  -> 第一次读 value 后才 startListening

这就是 lazy 的来源。

如果设置 lazy: false,Provider 的 Element 在 build 里会强制读一次:

if (widget.owner._lazy == false) {
  value;
}

于是 create 会提前执行。

10. ChangeNotifierProvider 的源码链路

ChangeNotifierProvider 继承 ListenableProvider

class ChangeNotifierProvider<T extends ChangeNotifier?>
    extends ListenableProvider<T> {
  ChangeNotifierProvider({
    required Create<T> create,
  }) : super(
          create: create,
          dispose: _dispose,
        );
}

ListenableProvider 传入了 startListening

static VoidCallback _startListening(
  InheritedContext<Listenable?> e,
  Listenable? value,
) {
  value?.addListener(e.markNeedsNotifyDependents);
  return () => value?.removeListener(e.markNeedsNotifyDependents);
}

完整更新链路:

context.watch<CounterModel>()
  -> Provider.of(listen: true)
  -> 当前 Element 登记为 _InheritedProviderScopeElement 的依赖者

CounterModel.increment()
  -> notifyListeners()
  -> ListenableProvider 注册的 listener 被调用
  -> e.markNeedsNotifyDependents()
  -> Provider Element markNeedsBuild
  -> build 时 notifyClients()
  -> 对每个 dependent 执行 notifyDependent()
  -> dependent.didChangeDependencies()
  -> markNeedsBuild()
  -> UI rebuild

所以 ChangeNotifierProvider 的本质是:

用 InheritedElement 管订阅者;
用 ChangeNotifier/Listenable 管变化事件;
把 notifyListeners 转换成 InheritedElement.notifyClients。

11. select 的源码:把 aspect 用起来

Flutter 原生的 dependOnInheritedWidgetOfExactType 有个参数:

Object? aspect

普通 InheritedWidget 默认不使用它;InheritedModel 和 Provider 的 select 会用。

Provider 的 context.select 核心逻辑:

final selected = selector(value);

dependOnInheritedElement(
  inheritedElement,
  aspect: (T? newValue) {
    return !DeepCollectionEquality().equals(
      selector(newValue),
      selected,
    );
  },
);

这里的 aspect 不是普通数据,而是一个函数:

给我新 value,我重新执行 selector;
如果 selector 结果和旧 selected 不一样,返回 true;
否则返回 false。

Provider 的 Element 重写了 updateDependencies

if (aspect is _SelectorAspect<T>) {
  selectorDependency.selectors.add(aspect);
  setDependencies(dependent, selectorDependency);
} else {
  setDependencies(dependent, const Object());
}

也重写了 notifyDependent

if (dependencies is _Dependency<T>) {
  for (final updateShouldNotify in dependencies.selectors) {
    shouldNotify = updateShouldNotify(value);
    if (shouldNotify) break;
  }
} else {
  shouldNotify = true;
}

if (shouldNotify) {
  dependent.didChangeDependencies();
}

所以:

watch<T>()
  -> 订阅整个 T
  -> T 通知时一定 rebuild

select<T, R>()
  -> 订阅 T 的某个投影 R
  -> T 通知时重新算 selector
  -> R 变了才 rebuild

这也是为什么 select 可以显著降低重建范围。

12. Consumer 和 Selector 的源码意义

Consumer<T> 没有什么魔法:

Widget buildWithChild(BuildContext context, Widget? child) {
  return builder(
    context,
    Provider.of<T>(context),
    child,
  );
}

它只是创建一个更小的 Widget 边界,并在这个边界里 watch

Column(
  children: [
    ExpensiveHeader(),
    Consumer<User>(
      builder: (_, user, __) => Text(user.name),
    ),
  ],
)

User 变化时,主要重建 Consumer 这一小块,而不是把读取逻辑放在外层大组件里。

Selector 则更进一步:缓存上一次 selector 的结果,只有结果变化时才刷新 builder。

13. Provider.value 与 create 的生命周期差别

Provider 有两种典型构造方式:

Provider(create: (_) => UserRepository())
Provider.value(value: existingRepository)

源码上:

  • create 使用 _CreateInheritedProvider,支持 createdisposeupdate
  • value 使用 _ValueInheritedProvider,只暴露现有对象,不负责创建它。

_CreateInheritedProviderState.dispose()

_removeListener?.call();
if (_didInitValue) {
  delegate.dispose?.call(element!, _value as T);
}

所以:

create 创建的对象,Provider 是 owner,Provider 卸载时负责 dispose。
value 传入的对象,外部是 owner,Provider 一般只负责取消自己加上的监听。

这就是官方一直强调的规则:

新建对象用 create。
复用已有对象用 value。
不要用 value 创建新对象。
不要用 create 传入会变化的外部现有对象。

14. 为什么 initState 里不能 watch

Provider 的源码里有保护:

if (_debugInheritLocked) {
  throw FlutterError(...);
}

错误信息会提示:

不要在 initState 或 provider create 回调中 listen: true。

原因不是“拿不到值”,而是生命周期不匹配:

watch 会建立依赖;
依赖变化后需要有一个能处理更新的生命周期;
initState 只执行一次,后续不会再执行;
所以在 initState 里订阅没有意义。

正确选择:

// 只读一次
final repo = context.read<Repo>();

// 需要响应依赖变化

void didChangeDependencies() {
  super.didChangeDependencies();
  final theme = context.watch<AppTheme>();
}

// 更常见:直接在 build 里 watch/select
Widget build(BuildContext context) {
  final userName = context.select((User u) => u.name);
  return Text(userName);
}

15. 一条完整源码路径复盘

以这段代码为例:

ChangeNotifierProvider(
  create: (_) => Counter(),
  child: CounterText(),
)

class CounterText extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final count = context.select((Counter c) => c.count);
    return Text('$count');
  }
}

首次 build:

ChangeNotifierProvider
  -> InheritedProvider
  -> _InheritedProviderScope
  -> _InheritedProviderScopeElement

CounterText 读取:

context.select
  -> Provider._inheritedElementOf<Counter>
  -> getElementForInheritedWidgetOfExactType<_InheritedProviderScope<Counter?>>()
  -> inheritedElement.value
  -> 第一次触发 create Counter()
  -> startListening: counter.addListener(markNeedsNotifyDependents)
  -> dependOnInheritedElement(..., aspect: selector comparison function)

Counter.increment()

count++
notifyListeners()
  -> markNeedsNotifyDependents()
  -> Provider Element markNeedsBuild
  -> build()
  -> notifyClients()
  -> notifyDependent()
  -> 执行 select 保存的 selector
  -> count 变了,didChangeDependencies()
  -> markNeedsBuild()
  -> CounterText rebuild

如果 Counter 通知了,但 count 没变,只是 name 变了:

selector(c) => c.count 的结果不变
  -> notifyDependent 不调用 didChangeDependencies
  -> CounterText 不 rebuild

16. 最容易混淆的点

16.1 Provider 不是状态变化源

普通 Provider<T> 不会自动知道 T 内部字段变了。

Provider<User>(create: (_) => User())

如果你改了:

user.name = 'new name';

UI 不会自动更新,除非:

  • 上层 rebuild 并换了 Provider 的 value;
  • 使用 ChangeNotifierProvider 并调用 notifyListeners()
  • 使用 ValueListenableProviderStreamProviderFutureProvider 等有事件源的 provider。

16.2 watch 不是“监听对象字段”

watch<T>() 监听的是 provider 的通知,不是自动监听 T 的所有字段。

对象字段变了 + 没有通知事件 = UI 不知道
对象发通知了 + watch 了整个对象 = UI rebuild
对象发通知了 + select 字段没变 = UI 不 rebuild

16.3 BuildContext 是 Element

context.watch 能登记依赖,是因为 context 实际是当前 Widget 对应的 Element。

不是 Widget 自己在订阅,而是 Element 在订阅。

16.4 updateShouldNotify 不是 Provider 的唯一通知入口

原生 InheritedWidget 主要靠 updateShouldNotify

Provider 的 _InheritedProviderScope.updateShouldNotify 永远 false,但它可以通过:

markNeedsNotifyDependents()
  -> _shouldNotifyDependents = true
  -> build 时 notifyClients()

主动通知。

17. 复习时记住这几句话

InheritedWidget 是声明,InheritedElement 才是依赖登记和通知中心。

dependOnInheritedWidgetOfExactType = 查找 + 登记依赖。
getElementForInheritedWidgetOfExactType = 只查找,不登记依赖。

Provider.of(listen: true) / watch = dependOn。
Provider.of(listen: false) / read = getElement。

ChangeNotifierProvider = InheritedElement 依赖系统 + ChangeNotifier 事件系统。

select = 把 selector 比较函数作为 aspect 存到依赖关系里,通知时按需 rebuild。

create 是懒执行的;第一次读 value 才创建,lazy: false 会提前创建。

create 创建的对象由 Provider dispose;value 传入的对象由外部负责生命周期。

18. 源码索引

  • InheritedWidget.createElementupdateShouldNotifyframework.dart:1853
  • Element.dependOnInheritedWidgetOfExactTypeframework.dart:5084
  • Element.getElementForInheritedWidgetOfExactTypeframework.dart:5100
  • Element.dependOnInheritedElementframework.dart:5077
  • Element.didChangeDependencies -> markNeedsBuildframework.dart:5193
  • InheritedElement._dependentsframework.dart:6259
  • InheritedElement.updateDependenciesframework.dart:6354
  • InheritedElement.updatedframework.dart:6399
  • InheritedElement.notifyClientsframework.dart:6417
  • Provider.ofprovider.dart:306
  • context.readprovider.dart:682
  • context.watchprovider.dart:726
  • InheritedProvider 构造与 delegate:inherited_provider.dart:54
  • context.selectinherited_provider.dart:250
  • _InheritedProviderScope.updateShouldNotify == falseinherited_provider.dart:340
  • _InheritedProviderScopeElement.updateDependenciesinherited_provider.dart:442
  • _InheritedProviderScopeElement.notifyDependentinherited_provider.dart:474
  • _InheritedProviderScopeElement.markNeedsNotifyDependentsinherited_provider.dart:585
  • _CreateInheritedProviderState.valueinherited_provider.dart:730
  • _CreateInheritedProviderState.disposeinherited_provider.dart:805
  • _ValueInheritedProviderState.willUpdateDelegateinherited_provider.dart:945
  • ListenableProvider._startListeninglistenable_provider.dart
  • ChangeNotifierProviderchange_notifier_provider.dart