前言
在前文【传送门】的网络请求封装中我们基于DIO的网络请求进行了封装,本身是没什么问题的,但是细想之下,我们并没有处理取消网络的逻辑。
在之前客户端的开发中,例如 Android 的 ViewModel 中我们不管是通过协程的绑定生命周期自动取消,还是使用 RxJava/RxKotlin 的方式手动收集 Disposable 并且在销毁的时候释放,我们都是需要做处理的。
而在 Flutter 中由于我们是基于 Get 框架实现的,那么在 GetxController 中我们并没有处理网络请求的取消,这样就可能会发生内存泄露、占用带宽和系统资源、空指针等。
所以我们需要考虑如何在 GetxController 中实现自动化的 DIO 的 CancelTag 管理。
实现的方式有很多,这里列举三种方式:一种是我们常用的基类封装,另一种是使用扩展方法实现,还有一种是混入实现,下面就分别看看如何实现吧。
一、基类的实现
其实我个人不推荐这种方法,不太优雅,因为可能我们自己封装了一个 BaseController 除了你自己其他人接触到你的项目就是一脸懵,直接改变了原本类,容易让人摸不着头脑。
scala
import 'package:dio/dio.dart';
import 'package:get/get.dart';
class BaseController extends GetxController {
// Dio 实例可以从外部传入,也可以在BaseController中创建
final Dio dio;
// 创建一个 CancelToken 用于取消Dio请求
final CancelToken cancelToken = CancelToken();
BaseController({required this.dio});
@override
void onClose() {
// 当控制器关闭时,取消所有的 Dio 请求
cancelToken.cancel("Controller has been disposed");
super.onClose();
}
// 其他公共方法或属性 ...
}
使用的时候:
scala
class MyController extends BaseController {
MyController({required Dio dio}) : super(dio: dio);
void fetchData() async {
try {
final response = await dio.get(
'https://example.com/data',
cancelToken: cancelToken,
);
// 处理响应 ...
} catch (e) {
if (CancelToken.isCancel(e)) {
print("请求取消:$e");
} else {
// 处理其他错误 ...
}
}
}
// 其他方法 ...
}
此时就是所有的 Controller 都需要继承你自定义的 BaseController,以后有什么逻辑就往 Base 里面加,反正能实现我的需求了。
二、扩展实现
使用扩展可能实现类似的功能,并且没有改变本身的 Controller 是继承与 GetxController 这个对象,让同事或者其他开发者一目了然。
但是缺点是扩展的逻辑不明显,如果你不告诉其他开发者,它甚至都不知道你在里面添加了什么逻辑,如果是 CancelToken 这种无感逻辑还好,如果是一些特定的逻辑或者有一些Bug坑,那真是找都不好找。
java
import 'package:dio/dio.dart';
import 'package:get/get.dart';
extension CancelableGetController on GetxController {
// 关联 CancelToken
CancelToken _cancelToken = CancelToken();
// 获取CancelToken的方法
CancelToken get cancelToken => _cancelToken;
// 取消请求的方法
void cancelRequests() {
_cancelToken.cancel("请求被取消");
}
// 为了确保自动取消网络请求,在类销毁时调用cancelRequests
@override
void onClose() {
cancelRequests();
super.onClose();
}
}
我们使用扩展就无需使用 BaseController 了:
scala
class MyController extends GetxController {
final Dio dio = Dio();
void fetchSomething() async {
try {
final response = await dio.get(
'https://myapi.com/data',
cancelToken: cancelToken, // 使用扩展提供的CancelToken
);
// 处理响应
} catch (e) {
// 错误处理
}
}
}
可以说是使用简单,逻辑无感,适用与一些特定场景。
三、混入实现
使用 mixin 的方式呢,和扩展比较类似,只是可以选装了,比如我这个 SettingController 它没有网络请求,只有一些本地数据处理逻辑,那么我就可以选择不混入 DIO 的 CancelToken 逻辑,如果我是 UserProfileController 这肯定是有网络请求我就可以选择混入DIO 的 CancelToken 逻辑。
可选装是他最大的有点,其他开发者可能一目了然,哦,你这个 Controller 加了一些奇怪的逻辑,他就能点进去看看你的混入逻辑,比较方便调试。
dart
import 'package:dio/dio.dart';
import 'package:get/get.dart';
mixin CancelableMixin on GetxController {
CancelToken _cancelToken = CancelToken();
CancelToken get cancelToken => _cancelToken;
@override
void onClose() {
_cancelToken.cancel("请求被取消");
super.onClose();
}
}
使用的时候:
scala
class MyController extends GetxController with CancelableMixin {
final Dio dio = Dio();
void fetchSomething() async {
try {
final response = await dio.get(
'https://myapi.com/data',
cancelToken: cancelToken,
);
// 处理响应
} catch (e) {
// 错误处理
}
}
}
总结
三种方式都可以实现类似的效果,如果你就像使用基类完全没问题,如果你想隐藏你的业务逻辑使用扩展的方法很难被人发现,如果你想做成可配置的效果,更加极限的内存优化,那么使用混入的方式比较适合你。
什么?我用的哪一种?我用的混入方式,不过我在魔改 GetxController 其他方式也用到了扩展方法,对于一些必须存在的自定义且无感的逻辑我还是喜欢用扩展的方式,如果是可用可不用这种配置选项我更喜欢用混入的方式。
那么本期内容就到这里,如讲的不到位或错漏的地方,希望同学们可以评论区指出。
本文的代码已经全部贴出,部分没贴出的代码可以在前文中找到,我的 Flutter Demo 项目正在整理中,后期开源了会更新文章链接。
如果感觉本文对你有一点点的启发,还望你能点赞
支持一下,你的支持是我最大的动力啦!
Ok,这一期就此完结。