之前在用 Flutter 开发 Demo,在使用 Dio 请求网络的过程中遇到的一些问题,结合最近在掘金上看到一个不错的文章,于是就把自己的问题拿出来记录一下,看看能不能把之前的优化方案再升级一下
问题背景
技术背景/技术选型:
类别 | 第三方库 |
---|---|
状态管理 | riverpod |
网络请求 | Dio + retrofit |
依赖注入 | injectable + get_it |
本地存储 | floor + shared_preferences + hive |
UI 组件库 | bruno |
injectable 依赖 get_it 库来实现依赖注入,整体使用起来跟做 Android 开发时使用的 MVVM 架构很类似
问题描述:
首先是一个 PageView,当页面切换到目标页时会触发数据请求,在数据请求下来前会先显示一个骨架屏,当在请求到数据前频繁切换 PageView 的页面时会遇到报错:
1 | Error: Bad state: Tried to use RestaurantsController after `dispose` was called. |
大致的意思是在StateNotifier
里在数据请求回来后设置到state
的时候,这时 StateNotifier 被dispose
了,强行设置数据到state
会报这个错误,按照Error
信息处理下就不会报这个错了
1 | final Data result = await // Do Request; |
但是还有另外一个问题,就是页面多次来回切换以后,UI 上会表现得有些卡顿,这是因为频繁的请求 Api 会往Event
队列里多次加入相同的Future
,Event
和Microtask
队列里的任务过多会影响 UI 性能,所以要做一些优化。
这里的
StateNotifier
是 riverpod 2.0 版本 里的提供的
解决方案
要解决这个问题目前用以下两个方法:
- 在页面关闭时,取消所有正在进行中的请求(虽然取消请求在
Dio
层面只是丢弃当前请求,对相应返回数据不再做处理,节省了数据的读写和解析的时间) - 把第一次请求成功的数据缓存起来,后面在遇到相同的请求就从缓存里读取,代价比从网络请求使用的资源少(适用于返回数据变动不频繁的
GET
接口,局限性较高)
好在目前我正在写的模块用到的接口返回数据都是不怎么变动的GET
接口,方法 2 还在进行中,方法 1 的实现如下:
1 | // retrofit下定义的api_service.dart |
该方案目前需要对应的接口额外提供一个requestKey
和在Controller
层里调用cancelRequest
方法,这里增加requestKey
的想法是本地缓存也可以用到相同的requestKey
,并且在repository
层还可以增加缓存过期以及内存缓存的逻辑。
优化方案思考
比如说把Map
缓存CancelToken
、取消请求、本地缓存和requestKey
的生成等逻辑放到Dio
的拦截器里,在拦截器层面实现网络请求的防抖
和节流
,方法就是把配置的参数传递过去,根据配置参数来执行一些操作。
缺点是随着配置参数的增多,根据配置参数来实现的代码也会越来越多,可能会造成拦截器里的代码臃肿
总结
这里的优化场景相对来说比较单一,还有更多场景比如:
- 输入框实时搜索请求网络时在控件层面加
防抖
,实时搜索的返回数据变动较大不适合节流,加入防抖
后在repository
层要先把之前的请求取消掉 - 按钮点击请求网络时加入
节流
,避免同时触发多个请求,或者在触发一次请求的同时改变Button
的状态短时间内让其不可被点击 - 网络请求失败、或者根据返回数据业务码等情况进行重试操作
Switch
控件怎么防止频发的触发网络请求- 列表分页数据怎么进行预请求优化
很多的优化要根据实际的场景和业务需求来实现,有的部分目前还未涉及,慢慢再补充吧