前不久看到 艾维码 大佬的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