Riverpod之autoDispose(八)

前言

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXPdPVUy-1692099742983)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b79f9b540c48425687e2dabb96c8dd8f~tplv-k3u1fbpfcp-watermark.image?)]

如果你不想每次都重复请求可以使用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中移除掉,再触发其回收操作,看看离了我它是不是也得挂
  • 对我挂掉这件事感兴趣的人,我死前得通知一声:哥走了

至此流程结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android网络请求的封装可以通过以下步骤实现: 1. 在AndroidManifest.xml文件中添加网络权限,以确保应用程序可以进行网络请求。可以在<manifest>标签内添加以下代码: <uses-permission android:name="android.permission.INTERNET"/> \[1\] 2. 定义一个网络请求回调接口,用于处理请求成功和失败的回调方法。可以创建一个接口,并在接口中定义onSuccess和onFailure方法,如下所示: public interface CallBackLis<T> { void onSuccess(String method, T content); void onFailure(String method, String error); } \[2\] 3. 在Activity或Fragment中调用网络请求方法。可以使用HttpRequest类中的静态方法来发送网络请求,并传入回调接口的实例作为参数。例如: HttpRequest.login(activity, new CallBackLis<具体data类型>() { @Override public void onSuccess(String method, 具体data类型 content) { // 在这里处理请求成功后的逻辑,content为我们需要的数据 } @Override public void onFailure(String method, String error) { // 在这里处理请求失败后的逻辑,error为错误信息 toast(error); } }); \[3\] 通过以上步骤,可以实现Android网络请求的封装,使得网络请求的调用更加简洁和方便。 #### 引用[.reference_title] - *1* [初学Android网络封装](https://blog.csdn.net/weixin_43993331/article/details/122151795)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Android 网络请求封装(Retrofit+OkHttp+RxJava+AutoDispose),解决网络请求引起的内存泄漏](https://blog.csdn.net/u013624014/article/details/122000289)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值