[Flutter]使用Dio网络请求优化一案例

前言

之前在用 Flutter 开发 Demo,在使用 Dio 请求网络的过程中遇到的一些问题,结合最近在掘金上看到一个不错的文章,于是就把自己的问题拿出来记录一下,看看能不能把之前的优化方案再升级一下

问题背景

技术背景/技术选型:

类别 第三方库
状态管理 riverpod
网络请求 Dio + retrofit
依赖注入 injectable + get_it
本地存储 floor + shared_preferences + hive
UI 组件库 Bruno

injectable 依赖 get_it 库来实现依赖注入,整体使用起来跟做 Android 开发时使用的 MVVM 架构很类似

问题描述: 首先是一个 PageView,当页面切换到目标页时会触发数据请求,在数据请求下来前会先显示一个骨架屏,当在请求到数据前频繁切换 PageView 的页面时会遇到报错:

log 复制代码
Error: Bad state: Tried to use RestaurantsController after `dispose` was called.
Consider checking `mounted`.
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 294:3  throw_
packages/state_notifier/state_notifier.dart 175:9                            <fn>
packages/state_notifier/state_notifier.dart 212:12                           set state
packages/fittrack_flutter/ui/page/food/restaurants_controller.dart 17:9      getRestaurants
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50           <fn>
dart-sdk/lib/async/zone.dart 1661:54                                         runUnary
dart-sdk/lib/async/future_impl.dart 162:18                                   handleValue
dart-sdk/lib/async/future_impl.dart 846:44                                   handleValueCallback
dart-sdk/lib/async/future_impl.dart 875:13                                   _propagateToListeners
dart-sdk/lib/async/future_impl.dart 647:5                                    [_completeWithValue]
dart-sdk/lib/async/future_impl.dart 721:7                                    callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                             _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                              _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 181:7           <fn>

大致的意思是在StateNotifier里在数据请求回来后设置到state的时候,这时 StateNotifier 被dispose了,强行设置数据到state会报这个错误,按照Error信息处理下就不会报这个错了

dart 复制代码
    final Data result = await // Do Request;
    if (mounted) {
      state = result;
    }

但是还有另外一个问题,就是页面多次来回切换以后,UI 上会表现得有些卡顿,这是因为频繁的请求 Api 会往Event队列里多次加入相同的FutureEventMicrotask队列里的任务过多会影响 UI 性能,所以要做一些优化。

这里的StateNotifier是 riverpod 2.0 版本 里的提供的

解决方案

要解决这个问题目前用以下两个方法:

  1. 在页面关闭时,取消所有正在进行中的请求(虽然取消请求在Dio层面只是丢弃当前请求,对相应返回数据不再做处理,节省了数据的读写和解析的时间)
  2. 把第一次请求成功的数据缓存起来,后面在遇到相同的请求就从缓存里读取,代价比从网络请求使用的资源少(适用于返回数据变动不频繁的GET接口,局限性较高)

好在目前我正在写的模块用到的接口返回数据都是不怎么变动的GET接口,方法 2 还在进行中,方法 1 的实现如下:

dart 复制代码
// retrofit下定义的api_service.dart
@GET('api/getData')
Future<Data> getDataList(@CancelRequest() CancelToken cancelToken);

//repository 层定义一个 BaseRepository
abstract class BaseRepository {
  // 主要是用一个Map把正在请求的CancelToken缓存起来
  final Map<String, CancelToken> cancelMap = {};

  CancelToken generateCancelToken(String requestKey) {
    //遇到相同Key的请求就取消前一个请求
    cancelMap[requestKey]?.cancel();
    cancelMap[requestKey] = CancelToken();
    return cancelMap[requestKey]!;
  }

  void requestFinsih(String requestKey) {
    cancelMap.remove(requestKey);
  }

  void cancelAllRequest() {
    cancelMap.forEach((key, value) {
      value.cancel();
    });
    cancelMap.clear();
  }
}
// 具体的Repository里调用api时
Result<Data> result = await apiServcie.getData(generateCancelToken(requestKey));
requestFinsih(requestKey);
// 在StateNotifier的controller层里的dispose回调
  @override
  void dispose() {
    _repository.cancelRequest();
    super.dispose();
  }

该方案目前需要对应的接口额外提供一个requestKey和在Controller层里调用cancelRequest方法,这里增加requestKey的想法是本地缓存也可以用到相同的requestKey,并且在repository层还可以增加缓存过期以及内存缓存的逻辑。

优化方案思考

比如说把Map缓存CancelToken、取消请求、本地缓存和requestKey的生成等逻辑放到Dio的拦截器里,在拦截器层面实现网络请求的防抖节流,方法就是把配置的参数传递过去,根据配置参数来执行一些操作。 缺点是随着配置参数的增多,根据配置参数来实现的代码也会越来越多,可能会造成拦截器里的代码臃肿

总结

这里的优化场景相对来说比较单一,还有更多场景比如:

  • 输入框实时搜索请求网络时在控件层面加防抖,实时搜索的返回数据变动较大不适合节流,加入防抖后在repository层要先把之前的请求取消掉
  • 按钮点击请求网络时加入节流,避免同时触发多个请求,或者在触发一次请求的同时改变Button的状态短时间内让其不可被点击
  • 网络请求失败、或者根据返回数据业务码等情况进行重试操作
  • Switch控件怎么防止频发的触发网络请求
  • 列表分页数据怎么进行预请求优化

很多的优化要根据实际的场景和业务需求来实现,有的部分目前还未涉及,慢慢再补充吧

相关推荐
小彭努力中4 分钟前
205.Vue3 + OpenLayers:加载动画,采用 CSS 的 @keyframes 方式
前端·css·vue.js·openlayers·cesium·webgis
xmdy58666 分钟前
Flutter + 开源鸿蒙实战|城市智慧停车管理系统 Day1 项目初始化+架构搭建+全局依赖集成+多端适配基座
flutter·开源·harmonyos
木斯佳7 分钟前
前端八股文面经大全:上海威派格前端实习(2026-05-07)·面经深度解析
前端
_Twink1e9 分钟前
基于Vue的纯前端的库存销售系统
前端·vue.js·vue·web
幽络源小助理16 分钟前
音频在线剪切助手网页版源码 – 纯前端HTML单文件免费分享
前端·音视频
陈振wx:zchen200816 分钟前
前端-面试题-Vue
前端·vue.js
计算机安禾18 分钟前
【c++面向对象编程】第5篇:类与对象(四):赋值运算符重载
java·前端·c++
Moment19 分钟前
从 beginWork 到 completeWork,Fiber 树是怎么“盖”出来的❓❓❓
前端·javascript·面试
Java面试题总结21 分钟前
.NET 8 Web开发入门(三):解构引擎——依赖注入(DI)与中间件管道
前端·中间件·.net
不会写DN24 分钟前
为什么需要 @types/react? 解决“无法找到模块 react 的声明文件”报错
前端·react.js·前端框架