前不久看到 艾维码 大佬的dio封装,通过摸索,改吧改吧,应用的不错。对于之前 艾维码 大佬文章中一些曾经生效的做了修改
为什么肯定要封装一手?
token拦挡,谬误拦挡,对立错误处理,对立缓存,信息封装(谬误,正确)
Cookie???滚犊子
不论cookie,再见
全局初始化,传入参数
dio初始化,传入baseUrl, connectTimeout, receiveTimeout,options,header 拦截器等。dio初始化的时候容许咱们传入的一些配置
dio初始化的配置
这里说下,之前 艾维码 大佬的帖子中的options,最新版的dio曾经应用requestOptions, 之前的merge,当初应用copyWith。详情向下看
如果要白嫖残缺的计划
能够参考应用这套计划开发的 flutter + getx 仿开眼视频app,有star的大佬能够赏点star。
我的项目地址 github地址
apk下载 panbaidu 提取码:3ev2
初始化
这里说下拦截器,能够在初始化的时候传入,也能够手写传入,例如我这里定义了四个拦截器,第一个用于全局request时候给申请投加上context-type:json。第二个是全局错误处理拦截器,上面的内容会介绍拦截器局部。
cache拦截器,全局解决接口缓存数据,retry重试拦截器(我临时没怎么用)
class Http { static final Http _instance = Http._internal(); // 单例模式应用Http类, factory Http() => _instance; static late final Dio dio; CancelToken _cancelToken = new CancelToken(); Http._internal() { // BaseOptions、Options、RequestOptions 都能够配置参数,优先级别顺次递增,且能够依据优先级别笼罩参数 BaseOptions options = new BaseOptions(); dio = Dio(options); // 增加request拦截器 dio.interceptors.add(RequestInterceptor()); // 增加error拦截器 dio.interceptors.add(ErrorInterceptor()); // // 增加cache拦截器 dio.interceptors.add(NetCacheInterceptor()); // // 增加retry拦截器 dio.interceptors.add( RetryOnConnectionChangeInterceptor( requestRetrier: DioConnectivityRequestRetrier( dio: dio, connectivity: Connectivity(), ), ), ); // 在调试模式下须要抓包调试,所以咱们应用代理,并禁用HTTPS证书校验 // if (PROXY_ENABLE) { // (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = // (client) { // client.findProxy = (uri) { // return "PROXY $PROXY_IP:$PROXY_PORT"; // }; // //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以咱们禁用证书校验 // client.badCertificateCallback = // (X509Certificate cert, String host, int port) => true; // }; // } } ///初始化公共属性 /// /// [baseUrl] 地址前缀 /// [connectTimeout] 连贯超时赶时间 /// [receiveTimeout] 接管超时赶时间 /// [interceptors] 根底拦截器 void init({ String? baseUrl, int connectTimeout = 1500, int receiveTimeout = 1500, Map<String, String>? headers, List<Interceptor>? interceptors, }) { dio.options = dio.options.copyWith( baseUrl: baseUrl, connectTimeout: connectTimeout, receiveTimeout: receiveTimeout, headers: headers ?? const {}, ); // 在初始化http类的时候,能够传入拦截器 if (interceptors != null && interceptors.isNotEmpty) { dio.interceptors..addAll(interceptors); } } // 敞开dio void cancelRequests({required CancelToken token}) { _cancelToken.cancel("cancelled"); } // 增加认证 // 读取本地配置 Map<String, dynamic>? getAuthorizationHeader() { Map<String, dynamic>? headers; // 从getx或者sputils中获取 // String accessToken = Global.accessToken; String accessToken = ""; if (accessToken != null) { headers = { 'Authorization': 'Bearer $accessToken', }; } return headers; } Future get( String path, { Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, bool refresh = false, bool noCache = !CACHE_ENABLE, String? cacheKey, bool cacheDisk = false, }) async { Options requestOptions = options ?? Options(); requestOptions = requestOptions.copyWith( extra: { "refresh": refresh, "noCache": noCache, "cacheKey": cacheKey, "cacheDisk": cacheDisk, }, ); Map<String, dynamic>? _authorization = getAuthorizationHeader(); if (_authorization != null) { requestOptions = requestOptions.copyWith(headers: _authorization); } Response response; response = await dio.get( path, queryParameters: params, options: requestOptions, cancelToken: cancelToken ?? _cancelToken, ); return response.data; } Future post( String path, { Map<String, dynamic>? params, data, Options? options, CancelToken? cancelToken, }) async { Options requestOptions = options ?? Options(); Map<String, dynamic>? _authorization = getAuthorizationHeader(); if (_authorization != null) { requestOptions = requestOptions.copyWith(headers: _authorization); } var response = await dio.post( path, data: data, queryParameters: params, options: requestOptions, cancelToken: cancelToken ?? _cancelToken, ); return response.data; } Future put( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { Options requestOptions = options ?? Options(); Map<String, dynamic>? _authorization = getAuthorizationHeader(); if (_authorization != null) { requestOptions = requestOptions.copyWith(headers: _authorization); } var response = await dio.put( path, data: data, queryParameters: params, options: requestOptions, cancelToken: cancelToken ?? _cancelToken, ); return response.data; } Future patch( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { Options requestOptions = options ?? Options(); Map<String, dynamic>? _authorization = getAuthorizationHeader(); if (_authorization != null) { requestOptions = requestOptions.copyWith(headers: _authorization); } var response = await dio.patch( path, data: data, queryParameters: params, options: requestOptions, cancelToken: cancelToken ?? _cancelToken, ); return response.data; } Future delete( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { Options requestOptions = options ?? Options(); Map<String, dynamic>? _authorization = getAuthorizationHeader(); if (_authorization != null) { requestOptions = requestOptions.copyWith(headers: _authorization); } var response = await dio.delete( path, data: data, queryParameters: params, options: requestOptions, cancelToken: cancelToken ?? _cancelToken, ); return response.data; } }
dio拦截器
上面咱们来看下拦截器,上面是一个解决解决拦截器案例
// 这里是一个我独自写得soket谬误实例,因为dio默认生成的是不容许批改message内容的,我只能自定义一个应用 class MyDioSocketException extends SocketException { late String message; MyDioSocketException( message, { osError, address, port, }) : super( message, osError: osError, address: address, port: port, ); } /// 错误处理拦截器 class ErrorInterceptor extends Interceptor { // 是否有网 Future<bool> isConnected() async { var connectivityResult = await (Connectivity().checkConnectivity()); return connectivityResult != ConnectivityResult.none; } @override Future<void> onError(DioError err, ErrorInterceptorHandler errCb) async { // 自定义一个socket实例,因为dio原生的实例,message属于是只读的 // 这里是我独自加的,因为默认的dio err实例,的几种类型,短少无网络状况下的谬误提示信息 // 这里我手动做解决,来加工一手,成果,看上面的图片,你就晓得 if (err.error is SocketException) { err.error = MyDioSocketException( err.message, osError: err.error?.osError, address: err.error?.address, port: err.error?.port, ); } // dio默认的谬误实例,如果是没有网络,只能失去一个未知谬误,无奈精准的得悉是否是无网络的状况 if (err.type == DioErrorType.other) { bool isConnectNetWork = await isConnected(); if (!isConnectNetWork && err.error is MyDioSocketException) { err.error.message = "以后网络不可用,请查看您的网络"; } } // error对立解决 AppException appException = AppException.create(err); // 谬误提醒 debugPrint('DioError===: ${appException.toString()}'); err.error = appException; return super.onError(err, errCb); } }
以上的代码能够看到,ErrorInterceptor类继承自Interceptor,能够从新onRequest 、onResponse、onError,三个状态,最初return super.onError将err实例传递给超类。
对立的错误信息包装解决
试想一下,如果你的我的项目,有十几种状态码,每种也也都须要吧code码转换成文字信息,因为有时候你须要给用户提醒。例如: 连贯超时,申请失败,网络谬误,等等。上面是对立的错误处理
AppException.dart
import 'package:dio/dio.dart';/// 自定义异样class AppException implements Exception { final String _message; final int _code; AppException( this._code, this._message, ); String toString() { return "$_code$_message"; } String getMessage() { return _message; } factory AppException.create(DioError error) { switch (error.type) { case DioErrorType.cancel: { return BadRequestException(-1, "申请勾销"); } case DioErrorType.connectTimeout: { return BadRequestException(-1, "连贯超时"); } case DioErrorType.sendTimeout: { return BadRequestException(-1, "申请超时"); } case DioErrorType.receiveTimeout: { return BadRequestException(-1, "响应超时"); } case DioErrorType.response: { try { int? errCode = error.response!.statusCode; // String errMsg = error.response.statusMessage; // return ErrorEntity(code: errCode, message: errMsg); switch (errCode) { case 400: { return BadRequestException(errCode!, "申请语法错误"); } case 401: { return UnauthorisedException(errCode!, "没有权限"); } case 403: { return UnauthorisedException(errCode!, "服务器拒绝执行"); } case 404: { return UnauthorisedException(errCode!, "无奈连贯服务器"); } case 405: { return UnauthorisedException(errCode!, "申请办法被禁止"); } case 500: { return UnauthorisedException(errCode!, "服务器外部谬误"); } case 502: { return UnauthorisedException(errCode!, "有效的申请"); } case 503: { return UnauthorisedException(errCode!, "服务器挂了"); } case 505: { return UnauthorisedException(errCode!, "不反对HTTP协定申请"); } default: { // return ErrorEntity(code: errCode, message: "未知谬误"); return AppException(errCode!, error.response!.statusMessage!); } } } on Exception catch (_) { return AppException(-1, "未知谬误"); } } default: { return AppException(-1, error.error.message); } } }}/// 申请谬误class BadRequestException extends AppException { BadRequestException(int code, String message) : super(code, message);}/// 未认证异样class UnauthorisedException extends AppException { UnauthorisedException(int code, String message) : super(code, message);}
应用的时候这样应用,
Future<ApiResponse<Feed>> getFeedData(url) async { try { dynamic response = await HttpUtils.get(url); // print(response); Feed data = Feed.fromJson(response); return ApiResponse.completed(data); } on DioError catch (e) { print(e); // 这里看这里,如果是有谬误的申请下,应用AppException对谬误对象进行解决 // 解决过后,你就能够比方弹个toast,提醒给用户等, // 弹窗toast等在上面的办法中调用 return ApiResponse.error(e.error); } } Future<void> _refresh() async { ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl); // 加工过后,咱们能够取得两个状态,Status.COMPLETED 和 Status.ERROR // 看这里 if (swiperResponse.status == Status.COMPLETED) { // 胜利的代码,想干嘛干嘛 }else if (swiperResponse.status == Status.ERROR) { // 失败的代码,能够给个toast,提醒给用户 // 例如我在这里提醒用户 // 应用 exception!.getMessage(); 取得谬误对象的文字信息,是咱们拦截器解决过后的提醒文字,非英文,拿到这,提醒给用户不香吗???看上面的图片成果 String errMsg = swiperResponse.exception!.getMessage(); publicToast(errMsg); }}
这里的提醒就是自定义err拦截器中减少的代码,对于dio不可能失去是否无网络的补充
磁盘缓存数据,拦截器
磁盘缓存接口数据,首先咱们要封装一个SpUtil类,
sputils.dart
class SpUtil { SpUtil._internal(); static final SpUtil _instance = SpUtil._internal(); factory SpUtil() { return _instance; } SharedPreferences? prefs; Future<void> init() async { prefs = await SharedPreferences.getInstance(); } Future<bool> setJSON(String key, dynamic jsonVal) { String jsonString = jsonEncode(jsonVal); return prefs!.setString(key, jsonString); } dynamic getJSON(String key) { String? jsonString = prefs?.getString(key); return jsonString == null ? null : jsonDecode(jsonString); } Future<bool> setBool(String key, bool val) { return prefs!.setBool(key, val); } bool? getBool(String key) { return prefs!.getBool(key); } Future<bool> remove(String key) { return prefs!.remove(key); }}
缓存拦截器
const int CACHE_MAXAGE = 86400000;const int CACHE_MAXCOUNT = 1000;const bool CACHE_ENABLE = false;class CacheObject { CacheObject(this.response) : timeStamp = DateTime.now().millisecondsSinceEpoch; Response response; int timeStamp; @override bool operator ==(other) { return response.hashCode == other.hashCode; } @override int get hashCode => response.realUri.hashCode;}class NetCacheInterceptor extends Interceptor { // 为确保迭代器程序和对象插入工夫统一程序统一,咱们应用LinkedHashMap var cache = LinkedHashMap<String, CacheObject>(); @override void onRequest( RequestOptions options, RequestInterceptorHandler requestCb, ) async { if (!CACHE_ENABLE) { return super.onRequest(options, requestCb); } // refresh标记是否是刷新缓存 bool refresh = options.extra["refresh"] == true; // 是否磁盘缓存 bool cacheDisk = options.extra["cacheDisk"] == true; // 如果刷新,先删除相干缓存 if (refresh) { // 删除uri雷同的内存缓存 delete(options.uri.toString()); // 删除磁盘缓存 if (cacheDisk) { await SpUtil().remove(options.uri.toString()); } return; } // get 申请,开启缓存 if (options.extra["noCache"] != true && options.method.toLowerCase() == 'get') { String key = options.extra["cacheKey"] ?? options.uri.toString(); // 策略 1 内存缓存优先,2 而后才是磁盘缓存 // 1 内存缓存 var ob = cache[key]; if (ob != null) { //若缓存未过期,则返回缓存内容 if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 < CACHE_MAXAGE) { return; } else { //若已过期则删除缓存,持续向服务器申请 cache.remove(key); } } // 2 磁盘缓存 if (cacheDisk) { var cacheData = SpUtil().getJSON(key); if (cacheData != null) { return; } } } return super.onRequest(options, requestCb); } @override void onResponse( Response response, ResponseInterceptorHandler responseCb) async { // 如果启用缓存,将返回后果保留到缓存 if (CACHE_ENABLE) { await _saveCache(response); } return super.onResponse(response, responseCb); } Future<void> _saveCache(Response object) async { RequestOptions options = object.requestOptions; // 只缓存 get 的申请 if (options.extra["noCache"] != true && options.method.toLowerCase() == "get") { // 策略:内存、磁盘都写缓存 // 缓存key String key = options.extra["cacheKey"] ?? options.uri.toString(); // 磁盘缓存 if (options.extra["cacheDisk"] == true) { await SpUtil().setJSON(key, object.data); } // 内存缓存 // 如果缓存数量超过最大数量限度,则先移除最早的一条记录 if (cache.length == CACHE_MAXCOUNT) { cache.remove(cache[cache.keys.first]); } cache[key] = CacheObject(object); } } void delete(String key) { cache.remove(key); }}
开始封装
class HttpUtils { static void init({ required String baseUrl, int connectTimeout = 1500, int receiveTimeout = 1500, List<Interceptor>? interceptors, }) { Http().init( baseUrl: baseUrl, connectTimeout: connectTimeout, receiveTimeout: receiveTimeout, interceptors: interceptors, ); } static void cancelRequests({required CancelToken token}) { Http().cancelRequests(token: token); } static Future get( String path, { Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, bool refresh = false, bool noCache = !CACHE_ENABLE, String? cacheKey, bool cacheDisk = false, }) async { return await Http().get( path, params: params, options: options, cancelToken: cancelToken, refresh: refresh, noCache: noCache, cacheKey: cacheKey, ); } static Future post( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { return await Http().post( path, data: data, params: params, options: options, cancelToken: cancelToken, ); } static Future put( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { return await Http().put( path, data: data, params: params, options: options, cancelToken: cancelToken, ); } static Future patch( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { return await Http().patch( path, data: data, params: params, options: options, cancelToken: cancelToken, ); } static Future delete( String path, { data, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, }) async { return await Http().delete( path, data: data, params: params, options: options, cancelToken: cancelToken, ); }}
注入,初始化
main。dart。这里参考我集体的应用例子
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // debugPaintSizeEnabled = true; await initStore(); runApp(MyApp());}Future<void> initStore() async { // 初始化本地存储类 await SpUtil().init(); // 初始化request类 HttpUtils.init( baseUrl: Api.baseUrl, ); // 历史记录,全局 getx全局注入, await Get.putAsync(() => HistoryService().init()); print("全局注入");}class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return GetMaterialApp( debugShowCheckedModeBanner: false, initialRoute: PageRoutes.INIT_ROUTER, getPages: PageRoutes.routes, ); }}
应用封装好得例子
// 这里定义一个函数,返回的是future 《apiResponse》,能够失去status的状态Future<ApiResponse<Feed>> getFeedData(url) async { try { dynamic response = await HttpUtils.get(url); // print(response); Feed data = Feed.fromJson(response); return ApiResponse.completed(data); } on DioError catch (e) { print(e); return ApiResponse.error(e.error); } } Future<void> _refresh() async { ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl); if (!mounted) { return; } // 应用 status.COMPLETED 判断是否胜利 if (swiperResponse.status == Status.COMPLETED) { setState(() { nextPageUrl = swiperResponse.data!.nextPageUrl; _swiperList = []; _swiperList.addAll(swiperResponse.data!.issueList![0]!.itemList!); _itemList = []; }); // 拉取新的,列表 await _loading(); // 应用 status.ERROR 判断是否失败 } else if (swiperResponse.status == Status.ERROR) { setState(() { stateCode = 2; }); // 谬误的话,咱们能够调用 getMessage() 获取错误信息。提醒给用户(汉化后的敌对提醒语) String errMsg = swiperResponse.exception!.getMessage(); publicToast(errMsg); print("产生谬误,地位home bottomBar1 swiper, url: ${initPageUrl}"); print(swiperResponse.exception); } }
如果要白嫖残缺的计划
能够参考应用这套计划开发的 flutter + getx 仿开眼视频app,有star的大佬能够赏点star。
我的项目地址 github地址
apk下载 panbaidu 提取码:3ev2