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,且只刷新一次

相关推荐
maaath8 分钟前
【无标题】Flutter for OpenHarmony 的文具手账应用开发实践
flutter·华为·harmonyos
里欧跑得慢9 分钟前
Flutter 主题管理:构建一致的用户界面
前端·css·flutter·web
liulian091615 小时前
Flutter for OpenHarmony 跨平台开发:单位转换功能实战指南
flutter
千码君201616 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
maaath17 小时前
【maaath】Flutter for OpenHarmony 手表配饰应用实战开发
flutter·华为·harmonyos
maaath18 小时前
【maaath】Flutter for OpenHarmony 跨平台计算器应用开发实践
flutter·华为·harmonyos
maaath1 天前
【maaath】Flutter for OpenHarmony 闹钟时钟应用开发实战
flutter·华为·harmonyos
maaath1 天前
【maaath】Flutter for OpenHarmony 短信管理应用实战
flutter·华为·harmonyos
maaath1 天前
【maaath】Flutter for OpenHarmony打造跨平台便签备忘录应用
flutter·华为·harmonyos
千码君20161 天前
flutter:与Android Studio模拟器的调试分享
android·flutter