在移动应用开发中,合理管理状态的生命周期至关重要。如果状态在不再使用时仍占用内存,会导致应用性能下降;反之,如果状态在重新进入页面时没有重置,可能会显示过时的数据。
Riverpod 提供了 autoDispose 机制来自动解决这些问题。本文将介绍其核心概念、在 riverpod_generator 中的用法以及高级应用场景。
1. 核心概念:什么是 AutoDispose?
AutoDispose 是 Riverpod 的一种机制,用于在 Provider 不再被使用时自动销毁其状态。
- 触发时机:当一个 Provider 不再有任何监听者(Listeners)时(例如,所有监听该 Provider 的 UI 页面都已被关闭)。
- 主要作用:
- 释放内存:销毁不再需要的对象(如关闭数据库连接、取消网络请求)。
- 重置状态:当用户重新进入页面时,重新执行 Provider 的构建逻辑,确保数据是最新的。
2. 基础用法:默认行为
在使用 riverpod_generator 时,您不需要像旧语法那样显式调用 .autoDispose。所有通过代码生成器创建的 Provider 默认都是自动释放的。
示例:自动释放的 HTTP 请求
假设有一个详情页,每次进入时都需要获取最新的详情数据,退出时应清理数据。
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'details_provider.g.dart';
// 使用 @riverpod 注解,默认开启 autoDispose
@riverpod
Future<String> fetchDetails(Ref ref) async {
// 模拟网络请求
await Future.delayed(const Duration(seconds: 1));
return '这是最新的详细数据';
}
- 行为:当用户进入详情页并
ref.watch(fetchDetailsProvider)时,请求开始。当用户退出详情页(不再监听)时,该 Provider 的状态会被销毁。下次进入时,会重新发起请求。
3. 关闭自动释放:保持状态 (KeepAlive)
有些状态是全局的(如用户配置、购物车数据),即使当前没有页面显示它们,我们也不希望它们被销毁。
在 riverpod_generator 中,可以通过在注解中设置 keepAlive: true 来禁用自动释放。
示例:全局用户配置
// 设置 keepAlive: true,状态将在整个应用生命周期内保持
@Riverpod(keepAlive: true)
String userProfile(Ref ref) {
return 'User123';
}
- 行为:即使没有任何 Widget 监听
userProfileProvider,它的数据('User123')也会一直保留在内存中,直到应用重启或被手动刷新。
4. 高级用法:细粒度的生命周期控制
有时我们需要介于 "总是释放" 和 "总是保留" 之间的逻辑。Riverpod 提供了 ref.keepAlive() 方法来实现灵活控制。
场景 A:请求未完成时不销毁
如果用户发起了一个耗时的 HTTP 请求,但在请求完成前离开了页面,默认的 autoDispose 会取消该请求(这通常是好事)。但如果您希望请求在后台继续完成(例如为了缓存结果供下次使用),可以使用 ref.keepAlive()。
@riverpod
Future<void> slowRequest(Ref ref) async {
// 1. 获取一个 KeepAliveLink,告诉 Riverpod "暂时别销毁我"
final link = ref.keepAlive();
try {
// 执行耗时操作
await http.get(Uri.parse('https://example.com/large-file'));
} finally {
// 2. 操作完成后,关闭 link。
// 如果此时没有监听者,Provider 将被销毁;如果有,则继续保持。
link.close();
}
}
场景 B:基于时间的缓存(Time-to-Live)
您可能希望数据在不再使用后还能保留一段时间(例如 5 分钟),以便用户快速返回时无需重新加载。
import 'dart:async';
@riverpod
Future<String> cachedData(Ref ref) async {
// 获取数据
final response = await http.get(Uri.parse('https://api.example.com/data'));
// 获取 KeepAliveLink
final link = ref.keepAlive();
// 设置一个定时器,在 Provider 不再被监听时(onDispose)启动倒计时
Timer? timer;
ref.onDispose(() {
timer?.cancel();
});
ref.onCancel(() {
// 当没有监听者时,启动 5 分钟倒计时
timer = Timer(const Duration(minutes: 5), () {
// 倒计时结束,关闭 link,允许 Riverpod 销毁状态
link.close();
});
});
ref.onResume(() {
// 如果在倒计时期间用户又回来了,取消定时器,继续保持状态
timer?.cancel();
});
return response.body;
}
5. 参数传递与 AutoDispose
在使用 riverpod_generator 时,传递参数(Family)非常简单,且它们完美支持 autoDispose。
@riverpod
Future<String> productDetails(Ref ref, {required int productId}) async {
return await api.fetchProduct(productId);
}
- 独立销毁:
productDetailsProvider(productId: 1)和productDetailsProvider(productId: 2)是两个独立的状态。 - 行为:如果用户不再查看 ID 为 1 的商品,但仍在查看 ID 为 2 的商品,那么 ID 1 的缓存会被自动销毁,而 ID 2 的保持不变。
6. 总结与建议
| 功能 | 说明 | riverpod_generator 语法 |
|---|---|---|
| 自动释放 (默认) | 页面关闭即销毁,适合页面级数据。 | @riverpod |
| 持久状态 | 全局单例,适合用户信息、应用配置。 | @Riverpod(keepAlive: true) |
| 条件保持 | 在特定操作完成前或特定时间内保持。 | 在函数体内使用 ref.keepAlive() |
最佳实践:
- 默认使用
@riverpod(自动释放)。这能最大程度防止内存泄漏,并确保用户总能看到最新数据。 - 仅对确实需要跨页面共享且不应频繁重置的数据使用
keepAlive: true。 - 善用
ref.invalidate(provider)来手动触发刷新(例如下拉刷新功能)。
通过合理使用这些机制,您可以构建出既流畅又节省资源的 Flutter 应用。