这一节我们主要关注ProviderElement,也是Riverpod的核心部分。我们通过watch观察、read读取、listen注册回调、invalidate刷新都要通过它。
老样子,我会以最基础的ProviderElement为例讲解。
1.Ref和WidgetRef
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
通常我们会用Ref或WidgetRef从另一个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 总结
- 创建
ProviderElement的唯一时机就是通过ProviderContainer去使用它时,即ProviderContainer#readProviderElement方法 ProviderElementBase内部的初始化流程大概是这样的:mount()->buildState()->create(),create()又会执行provider的_createFn。- 如果某个
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)发生改变时就会触发这个回调。
WidgetRef的watch也是通过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,
);
}
只要通过Ref或WidgetRef监听一个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改变时,如何通知监听者
- 会依次调用
_dependents中保存的回调,触发listen 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会在下一帧统一执行ProviderElementBase的flush方法。
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内部是如何处理刷新过程、优化性能、避免不必要的刷新的。
代表B监听了A,refB.watch(providerA)
我们假设每个Provider的内容都是获取上一级Provider的值,返回其+1
例1
Q :
StateProvider_A变化时,B和C是如何变化的?
A :没有Widget监听Provider_C,它的ProviderElement根本不会被创建,其_createFn也不会执行;同理,Provider_B也没有被监听,其Element也未被创建。它们不会有任何变化。
要修改A,就要通过ProviderContainer.readProviderElement获取A对应的ProviderElement。所以A会被创建、修改。
例2
Q :当
StateProvider_A的值发生改变,BCD是如何变化的?已知invalidateSelf每次都只会把自己的直接后继加入到ProviderScheduler,现在有3个Provider,会在1帧内完成刷新还是3帧?
A :B C D会在同一帧刷新。
当前帧:
当A变化的那一帧,A会通过setState() -> _notifyListeners() -> B# invalidateSelf() -> _container.scheduler.scheduleProviderRefresh(this),把自己的下级(这里是B)添加到ProviderScheduler待刷新列表中。下一帧开始刷新。
下一帧
ProviderScheduler会执行_performRefresh方法,在for循环内开始执行Element B的flush方法。此时,_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方法会刷新自己的值,再调用下级C的invalidateSelf
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 C的invalidateSelf(),将其添加到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 C、Element D重复相同的过程,最终D执行widget注册的回调(WidgetRef.watch是用listen实现的)
可以看到随着for循环的执行,_stateToRefresh需要刷新的Provider越来越多。这个断点需要打在ProviderScheduler的_performRefresh方法上。
例3
Q : 先修改
C,再修改A,刷新流程是怎样的?Provider D会刷新两次吗?
答: D只会刷新一次,这里用到了4.3小节中提到过的_maybeRebuildDependencies(),过程如下
当前帧:
C会调用D的invalidateSelf,把D加入待刷新列表;A会调用B的invalidateSelf,把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
下一帧:
- 首先会执行
D的flush方法,进入_maybeRebuildDependencies(),由于标记位为true,所以D会尝试刷新自己的上级,即C和B
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(),
);
}
C的_mustRecomputeState为false,跳过刷新B还未被刷新(在待刷新列表中,B排在D后面),会执行B的flush方法D的_maybeRebuildDependencies执行完,B和C都是最新状态,刷新自己for循环走到下一步,尝试刷新B。但是它已经被刷新过,_mustRecomputeState == false,跳过刷新过程
这里time是当前毫秒时间戳,可以看到是同一帧内刷新,刷新顺序也是先B后D,且只刷新一次