Riverpod Provider 自动销毁原理深度解析

3 阅读6分钟

Riverpod Provider 自动销毁原理深度解析

核心概念

Riverpod 的自动销毁(auto-dispose)基于 引用计数 + 生命周期管理

1. 核心类结构

// 简化的源码结构(基于 Riverpod 3.0)

/// ProviderContainer 是所有 Provider 的容器
class ProviderContainer {
  /// 存储所有 Provider 的状态
  final Map<ProviderBase, ProviderElementBase> _providerElements = {};
  
  /// 读取 Provider(自动创建 element)
  T read<T>(ProviderBase<T> provider) {
    return _getOrCreateElement(provider).readSelf();
  }
  
  ProviderElementBase _getOrCreateElement(ProviderBase provider) {
    return _providerElements.putIfAbsent(provider, () {
      // 创建 element 并初始化
      return provider.createElement()..mount();
    });
  }
}

/// ProviderElement 是每个 Provider 实例的实际状态管理者
abstract class ProviderElementBase<State> {
  /// 监听者列表(谁在 watch 这个 provider)
  final List<ProviderSubscription> _subscriptions = [];
  
  /// 是否应该自动销毁
  bool get autoDispose => provider.autoDispose;
  
  /// 引用计数
  int get _listenerCount => _subscriptions.length;
  
  /// 是否已销毁
  bool _disposed = false;
  
  /// 当前状态
  late State _state;
}

2. 监听机制(引用计数)

class ProviderElementBase<State> {
  /// 添加监听者
  ProviderSubscription<State> addListener(
    Node node,
    void Function(State? previous, State next) listener,
  ) {
    final subscription = ProviderSubscription._(
      this,
      node,
      listener,
    );
    
    _subscriptions.add(subscription);
    
    // 🎯 关键:如果之前没有监听者,触发 onResume
    if (_subscriptions.length == 1 && _onResumeCallbacks != null) {
      for (final callback in _onResumeCallbacks!) {
        callback();
      }
    }
    
    return subscription;
  }
  
  /// 移除监听者
  void removeListener(ProviderSubscription subscription) {
    _subscriptions.remove(subscription);
    
    // 🎯 关键:如果没有监听者了
    if (_subscriptions.isEmpty) {
      // 触发 onCancel 回调
      if (_onCancelCallbacks != null) {
        for (final callback in _onCancelCallbacks!) {
          callback();
        }
      }
      
      // 如果是 autoDispose,标记为待销毁
      if (autoDispose) {
        _scheduleDispose();
      }
    }
  }
  
  /// 延迟销毁(防止频繁创建销毁)
  void _scheduleDispose() {
    // 在下一个微任务中检查是否真的需要销毁
    scheduleMicrotask(() {
      if (_subscriptions.isEmpty && autoDispose && !_disposed) {
        dispose();
      }
    });
  }
  
  /// 真正的销毁
  void dispose() {
    if (_disposed) return;
    _disposed = true;
    
    // 🎯 触发 onDispose 回调
    if (_onDisposeCallbacks != null) {
      for (final callback in _onDisposeCallbacks!) {
        callback();
      }
    }
    
    // 清理状态
    _state = null as State;
    
    // 从容器中移除
    container._providerElements.remove(provider);
  }
}

3. Widget 如何触发监听

/// ConsumerWidget 的实现原理
abstract class ConsumerWidget extends Widget {
  @override
  ConsumerStatefulElement createElement() => ConsumerStatefulElement(this);
}

class ConsumerStatefulElement extends StatefulElement {
  /// 当前 element 持有的所有 subscription
  final List<ProviderSubscription> _subscriptions = [];
  
  /// ref.watch 的实现
  T watch<T>(ProviderListenable<T> provider) {
    // 🎯 关键:添加监听,引用计数 +1
    final subscription = provider.addListener(
      this,  // 传入当前 widget element
      (previous, next) {
        // 当 provider 状态变化时,标记需要重建
        markNeedsBuild();
      },
    );
    
    _subscriptions.add(subscription);
    
    return provider.read();
  }
  
  @override
  void unmount() {
    // 🎯 关键:Widget 销毁时,取消所有订阅,引用计数 -1
    for (final subscription in _subscriptions) {
      subscription.close();
    }
    _subscriptions.clear();
    
    super.unmount();
  }
}

4. 完整的生命周期流程

Widget 创建
    ↓
ref.watch(provider)
    ↓
创建 ProviderElement(如果不存在)
    ↓
element.addListener()  → 引用计数 +1
    ↓
如果是第一个监听者 → 触发 onInit + onResume
    ↓
【Provider 正常工作中】
    ↓
Widget 销毁(页面退出)
    ↓
element.removeListener()  → 引用计数 -1
    ↓
如果引用计数 = 0 → 触发 onCancel
    ↓
如果 autoDispose = true → 延迟销毁
    ↓
检查:还有监听者吗?
    ├─ 有 → 取消销毁
    └─ 没有 → 执行 dispose()
           ↓
       触发 onDispose
           ↓
       清理资源
           ↓
       从容器中移除

5. 实战示例:源码级别的理解

示例 1:单页面使用

@riverpod  // autoDispose: true(默认)
class Counter extends _$Counter {
  @override
  int build() {
    print('🏗️ Counter build - 创建 element');
    
    ref.onInit(() {
      print('🎬 onInit - 第一个监听者到来');
    });
    
    ref.onResume(() {
      print('▶️ onResume - 有新监听者');
    });
    
    ref.onCancel(() {
      print('⏸️ onCancel - 所有监听者离开');
    });
    
    ref.onDispose(() {
      print('💀 onDispose - element 销毁');
    });
    
    return 0;
  }
  
  void increment() => state++;
}

// 场景:进入页面 → 离开页面
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 🔥 watch 触发 addListener,引用计数 +1
    final count = ref.watch(counterProvider);
    
    return Text('$count');
  }
}

// 日志输出:
// 用户进入页面:
//   🏗️ Counter build
//   🎬 onInit
//   ▶️ onResume
//
// 用户离开页面:
//   ⏸️ onCancel
//   💀 onDispose

示例 2:多个 Widget 使用

class Page1 extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 引用计数 +1(第1个监听者)
    final count = ref.watch(counterProvider);
    return Text('Page1: $count');
  }
}

class Page2 extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 引用计数 +1(第2个监听者)
    final count = ref.watch(counterProvider);
    return Text('Page2: $count');
  }
}

// 场景:
// 1. 打开 Page1 → 引用计数 = 1 → 触发 onInit + onResume
// 2. 打开 Page2 → 引用计数 = 2 → 不触发任何回调
// 3. 关闭 Page1 → 引用计数 = 1 → 不触发任何回调
// 4. 关闭 Page2 → 引用计数 = 0 → 触发 onCancel → 销毁

示例 3:keepAlive 的区别

@Riverpod(keepAlive: true)  // 🔒 永不自动销毁
class GlobalConfig extends _$GlobalConfig {
  @override
  Config build() {
    print('🏗️ GlobalConfig build');
    
    ref.onDispose(() {
      print('💀 永远不会执行(除非 app 关闭)');
    });
    
    return Config();
  }
}

// 即使没有任何 Widget 监听,element 也不会销毁
// 引用计数可以变成 0,但不会触发 dispose()

6. 延迟销毁的作用(防抖)

void _scheduleDispose() {
  // 🎯 为什么用 scheduleMicrotask?
  // 防止以下场景:
  scheduleMicrotask(() {
    if (_subscriptions.isEmpty && autoDispose && !_disposed) {
      dispose();
    }
  });
}

// 场景:快速切换页面
// 1. 离开 PageA → 引用计数 = 0 → 调度销毁
// 2. 立即进入 PageB(同一帧内)→ 引用计数 = 1
// 3. 检查销毁条件 → 引用计数 != 0 → 取消销毁
// 
// 如果没有延迟,会导致:
// 离开 PageA → 立即销毁 → 进入 PageB → 重新创建
// 性能浪费!

7. 核心数据结构

/// Provider 订阅关系
class ProviderSubscription<T> {
  final ProviderElementBase<T> source;  // 被监听的 provider
  final Node listener;                   // 监听者(通常是 Widget)
  final void Function(T? previous, T next) onChange;
  
  void close() {
    source.removeListener(this);  // 移除监听,引用计数 -1
  }
}

/// 依赖图节点
abstract class Node {
  /// 这个节点依赖的所有 provider
  final List<ProviderSubscription> _dependencies = [];
  
  /// 这个节点被销毁时,自动清理所有依赖
  void dispose() {
    for (final dep in _dependencies) {
      dep.close();  // 每个依赖的引用计数 -1
    }
  }
}

8. 真实源码位置

// packages/riverpod/lib/src/framework/container.dart
class ProviderContainer { ... }

// packages/riverpod/lib/src/framework/element.dart  
abstract class ProviderElementBase<State> { ... }

// packages/flutter_riverpod/lib/src/consumer.dart
class ConsumerStatefulElement extends StatefulElement { ... }

总结:自动销毁的本质

  1. 引用计数机制

    • 每个 ref.watch → +1
    • Widget dispose → -1
    • 计数 = 0 且 autoDispose = true → 销毁
  2. 生命周期回调

    • onInit: 首次创建时
    • onResume: 0 → 1 监听者时
    • onCancel: n → 0 监听者时
    • onDispose: 真正销毁时
  3. 延迟销毁优化

    • 使用微任务延迟
    • 防止频繁创建销毁
    • 提升性能
  4. keepAlive 的本质

    • 就是跳过 if (autoDispose) 检查
    • 引用计数仍然工作
    • 只是不触发销毁

这就是 Riverpod 自动管理内存的核心机制!🎯

scheduleMicrotask 在异步代码中优先级最高,但永远晚于同步代码

和Android中的View.post类似

Riverpod 以此 来实现

延迟销毁优化

  • 使用微任务延迟

  • 防止频繁创建销毁

  • 提升性能

// ❌ 如果用 delay(错误的做法)

void _scheduleDispose() {

Future.delayed(Duration(milliseconds: 100), () {

if (_subscriptions.isEmpty && autoDispose && !_disposed) {

dispose();

}

});

}

// ✅ 为什么用 scheduleMicrotask(正确的做法)

void _scheduleDispose() {

scheduleMicrotask(() {

if (_subscriptions.isEmpty && autoDispose && !_disposed) {

dispose();

}

});

}


## 2. Dart 事件循环机制

Dart 的事件循环分为两个队列:

┌─────────────────────────────────────┐

│ Dart Event Loop │

├─────────────────────────────────────┤

│ 1️⃣ Microtask Queue(微任务队列) │ ← 优先级高

│ - scheduleMicrotask │

│ - Future.microtask │

├─────────────────────────────────────┤

│ 2️⃣ Event Queue(事件队列) │ ← 优先级低

│ - Future │

│ - Timer │

│ - I/O events │

│ - User interactions │

└─────────────────────────────────────┘

执行顺序:

当前同步代码 → Microtask Queue → Event Queue → 下一帧

在同一个 Event Loop Tick 内,同步代码先执行完,再执行 Microtask

scheduleMicrotask 在异步代码中优先级最高,但永远晚于同步代码

和Android中的View.post类似

Riverpod 以此 来实现

延迟销毁优化

  • 使用微任务延迟

  • 防止频繁创建销毁

  • 提升性能

// ❌ 如果用 delay(错误的做法)

void _scheduleDispose() {

Future.delayed(Duration(milliseconds: 100), () {

if (_subscriptions.isEmpty && autoDispose && !_disposed) {

dispose();

}

});

}

// ✅ 为什么用 scheduleMicrotask(正确的做法)

void _scheduleDispose() {

scheduleMicrotask(() {

if (_subscriptions.isEmpty && autoDispose && !_disposed) {

dispose();

}

});

}


## 2. Dart 事件循环机制

Dart 的事件循环分为两个队列:

┌─────────────────────────────────────┐

│ Dart Event Loop │

├─────────────────────────────────────┤

│ 1️⃣ Microtask Queue(微任务队列) │ ← 优先级高

│ - scheduleMicrotask │

│ - Future.microtask │

├─────────────────────────────────────┤

│ 2️⃣ Event Queue(事件队列) │ ← 优先级低

│ - Future │

│ - Timer │

│ - I/O events │

│ - User interactions │

└─────────────────────────────────────┘

执行顺序:

当前同步代码 → Microtask Queue → Event Queue → 下一帧

在同一个 Event Loop Tick 内,同步代码先执行完,再执行 Microtask

系统不能干涉,应用层的同步代码是否超时,但是 Riverpod 的设计依托于 Widget 生命周期方法都是同步的,比如系统框架的 build 和 dispose 两个方法里面一定是同步的,而 引用计数和判断计数是否可回收,都是在同步方法里面执行的,完全能保证 Riverpod的逻辑自洽