[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队列里多次加入相同的Future,Event和Microtask队列里的任务过多会影响 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控件怎么防止频发的触发网络请求
  • 列表分页数据怎么进行预请求优化

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

相关推荐
淡淡的香烟1 小时前
Flutter实现多语言切换
android·flutter·1024程序员节
计算机魔术师1 小时前
管谷歌的不是Pichai一个人,是一整层AI智能体
前端
米米商聊1 小时前
图标能点,按钮就可用了吗?键盘、名称与收藏状态
前端·javascript
Younglina2 小时前
三个晚上,我和 WorkBuddy 做了个生图网站
前端·后端·ai编程
xy34532 小时前
Axure 9.0 内联框架加载方式
前端·ui·html·axure·原型·产品设计
陈随易3 小时前
副业搞钱点子之收入查看器
前端·后端
Profile排查笔记3 小时前
浏览器指纹逆向怎么分析:从网络请求追踪到 JavaScript 采集链
前端·人工智能·后端·自动化
hudie7654 小时前
LikeShop 二开最常改的 5 个文件(附改前改后对比)
前端·uni-app·php·个人开发
qetfw4 小时前
Bash 函数与流程控制:read、if、for、case 验证
linux·前端·javascript·bash
鬼手点金5 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节