在移动应用开发中,合理管理状态的生命周期至关重要。如果状态在不再使用时仍占用内存,会导致应用性能下降;反之,如果状态在重新进入页面时没有重置,可能会显示过时的数据。

Riverpod 提供了 autoDispose 机制来自动解决这些问题。本文将介绍其核心概念、在 riverpod_generator 中的用法以及高级应用场景。

1. 核心概念:什么是 AutoDispose?

AutoDispose 是 Riverpod 的一种机制,用于在 Provider 不再被使用时自动销毁其状态。

  • 触发时机:当一个 Provider 不再有任何监听者(Listeners)时(例如,所有监听该 Provider 的 UI 页面都已被关闭)。
  • 主要作用
    1. 释放内存:销毁不再需要的对象(如关闭数据库连接、取消网络请求)。
    2. 重置状态:当用户重新进入页面时,重新执行 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 应用。