Riverpod源码分析3:Provider的观察、刷新与销毁

这一节我们主要关注ProviderElement,也是Riverpod的核心部分。我们通过watch观察、read读取、listen注册回调、invalidate刷新都要通过它。

老样子,我会以最基础的ProviderElement为例讲解。

1.RefWidgetRef

1.1 Ref: ProviderElementBase implements Ref

Provider_createFn(Ref ref)方法中接收的参数就是本provider对应的providerElement实例。ProviderElementBase实现了Ref接口 ,通过它我们可以读取/监听某个Provider的值,刷新、无效化某个provider等等。

以下是Ref的主要方法,我会把它们的作用以注释的方式写出

dart 复制代码
// Ref就是ProviderElementBase
abstract class Ref<
    @Deprecated('Will be removed in 3.0') State extends Object?> {
    
  // 本ProviderElement所属的container
  ProviderContainer get container;
  
  // 立刻重新计算某个Provider的值,等价于invalidate + read
  T refresh<T>(Refreshable<T> provider);
  
  // 令某个或一系列provider失效
  void invalidate(ProviderOrFamily provider);
  
  // 通知监听本provider的其他provider,我刷新了
  void notifyListeners();
  
  // 无效化自己
  void invalidateSelf();
  
  // ---- 生命周期相关

  // 有新监听者注册
  void onAddListener(void Function() cb);
  
  // 有监听者移除
  void onRemoveListener(void Function() cb);
  
  // 从0到1,有一个监听者开始监听本provider时
  void onResume(void Function() cb);
  
  // 最后一个监听者被移除时触发(没有被废弃)
  void onCancel(void Function() cb);
  
  // 本Element被废弃
  void onDispose(void Function() cb);
  
  // ---- 读取其他provider

  // 读取某个provider的值
  T read<T>(ProviderListenable<T> provider);
  
  // 判断某个ProviderElement是否被初始化过
  bool exists(ProviderBase<Object?> provider);
  
  // 观察某个provider,对方变化时自己要重新计算
  T watch<T>(ProviderListenable<T> provider);
  
  // 保活
  KeepAliveLink keepAlive();
  
  // 监听某个provider,对方变化时执行回调listener
  ProviderSubscription<T> listen<T>(
    ProviderListenable<T> provider,
    void Function(T? previous, T next) listener, {
    void Function(Object error, StackTrace stackTrace)? onError,
    // 是否立刻执行一次回调
    bool fireImmediately,
  });
}

1.2 WidgetRef: ConsumerStatefulElement extends StatefulElement implements WidgetRef

StatefulElement就是BuildContext,所以WidgetRef实际上就是BuildContext

WidgetRef方法和Ref大同小异,区别在于Ref.watch是刷新provider,而WidgetRef.watch是通过markNeedsBuild刷新UI。

2. 初始化ProviderElementBase

通常我们会用RefWidgetRef从另一个provider中拿到数据。这里我们以WidgetRef.read(provider)开始过一遍流程。

2.1 找到对应的_StateReader

dart 复制代码
// `WidgetRef.read`
@override
T read<T>(ProviderListenable<T> provider) {
  return ProviderScope.containerOf(this, listen: false).read(provider);
}

// `ProviderContainer.read`
Result read<Result>(ProviderListenable<Result> provider) {
  return provider.read(this);
}

// `ProviderBase.read`, `Node`是`ProviderContainer`
StateT read(Node node) {
  // `Node`是`ProviderContainer`。这里会初始化ProviderElement
  final element = node.readProviderElement(this);
  // 刷新Provider的值
  element.flush();
  element.mayNeedDispose();
  return element.requireState;
}

ProviderContainer#readProviderElement在第二篇文章也说过,就是通过_StateReader#getElement懒加载ProviderElement。我们再贴一遍_StateReader中相关的代码:

dart 复制代码
// 使用已有的Element或初始化
ProviderElementBase getElement() => _element ??= _create();

// 创建新的ProviderElement
ProviderElementBase _create() {
  final element = override.createElement()
    .._provider = override
    .._origin = origin
    .._container = container
    ..mount();
  element.getState()
  return element;
}

从这里开始正式进入ProviderElementBase内部。

2.2 初始化并赋值:mount() & buildState()

ProviderElementBase被初始化后,_StateReader随即会调用其mount方法。

dart 复制代码
void mount() {
  _mounted = true;
  buildState();
}
dart 复制代码
// ProviderElementBase#buildState()
void buildState() {
  final previousDidChangeDependency = _didChangeDependency;
  _didChangeDependency = false;
  _didBuild = false;
  try {
    _mounted = true;
    // 执行Provider的_createFn()
    create(didChangeDependency: previousDidChangeDependency);
  } catch (err, stack) {
    _state = Result.error(err, stack);
  } finally {
    _didBuild = true;
  }
}

对于create()方法,每种Provider对应的Element实现均不相同。我们以ProviderElement为例:执行provider_createFn方法,把得到的值通过setState()设置给自己,同时触发回调。

dart 复制代码
@override
void create({required bool didChangeDependency}) {
  final provider = this.provider as InternalProvider<State>;
  // 会触发回调,见3.3
  setState(provider._create(this));
}

mount()结束后,这个ProviderElementBase就算是正式初始化完成了。

2.3 总结

  1. 创建ProviderElement的唯一时机就是通过ProviderContainer去使用它时,即ProviderContainer#readProviderElement方法
  2. ProviderElementBase内部的初始化流程大概是这样的:mount() -> buildState() -> create()create()又会执行provider_createFn
  3. 如果某个provider A需要依赖其他provider B,那么A必然要在其_createFn(Ref)方法中通过ref.watch/read去监听B,又会触发ProviderContainer#readProviderElement...这样,provider就被递归的初始化。

3. listen/watch:监听ProviderElement

ProviderElementBase中使用列表来保存自己监听和监听自己的订阅者

dart 复制代码
// 自己监听(listen)其他ProviderElement所返回的Subscription,上级
List<ProviderSubscription>? _subscriptions;

// 其他Provider监听自己的Subscription,下级
List<ProviderSubscription>? _dependents;

// 观察(watch)自己的ProviderElement
final _providerDependents = <ProviderElementBase<Object?>>[];

3.1 ref.listen:注册回调

ref.listen的工作原理是:把一个回调listener添加到想要监听的ProviderElementBase的回调列表_dependents中。当上级ProviderElementBase的状态(_state)发生改变时就会触发这个回调。

WidgetRefwatch也是通过listen实现的,只是注册了一个markNeedsLayout()回调。

dart 复制代码
//  `ProviderBase#addListener`
@override
ProviderSubscription<StateT> addListener(
  Node node,
  void Function(StateT? previous, StateT next) listener, {
  required void Function(Object error, StackTrace stackTrace)? onError,
  required void Function()? onDependencyMayHaveChanged,
  required bool fireImmediately,
}) {
  // node是ProviderContainer
  final element = node.readProviderElement(this);
  // 刷新element以获取最新值
  element.flush();
  // 立刻执行一次回调
  if (fireImmediately) {
    handleFireImmediately(
      element.getState()!,
      listener: listener,
      onError: onError,
    );
  }
  
  // 触发onAddListener/onResume回调。 todo 为什么从外部触发?
  element._onListen();
  
  // 返回一个ProviderSubscription。node是ProviderContainer,
  // listenedElement是监听的ProviderElement
  // 在构造方法中把自己添加到被监听ProviderElement的所属
  return _ProviderStateSubscription<StateT>(
    node,
    listenedElement: element,
    listener: (prev, next) => listener(prev as StateT?, next as StateT),
    onError: onError,
  );
}

只要通过RefWidgetRef监听一个provider,都会返回一个ProviderSubscription。通过它我们可以随时获取provider的值或取消监听。

dart 复制代码
// 在构造方法中把自己添加到被监听ProviderElement的所属
class _ProviderStateSubscription<StateT> extends ProviderSubscription<StateT> {
  _ProviderStateSubscription(
    super.source, {
    required this.listenedElement,
    required this.listener,
    required this.onError,
  }) {
    final dependents = listenedElement._dependents ??= [];
    dependents.add(this);
  }

  final void Function(Object? prev, Object? state) listener;
  final ProviderElementBase<StateT> listenedElement;
  final OnError onError;

  // 获取监听的ProviderElement的值
  @override
  StateT read() => listenedElement.readSelf();
  
  // 取消订阅
  @override
  void close() {
    if (!closed) {
      listenedElement._dependents?.remove(this);
      listenedElement._onRemoveListener();
    }
    super.close();
  }
}

listen方法结束后,会返回一个ProviderSubscription对象。可以通过它读取监听的Provider的最新值,也可以用它取消回调。同时它还保存了我们传入的listener回调,保存在监听的ProviderElement中。

3.2 ref.watch:观察另一个ProviderElement

watch方法会将观察的provider添加到自己的依赖中,并把自己添加到需要监听的ProviderElement_providerDependents列表中,形成双向绑定的关系。

dart 复制代码
@override
T watch<T>(ProviderListenable<T> listenable) {
  final element = _container.readProviderElement(listenable);
  // _dependencies是自己的上级
  _dependencies.putIfAbsent(element, () {
    final previousSub = _previousDependencies?.remove(element);
    if (previousSub != null) {
      return previousSub;
    }

    element
      // 触发一次回调
      .._onListen()
      // 把自己添加到上级的_providerDependents列表中
      .._providerDependents.add(this);

    return Object();
  });
  // 返回对方当前的值
  return element.readSelf();
}

3.3 _state改变时,如何通知监听者

  1. 会依次调用_dependents中保存的回调,触发listen
  2. ProviderElement会通过让下级(即观察watch自己的Provider)失效(invalidateSelf)的方式触发它们的重新计算。

3.3.1 setState:改变状态

想改变ProviderElement的状态(或者说它的值)就必须通过setState(Result<T> result)方法。Result是一个包装类,内容可能是正常值 value异常 error,stackTrace。修改后会通过_notifyListeners触发回调、通知下级。

dart 复制代码
void setState(StateT newState) {
  final previousResult = getState();
  // 修改_state
  final result = _state = ResultData(newState);
  _notifyListeners(result, previousResult);
}

3.3.2 _notifyListeners:触发回调,响应watch/listen

dart 复制代码
  void _notifyListeners(
    Result<StateT> newState,
    Result<StateT>? previousStateResult, {
    bool checkUpdateShouldNotify = true,
  }) {

    final previousState = previousStateResult?.stateOrNull;

    // 如果实际值没有变化则不通知,实现select选择性刷新的效果
    if (checkUpdateShouldNotify &&
        previousStateResult != null &&
        previousStateResult.hasState &&
        newState.hasState &&
        !updateShouldNotify(
          previousState as StateT,
          newState.requireState,
        )) {
      return;
    }

    final listeners = _dependents?.toList(growable: false);

    // listeners中存储了ProviderSubscription,其中保存了listener回调
    if (listeners != null) {
      for (var i = 0; i < listeners.length; i++) {
        final listener = listeners[i];
        if (listener is _ProviderStateSubscription) {
          listener.listener(previousState,newState.state),
        }
      }
    }

    // _providerDependents中存储着下级ProviderElement
    for (var i = 0; i < _providerDependents.length; i++) {
      // 标记它们的依赖发生变化,实际上就是invalidateSelf()
      _providerDependents[i]._markDependencyChanged();
    }
  }
dart 复制代码
void _markDependencyChanged() {
  _didChangeDependency = true;
  if (_mustRecomputeState) return;
  invalidateSelf();
}

4. 无效化invalidate

注意:无效化(invalidate)并不等于废弃(dispose)。如果某个Provider不是autoDisposed,那么在它所属的作用域销毁之前它不会被销毁。

  • 无效化:ProviderElement中的值_state需要重新计算,执行ref.onDispose()注册的回调
  • 废弃:销毁这个ProviderElement

4.1 ref.invalidate(ProviderOfFamily provider)

我们直接快进到ProviderContainer的相关方法。invalidate接收一个ProviderOrFamily参数。

dart 复制代码
void invalidate(ProviderOrFamily provider) {
  if (provider is ProviderBase) {
    final reader = _getOrNull(provider);
    reader?._element?.invalidateSelf();
  } else {
    // 会无效化通过ProviderFamily创造的所有provider
    provider as Family;

    final familyContainer =
        _overrideForFamily[provider]?.container ?? _root ?? this;

    for (final stateReader in familyContainer._stateReaders.values) {
      if (stateReader.origin.from != provider) continue;
      stateReader._element?.invalidateSelf();
    }
  }
}

4.2 element.invalidateSelf():使自己失效

4.2.1 标记自己无效

dart 复制代码
// 标记自己需要重新计算
@override
void invalidateSelf() {
  if (_mustRecomputeState) return;
  _mustRecomputeState = true;
  // 清理回调及各种注册
  runOnDispose();
  // 自动销毁的Provider会重写这个方法,否则为空
  mayNeedDispose();
  // 本帧结束后,如果hasListener不为false,执行flush方法
  // 当自己被listen或watch时,hasListener为true
  _container.scheduler.scheduleProviderRefresh(this);
  // 通知孩子依赖可能被更改,修改它们的标记位
  visitChildren(
    elementVisitor: (element) => element._markDependencyMayHaveChanged(),
    notifierVisitor: (notifier) => notifier.notifyDependencyMayHaveChanged(),
  );
}

4.2.2 runOnDispose: 清理、执行一部分回调

注意_providerDependents(下级)_dependencies(listen回调)列表没有被清空。hasListener可能返回true。

为什么要清理回调和生命周期相关方法?是因为所有回调都是在Provider的_createFn中注册的,而无效化需要重新执行_createFn以获取到最新的值。所以提前清理、再次注册可以防止注册多次回调。

dart 复制代码
void runOnDispose() {
  if (!_mounted) return;
  _mounted = false;
  
  final subscriptions = _subscriptions;
  if (subscriptions != null) {
    while (subscriptions.isNotEmpty) {
      final sub = subscriptions.first;
      // close会把自己从_subscriptions列表中移除,所以不会无限循环
      sub.close();
    }
  }
  
  // 执行onDispose
  _onDisposeListeners?.forEach(runGuarded);
  // 清理生命周期方法,再次create时会重新注册
  _onDisposeListeners = null;
  _onCancelListeners = null;
  _onResumeListeners = null;
  _onAddListeners = null;
  _onRemoveListeners = null;
  _onChangeSelfListeners = null;
  _onErrorSelfListeners = null;
  _didCancelOnce = false;
}

到这里,本帧就结束了,但是ProviderElement的状态依然没有被刷新。默认情况下,ProviderScheduler会在下一帧统一执行ProviderElementBaseflush方法。

4.2.3 刷新状态_flush、建立依赖

下一帧开始。buildState会调用provider_createFn,重新注册listener、建立依赖关系。

dart 复制代码
@internal
void flush() {
  // 这句有什么用?
  _maybeRebuildDependencies();
  if (_mustRecomputeState) {
    _mustRecomputeState = false;
    _performBuild();
  }
}


void _maybeRebuildDependencies() {
  if (!_dependencyMayHaveChanged) return;
  _dependencyMayHaveChanged = false;
  visitAncestors(
    (element) => element.flush(),
  );
}

注意这里:provider自身需要重建,为什么还需要刷新自己的上级? 其实这里和刷新机制的性能优化有关,我也是想了好一会才想到的。看到这的同学可以自己想一想,答案在第5段。

buildState(见2.2)会重新执行_createFn,注册回调、建立依赖关系,返回最新的值

dart 复制代码
void _performBuild() {
  final previousDependencies = _previousDependencies = _dependencies;
  _dependencies = HashMap();

  final previousStateResult = _state;

  buildState();
  
  // identical比较两个对象是否为同一个对象,这里一定会返回false
  if (!identical(_state, previousStateResult)) {
    // 把自己添加到待刷新列表
    _notifyListeners(_state!, previousStateResult);
  }

  // 把自己从下级的依赖列表中移除。因为会下级又会重新观察
  for (final sub in previousDependencies.entries) {
    sub.key
      .._providerDependents.remove(this)
      .._onRemoveListener();
  }
  _previousDependencies = null;
}

5. 举个例子...

这一小节我会举几个例子说明Riverpod的刷新流程。看一看Riverpod内部是如何处理刷新过程、优化性能、避免不必要的刷新的。

graph LR Provider_A --> Provider_B

代表B监听了ArefB.watch(providerA)

我们假设每个Provider的内容都是获取上一级Provider的值,返回其+1

例1

graph LR StateProvider_A --> Provider_B --> Provider_C

Q :StateProvider_A变化时,BC是如何变化的?

A :没有Widget监听Provider_C,它的ProviderElement根本不会被创建,其_createFn也不会执行;同理,Provider_B也没有被监听,其Element也未被创建。它们不会有任何变化。

要修改A,就要通过ProviderContainer.readProviderElement获取A对应的ProviderElement。所以A会被创建、修改。

例2

graph LR StateProvider_A --> Provider_B --> Provider_C --> Provider_D --> WidgetRef.watch

Q :当StateProvider_A的值发生改变,B C D是如何变化的?已知invalidateSelf每次都只会把自己的直接后继加入到ProviderScheduler,现在有3个Provider,会在1帧内完成刷新还是3帧?

AB C D会在同一帧刷新。

当前帧:

A变化的那一帧,A会通过setState() -> _notifyListeners() -> B# invalidateSelf() -> _container.scheduler.scheduleProviderRefresh(this),把自己的下级(这里是B)添加到ProviderScheduler待刷新列表中。下一帧开始刷新。

下一帧

ProviderScheduler会执行_performRefresh方法,在for循环内开始执行Element Bflush方法。此时,_stateToRefresh内只有Element B,长度为1。

dart 复制代码
void _performRefresh() {
  for (var i = 0; i < _stateToRefresh.length; i++) {
    // 此刻_stateToRefresh内只有Element B,长度为1
    final element = _stateToRefresh[i];
    if (element.hasListeners) element.flush();
  }
}

flush方法会刷新自己的值,再调用下级CinvalidateSelf

dart 复制代码
  // B的内部
  void flush() {
    _performBuild();
  }

  void _performBuild() {
    buildState();
    _notifyListeners(_state!, previousStateResult);
  }


  void _notifyListeners(
    Result<StateT> newState,
    Result<StateT>? previousStateResult, {
    bool checkUpdateShouldNotify = true,
  }) {
    for (var i = 0; i < _providerDependents.length; i++) {
      // B的下级为C
      _providerDependents[i]._markDependencyChanged();
    }
  }

以下会执行ProviderElement CinvalidateSelf(),将其添加到ProviderScheduer._stateToRefresh列表中,现在列表长度为2

dart 复制代码
// 执行C的`invalidateSelf`
void _markDependencyChanged() {
  _didChangeDependency = true;
  if (_mustRecomputeState) return;

  // will notify children that their dependency may have changed
  invalidateSelf();
}

void invalidateSelf() {
  // C将自己添加到待刷新列表中
  _container.scheduler.scheduleProviderRefresh(this);
}

此时_performRefresh内的for循环继续,对Element CElement D重复相同的过程,最终D执行widget注册的回调(WidgetRef.watch是用listen实现的)

可以看到随着for循环的执行,_stateToRefresh需要刷新的Provider越来越多。这个断点需要打在ProviderScheduler_performRefresh方法上。


例3

graph LR StateProvider_C --> Provider_D StateProvider_A --> Provider_B --> Provider_D --> WidgetRef.watch

Q : 先修改C,再修改A,刷新流程是怎样的?Provider D会刷新两次吗?

答: D只会刷新一次,这里用到了4.3小节中提到过的_maybeRebuildDependencies(),过程如下

当前帧:

  1. C会调用DinvalidateSelf,把D加入待刷新列表;
  2. A会调用BinvalidateSelf,把B加入待刷新列表,同时B标记D的依赖可能发生改变。
dart 复制代码
@override
void invalidateSelf() {
  _container.scheduler.scheduleProviderRefresh(this);
  // B的下级是D,通知孩子依赖可能被更改
  visitChildren(
    elementVisitor: (element) => element._markDependencyMayHaveChanged(),
    notifierVisitor: (notifier) => notifier.notifyDependencyMayHaveChanged(),
  );
}

现在_stateToRefresh内的Element依次是 [D,B]D_dependencyMayHaveChanged == true

下一帧

  1. 首先会执行Dflush方法,进入_maybeRebuildDependencies(),由于标记位为true,所以D会尝试刷新自己的上级,即CB
dart 复制代码
// D的flush方法
void flush() {
  _maybeRebuildDependencies();
  if (_mustRecomputeState) {
    _mustRecomputeState = false;
    _performBuild();
  }
}

// 刷新自己的上级
void _maybeRebuildDependencies() {
  if (!_dependencyMayHaveChanged) return;
  _dependencyMayHaveChanged = false;
  // D的上级是C和B,会调用它们的flush方法
  visitAncestors(
    (element) => element.flush(),
  );
}
  1. C_mustRecomputeState为false,跳过刷新
  2. B还未被刷新(在待刷新列表中,B排在D后面),会执行Bflush方法
  3. D_maybeRebuildDependencies执行完,B和C都是最新状态,刷新自己
  4. for循环走到下一步,尝试刷新B。但是它已经被刷新过,_mustRecomputeState == false,跳过刷新过程

这里time是当前毫秒时间戳,可以看到是同一帧内刷新,刷新顺序也是先B后D,且只刷新一次

相关推荐
小墙程序员9 小时前
Flutter 教程(六)路由管理
flutter
zacksleo14 小时前
鸿蒙Flutter开发故事:不,你不需要鸿蒙化
flutter·harmonyos
帅次15 小时前
Flutter DropdownButton 详解
android·flutter·ios·kotlin·gradle·webview
bst@微胖子16 小时前
Flutter项目之构建打包分析
flutter
江上清风山间明月18 小时前
Flutter开发There are multiple heroes that share the same tag within a subtree报错
android·javascript·flutter·动画·hero
无知的前端18 小时前
Flutter 性能优化:实战指南
flutter·性能优化
飞川00119 小时前
Flutter敏感词过滤实战:基于AC自动机的高效解决方案
android·flutter
小墙程序员19 小时前
Flutter 教程(五)事件处理
flutter
耳東陈19 小时前
Flutter开箱即用一站式解决方案
flutter
SoaringHeart19 小时前
Flutter进阶:日志信息的快速定位解决方案 DLog
前端·flutter