likes
comments
collection
share

Riverpod之autoDispose(八)

作者站长头像
站长
· 阅读数 6

前言

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的,每次退出的时候都会回收掉,所以每次进入都会重新初始化,也就会重新请求数据,效果如下

Riverpod之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);

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中移除掉,再触发其回收操作,看看离了我它是不是也得挂
  • 对我挂掉这件事感兴趣的人,我死前得通知一声:哥走了

至此流程结束