Riverpod之Provider(一),使用Provider讲解了WidgetRef和Ref的watch要点
Riverpod之StateProvider(二),讲解了StateProvider的内部流程,主要涉及的是其内部的名叫state的Provider。
Riverpod之Provider&StateProvider(三),讲解了Provider和StateProvider的组合使用。
Riverpod之StateNotifierProvider(四),介绍了StateNotifierProvider的使用。
Riverpod之FutureProvider(五),介绍了FutureProvider的使用。
Riverpod之select(六),介绍了Provider的select方法使用和原理。
Riverpod之family(七),介绍了family方法得使用和内部流程
前言
autoDispose是除family之外的另一修饰符,它可以给各种Provider添加一个额外功能:自动回收。之前使用的各种Provider,它的生命周期和应用是一样长的,尤其是像family量产的Providers,不能回收就会导致内存泄漏。
所以如果你的Provider不需要和应用共生死,考虑使用autoDispose修饰一把,该走的时候就得送走。autoDispose的使用和family一样,我们先从一个Demo开始
autoDispose的使用
核心代码如下,需要dispose的Provider使用autoDispose修饰即可
// 使用dio负责网络请求
final dioProvider = Provider((ref) => Dio());
final postProvider = FutureProvider.autoDispose<String>((ref) async {
CancelToken cancelToken = CancelToken();
// 对dispose回调监听,被销毁时取消网络请求
ref.onDispose(() {
cancelToken.cancel();
});
Response<String> response = await ref.watch(dioProvider).get<String>(
"https://jsonplaceholder.typicode.com/posts/",
cancelToken: cancelToken);
if (response.data == null || response.statusCode != 200) {
throw HttpException('Http Error! ${response.statusCode}');
} else {
return response.data!;
}
});
主页面,没有使用Provider,用来跳转到_Post组件
class AutoDisposeApp extends StatelessWidget {
const AutoDisposeApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: _Home());
}
}
class _Home extends StatelessWidget {
const _Home();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 点击跳转到_Post组件
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) {
return const _Post();
},
));
},
child: const Icon(Icons.forward),
),
);
}
}
_Post页面,正常使用postProvider
class _Post extends StatelessWidget {
const _Post();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Posts')),
body: Consumer(builder: (context, ref, _) {
AsyncValue<String> post = ref.watch(postProvider);
return post.when(
data: (value) {
return Text(value);
},
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text("$err"));
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 退出此页面,postProvider即被销毁
Navigator.pop(context);
},
child: const Icon(Icons.keyboard_return),
),
);
}
}
由于postProvider是autoDispose的,每次退出的时候都会回收掉,所以每次进入都会重新初始化,也就会重新请求数据,效果如下
如果你不想每次都重复请求可以使用maintainState属性
ref.maintainState = true;
在请求成功后标记maintainState为true,有以下好处
- 如果网络请求过程中退出当前页面,那么会取消该网络请求,下次进入会重新请求
- 如果网络请求失败,下次进入同样会重新请求
- 如果请求成功,maintainState设置成功,postProvider不会销毁,下次会直接读取数据
final postProvider = FutureProvider.autoDispose<String>((ref) async {
...
if (response.data == null || response.statusCode != 200) {
throw HttpException('Http Error! ${response.statusCode}');
} else {
// 成功后标记保持状态
ref.maintainState = true;
return response.data!;
}
});
那audoDispose和maintainState属性到底是如何改变Provider的行为的呢?
audoDispose内部流程
以FutureProvider为例,和其family一样,autoDispose修饰后也是通过builder构造示例的
static const autoDispose = AutoDisposeFutureProviderBuilder();
关于call方法在famlily说过,比较特殊,在调用时可以省略,实际上是下面这样
FutureProvider.autoDispose.call((ref) async {…});
通过这种方式,返回的就不是之前的FutureProvider实例了,而是悄咪咪的变成了AutoDisposeFutureProvider
AutoDisposeFutureProvider<State> call<State>(
Create<FutureOr<State>, AutoDisposeFutureProviderRef<State>> create, {
String? name,
List<ProviderOrFamily>? dependencies,
}) {
return AutoDisposeFutureProvider(
create,
name: name,
dependencies: dependencies,
);
}
那AutoDisposeFutureProvider创建的Element有什么特殊的地方呢?在于其复写了mayNeedDispose方法,如果没有设置maintainState=true,也没有Listeners,在该销毁的时候就会安排上。而基类中其是个空方法,也就是默认不会执行回收操作。
abstract class ProviderElementBase<State> implements Ref {
...
@protected
@visibleForOverriding
void mayNeedDispose() {}
}
abstract class AutoDisposeProviderElementBase<State>
extends ProviderElementBase<State> implements AutoDisposeRef {
...
@override
void mayNeedDispose() {
if (!maintainState && !hasListeners) {
_container._scheduler.scheduleProviderDispose(this);
}
}
}
那Listeners具体指哪些呢,其实是对三个List的判断
- _listeners的元素类型为_ProviderSubscription,来自WidgetRef(ConsumerWidget之类的Widget)的watch或listen
- _subscribers的元素类型为_ProviderListener,来自Ref(各种ProviderElement)的listen
- _dependents的元素类型为ProviderElementBase,来自Ref(各种ProviderElement)的watch
bool get hasListeners =>
_listeners.isNotEmpty ||
_subscribers.isNotEmpty ||
_dependents.isNotEmpty;
当maintainState为false的时候,这个ProviderElement既没有来自WidgetRef的wacth、listen也没有来自Ref的watch、listen才是可回收的。注意回收的是这个ProviderElement,并不是Provider。
知道了原理,你就知道,不是使用了autoDispose就能回收掉,即使用autoDispose的Provider要是在根页面被watch或listen,那大概率就是送不走的。
dispose流程
那Provider的回收流程是怎么触发的呢?其实,Provider就是为组件提供数据的,它始于Widget,当然也终于Widget,当一个和Provider相关的Widget收到framework的unmount通知时,回收也就开始了
- _dependencies记录了watch的Provider
- _listeners记录了listen的Provider
class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
....
@override
Res watch<Res>(ProviderListenable<Res> target) {
return _dependencies.putIfAbsent(target,...).read() as Res;
}
@override
void listen<T>(
ProviderListenable<T> provider,
void Function(T? previous, T value) listener, {
void Function(Object error, StackTrace stackTrace)? onError,
}) {
...
final sub = _container.listen<T>(provider, listener, onError: onError);
_listeners.add(sub);
}
@override
void unmount() {
for (final dependency in _dependencies.values) {
dependency.close();
}
for (var i = 0; i < _listeners.length; i++) {
_listeners[i].close();
}
super.unmount();
}
}
在unmount方法中,把watch、listen的Providers都close了一把,,这个close方法实现如下:
class _ProviderSubscription<State> implements ProviderSubscription<State> {
...
@override
void close() {
_closed = true;
_listenedElement._listeners.remove(this);
_listenedElement.mayNeedDispose();
}
...
}
这个_listeners在前面说过,是Widget在watch、listen时添加的,现在Widget要走了,得先移除掉
_listeners的元素类型为_ProviderSubscription,来自WidgetRef(ConsumerWidget之类的Widget)的watch或listen
mayNeedDispose方法前面已说过了,在满足条件后,通过下面方法开始真正的dispose流程
_container._scheduler.scheduleProviderDispose(this);
scheduleProviderDispose方法的执行还是有自己的一套的,类似操作参考Ref watch Ref,不再赘述。关键操作如下:
class _ProviderScheduler {
void _performDispose() {
for (var i = 0; i < _stateToDispose.length; i++) {
final element = _stateToDispose[i];
if (element.maintainState || element.hasListeners || !element._mounted) {
continue;
}
element._container._disposeProvider(element._origin);
}
}
}
从下面方法可以看出回收的是Provider对应的element,主要两点
- 执行element.dispose方法回收资源
- 从container及其子container中的此element都移除。
void _disposeProvider(ProviderBase<Object?> provider) {
final element = readProviderElement(provider);
element.dispose();
final reader = _stateReaders[element._origin]!;
if (reader.isDynamicallyCreated) {
void removeStateReaderFrom(ProviderContainer container) {
container._stateReaders.remove(element._origin);
for (var i = 0; i < container._children.length; i++) {
removeStateReaderFrom(container._children[i]);
}
}
removeStateReaderFrom(this);
} else {
...
}
}
要看elment中的dispose具体做了哪些操作,还得先看懂三个集合
- _subscriptions,记录的是此elemnt所有listen的providers
- _dependencies,记录的是此elemnt所有watch的providers
- _onDisposeListeners,记录的是监听dispose方法调用的回调
void dispose() {
_mounted = false;
_runOnDispose();
for (final sub in _dependencies.entries) {
sub.key._dependents.remove(this);
sub.key.mayNeedDispose();
}
...
}
void _runOnDispose() {
for (final subscription in _subscriptions) {
subscription.close();
}
_subscriptions.clear();
_onDisposeListeners?.forEach(_runGuarded);
_onDisposeListeners = null;
}
了解这三个集合的含义,dispose的操作也就明了了:
- 将我listen的所有providers都执行关闭,触发其回收操作,看看离了我它是不是也得挂
- 将我从watch的所有Providers中移除掉,再触发其回收操作,看看离了我它是不是也得挂
- 对我挂掉这件事感兴趣的人,我死前得通知一声:哥走了
至此流程结束