Flutter之Dio封装+实例(自己梳理)

参考链接

https://github.com/cfug/dio/blob/main/dio/README-ZH.md

添加依赖

 手动添加到pubspec.yaml:

dependencies:
  dio: ^替换为最新版本

在终端使用以下命令:

$ dart pub add dio

Dio

dio 是一个强大的 HTTP 网络请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。

 使用单例模式封装网络工具类

单例模式详见:Flutter之单例模式的四种方法_YUFENGSHI.LJ的博客-CSDN博客

class HttpManager{

  //1、通过静态方法 getInstance() 访问实例—————— getInstance() 构造、获取、返回实例
  /*通过工厂方法获取该类的实例,将实例对象按对应的方法返回出去
   *实例不存在时,调用命名构造方法获取一个新的实例 */
  static HttpManager getInstance(){
    if(_instance==null){
      _instance=HttpManager._internal();
    }
    return _instance!;
  }

  //2、静态属性——该类的实例
  static HttpManager? _instance=HttpManager._internal();

  //3、私有的命名构造函数,确保外部不能拿到它————初始化实例
  HttpManager._internal(){}

  //4.1、创建一个 Dio 实例
  late Dio dio;

创建一个Dio实例,并初始化

可以使用默认配置或传递一个可选 BaseOptions参数来创建一个Dio实例

请求配置

BaseOptions 描述的是 Dio 实例发起网络请求的的公共配置,而 Options 描述了每一个Http请求的配置信息,每一次请求都可以单独配置。

单次请求的 Options 中的配置信息可以覆盖 BaseOptions 中的配置。

BaseOptions :

基类请求配置

//请求方式
String? method,

//连接超时时间
Duration? connectTimeout,

//接收超时
Duration? receiveTimeout,

//发送超时
Duration? sendTimeout,

//基本网址
String baseUrl = '',

//请求包头
Map<String, dynamic>? headers,

//以何种方式接收响应数据,默认是json
ResponseType? responseType = ResponseType.json,

//内容类型
String? contentType,

Map<String, dynamic>? queryParameters,

Map<String, dynamic>? extra,   

ValidateStatus? validateStatus,

bool? receiveDataWhenStatusError,

bool? followRedirects,

int? maxRedirects,

bool? persistentConnection,

RequestEncoder? requestEncoder,

ResponseDecoder? responseDecoder,

ListFormat? listFormat,

Options : 

单次请求配置

/// 请求方式。
String method;

/// 请求基本地址,可以包含路径例如 https://dart.dev/api/。
String? baseUrl;

/// HTTP 请求头。
Map<String, dynamic>? headers;

/// 连接服务器超时时间.
Duration? connectTimeout;

/// 两次数据流数据接收的最长间隔时间,注意不是请求的最长接收时间。
Duration? receiveTimeout;

/// 请求内容体,可以是任意类型。
dynamic data;

/// 请求路径,如果以 http(s)开始, 则 [baseURL] 会被忽略,
/// 否则将会和 [baseUrl] 拼接出完整的地址。
String path = '';

/// 请求的 Content-Type。
///
/// 默认值会由 [ImplyContentTypeInterceptor] 根据请求载荷类型进行推导。
/// 可以调用 [Interceptors.removeImplyContentTypeInterceptor] 进行移除。
///
/// 如果你想以 `application/x-www-form-urlencoded` 格式编码请求数据,
/// 可以设置此选项为 `Headers.formUrlEncodedContentType`,
/// [Dio] 会自动编码请求体。
String? contentType;

/// 期望以哪种格式(方式)接受响应数据,包括 `json`、`stream` 和 `plain`。
///
/// 默认值是 `json`, 当响应头中 content-type 为 `application/json` 时,
/// dio 会自动将响应内容转化为 json 对象。
/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `stream`。
///
/// 如果想以文本(字符串)格式接收响应数据,请使用 `plain`。
ResponseType? responseType;

/// `validateStatus` 决定 HTTP 响应状态码是否被视为请求成功,
/// 返回 `true` 请求结果就会按成功处理,否则会按失败处理.
ValidateStatus? validateStatus;

/// 用户自定义字段,可以在 [Interceptor]、[Transformer] 和 [Response] 中依次传递。
Map<String, dynamic>? extra;

/// 请求地址的参数。
Map<String, dynamic /*String|Iterable<String>*/ >? queryParameters;

/// 请求数据中数组的编码的方式,默认值为 `multiCompatible`。
ListFormat? listFormat;

RequestOptions:

实际请求配置,即[BaseOptions]和[Options]组合后的最终结果。 

/// The internal request option class that is the eventual result after
/// [BaseOptions] and [Options] are composed.
class RequestOptions extends _RequestConfig with OptionsMixin {
  RequestOptions({
    this.path = '',
    this.data,
    this.onReceiveProgress,
    this.onSendProgress,
    this.cancelToken,
    String? method,
    Duration? sendTimeout,
    Duration? receiveTimeout,
    Duration? connectTimeout,
    Map<String, dynamic>? queryParameters,
    String? baseUrl,
    Map<String, dynamic>? extra,
    Map<String, dynamic>? headers,
    ResponseType? responseType,
    String? contentType,
    ValidateStatus? validateStatus,
    bool? receiveDataWhenStatusError,
    bool? followRedirects,
    int? maxRedirects,
    bool? persistentConnection,
    RequestEncoder? requestEncoder,
    ResponseDecoder? responseDecoder,
    ListFormat? listFormat,
    bool? setRequestContentTypeWhenNoPayload,
    StackTrace? sourceStackTrace,
  })  : assert(connectTimeout == null || !connectTimeout.isNegative),
        super(
          method: method,
          sendTimeout: sendTimeout,
          receiveTimeout: receiveTimeout,
          extra: extra,
          headers: headers,
          responseType: responseType,
          contentType: contentType,
          validateStatus: validateStatus,
          receiveDataWhenStatusError: receiveDataWhenStatusError,
          followRedirects: followRedirects,
          maxRedirects: maxRedirects,
          persistentConnection: persistentConnection,
          requestEncoder: requestEncoder,
          responseDecoder: responseDecoder,
          listFormat: listFormat,
        ) {
    this.sourceStackTrace = sourceStackTrace ?? StackTrace.current;
    this.queryParameters = queryParameters ?? {};
    this.baseUrl = baseUrl ?? '';
    this.connectTimeout = connectTimeout;
  }

  /// Create a [RequestOptions] from current instance with merged attributes.
  RequestOptions copyWith({
    String? method,
    Duration? sendTimeout,
    Duration? receiveTimeout,
    Duration? connectTimeout,
    dynamic data,
    String? path,
    Map<String, dynamic>? queryParameters,
    String? baseUrl,
    ProgressCallback? onReceiveProgress,
    ProgressCallback? onSendProgress,
    CancelToken? cancelToken,
    Map<String, dynamic>? extra,
    Map<String, dynamic>? headers,
    ResponseType? responseType,
    String? contentType,
    ValidateStatus? validateStatus,
    bool? receiveDataWhenStatusError,
    bool? followRedirects,
    int? maxRedirects,
    bool? persistentConnection,
    RequestEncoder? requestEncoder,
    ResponseDecoder? responseDecoder,
    ListFormat? listFormat,
    bool? setRequestContentTypeWhenNoPayload,
  }) {
    final contentTypeInHeader = headers != null &&
        headers.keys
            .map((e) => e.toLowerCase())
            .contains(Headers.contentTypeHeader);

    assert(
      !(contentType != null && contentTypeInHeader),
      'You cannot set both contentType param and a content-type header',
    );

    final ro = RequestOptions(
      method: method ?? this.method,
      sendTimeout: sendTimeout ?? this.sendTimeout,
      receiveTimeout: receiveTimeout ?? this.receiveTimeout,
      connectTimeout: connectTimeout ?? this.connectTimeout,
      data: data ?? this.data,
      path: path ?? this.path,
      baseUrl: baseUrl ?? this.baseUrl,
      queryParameters: queryParameters ?? Map.from(this.queryParameters),
      onReceiveProgress: onReceiveProgress ?? this.onReceiveProgress,
      onSendProgress: onSendProgress ?? this.onSendProgress,
      cancelToken: cancelToken ?? this.cancelToken,
      extra: extra ?? Map.from(this.extra),
      headers: headers ?? Map.from(this.headers),
      responseType: responseType ?? this.responseType,
      validateStatus: validateStatus ?? this.validateStatus,
      receiveDataWhenStatusError:
          receiveDataWhenStatusError ?? this.receiveDataWhenStatusError,
      followRedirects: followRedirects ?? this.followRedirects,
      maxRedirects: maxRedirects ?? this.maxRedirects,
      persistentConnection: persistentConnection ?? this.persistentConnection,
      requestEncoder: requestEncoder ?? this.requestEncoder,
      responseDecoder: responseDecoder ?? this.responseDecoder,
      listFormat: listFormat ?? this.listFormat,
      sourceStackTrace: sourceStackTrace,
    );

    if (contentType != null) {
      ro.headers.remove(Headers.contentTypeHeader);
      ro.contentType = contentType;
    } else if (!contentTypeInHeader) {
      ro.contentType = this.contentType;
    }

    return ro;
  }

  /// The source [StackTrace] which should always point to the invocation of
  /// [DioMixin.request] or if not provided, to the construction of the
  /// [RequestOptions] instance. In both instances the source context should
  /// still be available before it is lost due to asynchronous operations.
  @internal
  StackTrace? sourceStackTrace;

  /// Generate the requesting [Uri] from the options.
  Uri get uri {
    String url = path;
    if (!url.startsWith(RegExp(r'https?:'))) {
      url = baseUrl + url;
      final s = url.split(':/');
      if (s.length == 2) {
        url = '${s[0]}:/${s[1].replaceAll('//', '/')}';
      }
    }
    final query = Transformer.urlEncodeQueryMap(queryParameters, listFormat);
    if (query.isNotEmpty) {
      url += (url.contains('?') ? '&' : '?') + query;
    }
    // Normalize the url.
    return Uri.parse(url).normalizePath();
  }

  /// Request data in dynamic types.
  dynamic data;

  /// Defines the path of the request. If it starts with "http(s)",
  /// [baseUrl] will be ignored. Otherwise, it will be combined and resolved
  /// with the [baseUrl].
  String path;

  /// {@macro dio.CancelToken}
  CancelToken? cancelToken;

  /// {@macro dio.options.ProgressCallback}
  ProgressCallback? onReceiveProgress;

  /// {@macro dio.options.ProgressCallback}
  ProgressCallback? onSendProgress;
}

初始化DIO 

 /*在私有构造方法中,在里面可以进行初始化dio实例*/
  HttpManager._internal(){
    //4.2、设置BaseOptions
    BaseOptions baseOptions=BaseOptions(
      //基本网址
      baseUrl:"https://lionstock-uat-new.chinaeast2.cloudapp.chinacloudapi.cn:8200/",
      //连接超时
      connectTimeout: Duration(milliseconds: 30000),
      //接收超时
      receiveTimeout: Duration(milliseconds: 5000),
      //包头
      headers: {
        "Content-Type": "application/json;Charset=UTF-8",
        "connect":"get"
      },
      //内容类型
      contentType: 'application/json;Charset=UTF-8',
      //响应类型——期待已那种方式接收数据,默认是json
      responseType: ResponseType.json,
    );
    //4.3 初始化dio实例
    dio=new Dio(baseOptions) ;
    //添加一个拦截器
    dio.interceptors.add(new DioLogInterceptor());
  }

响应数据 Response

当请求成功时会返回一个Response对象,它包含如下字段: 

/// 响应数据。可能已经被转换了类型, 详情请参考 [ResponseType]。
T? data;

/// 响应对应的实际请求配置。
RequestOptions requestOptions;

/// 响应的 HTTP 状态码。
int? statusCode;

/// 响应对应状态码的详情信息。
String? statusMessage;

/// 响应是否被重定向
bool isRedirect;

/// 请求连接经过的重定向列表。如果请求未经过重定向,则列表为空。
List<RedirectRecord> redirects;

/// 在 [RequestOptions] 中构造的自定义字段。
Map<String, dynamic> extra;

/// 响应对应的头数据(响应头)
Headers headers;

Get方法

get方法中只有路径是必填的,

Future<Response<T>> get<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  });

get方法使用指定的路径和查询参数向服务器发送 HTTP GET 请求。它还允许您传递其他选项,例如标头、响应类型和超时。该方法返回一个 Future 对象,该对象使用包含来自服务器的 HTTP 响应的 Response 对象进行解析。

封装get方法

get(String url,{option,params}) async {
    Response response;
    try{
      response=await dio.get(url,options: Options(responseType: ResponseType.json));
      print("response.data:${response.data}");
      print("response.data:${response.statusCode}");
      print("response.data:${response.statusMessage}");
      print("response.data:${response.headers}");

    }
    on Exception catch(e){
      print("Get方法出错:${e.toString()}");
    }

  }

get请求无参数

 接口:https://api.github.com/orgs/flutterchina/repos 

HttpManager.getInstance().get(
     "https://reqres.in/api/users",
     option: Options(responseType: ResponseType.plain),
  );

 get请求有参数

在请求链接中拼接参数

   // 获取id 法一
   HttpManager.getInstance().get("https://reqres.in/api/users/1");

   // 获取id 法二
   HttpManager.getInstance().get("https://reqres.in/api/users?id=2");

通过 queryParameters 配制参数 

   // 获取id 法三 用参数类型
   Map<String,dynamic> map = Map();
   map["id"]= 3;
   HttpManager.getInstance().get(
     "https://reqres.in/api/users",
     option: Options(responseType: ResponseType.json),
     params:map
   );

 Post方法 

在发出 GET 请求时,我们通常不传递任何数据。但是当发出 POST、PUT、DELETE 等请求时,我们需要传递正文/数据。

post方法与方法类似get,但增加了一个data参数,该参数代表请求正文。使用该方法获取请求标头getAuthorizationHeader并与提供的任何选项合并。使用发出请求dio.post,如果成功则返回响应数据。如果请求由于 Dio 错误而失败,ErrorEntity则会抛出异常。

封装post方法

post(api,{params}) async {
    Response response;
    //请求参数 为空时,配置
    if(params==null){
      params["marketNo"] = "PC_Flutter";
      params["versionNo"] = '10105';/*版本号*/
      params["token"] = '6b2fc908787c428ab16559fce9d86bf2';
      params["uid"] = '201323';
    }

    try{
      response=await dio.post(
        api,
        queryParameters: params,
      );
      print("post response:${response.data}\n");
    }
    on Exception catch (e){
     print("post出错:${e.toString()}");
    }

  }

利用post方法注册一个用户

HttpManager.getInstance().post(
       "https://www.wanandroid.com/user/register",
     params: {
          "username": "zsdhwiehfwo",
          "password": "123456",
          "repassword": "123456"}
   );

打印结果: 

post response:{data: {admin: false, chapterTops: [], coinCount: 0, collectIds: [], email: , icon: , id: 151550, nickname: zsdhwiehfwo, password: , publicName: zsdhwiehfwo, token: , type: 0, username: zsdhwiehfwo}, errorCode: 0, errorMsg: }

拦截器

每个 Dio 实例都可以添加任意多个拦截器,他们会组成一个队列,拦截器队列的执行顺序是先进先出。 通过使用拦截器,你可以在请求之前、响应之后和发生异常时(未被 then 或 catchError 处理) 做一些统一的预处理操作。

拦截器处理器

handler.next

/// Deliver the [response] to the next interceptor.
///
/// Typically, the method should be called once interceptors done
/// manipulating the [response].
///将[响应]传递给下一个拦截器。通常,一旦拦截器完成操作[响应],就应该调用该方法。
  void next(Response response) {
    _completer.complete(
      InterceptorState<Response>(response),
    );
    _processNextInQueue?.call();
  }

如果不调用handler.next(response)方法,那么请求将被中止,也就是说后续的拦截器和回调函数将不会被执行。 

请求前 onRequest 

/// Called when the request is about to be sent.
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) {
    handler.next(options);
  }
  • RequestOptions options,表示本次请求的实际配置

  • RequestInterceptorHandler是一个拦截器处理器,用于处理请求拦截器中的逻辑。

/// The handler for interceptors to handle after respond.
class ResponseInterceptorHandler extends _BaseHandler {
  /// Deliver the [response] to the next interceptor.
  ///
  /// Typically, the method should be called once interceptors done
  /// manipulating the [response].
  void next(Response response) {
    _completer.complete(
      InterceptorState<Response>(response),
    );
    _processNextInQueue?.call();
  }

  /// Completes the request by resolves the [response] as the result.
  void resolve(Response response) {
    _completer.complete(
      InterceptorState<Response>(
        response,
        InterceptorResultType.resolve,
      ),
    );
    _processNextInQueue?.call();
  }

  /// Completes the request by reject with the [error] as the result.
  ///
  /// Invoking the method will make the rest of interceptors in the queue
  /// skipped to handle the request,
  /// unless [callFollowingErrorInterceptor] is true
  /// which delivers [InterceptorResultType.rejectCallFollowing]
  /// to the [InterceptorState].
  void reject(DioException error,
      [bool callFollowingErrorInterceptor = false]) {
    _completer.completeError(
      InterceptorState<DioException>(
        error,
        callFollowingErrorInterceptor
            ? InterceptorResultType.rejectCallFollowing
            : InterceptorResultType.reject,
      ),
      error.stackTrace,
    );
    _processNextInQueue?.call();
  }
}
  • handler.next(options)方法将处理后的请求传递给下一个拦截器或者最终的请求回调函数。如果不调用handler.next(options)方法,那么请求将被中止,也就是说后续的拦截器和回调函数将不会被执行。
  • handler.resolve(options)表示直接将请求返回给请求回调函数
  • handler.reject(error)表示将错误信息返回给请求回调函数等

响应前 onResponse

/// Called when the response is about to be resolved.
///当响应即将解决时调用。
  void onResponse(
    Response response,
    ResponseInterceptorHandler handler,
  ) {
    handler.next(response);
  }
  • response表示响应数据,包括响应状态码、响应头、响应数据等

  • ResponseInterceptorHandler是一个拦截器处理器,用于处理响应拦截器中的逻辑。

/// The handler for interceptors to handle after respond.
class ResponseInterceptorHandler extends _BaseHandler {
  /// Deliver the [response] to the next interceptor.
  ///
  /// Typically, the method should be called once interceptors done
  /// manipulating the [response].
  void next(Response response) {
    _completer.complete(
      InterceptorState<Response>(response),
    );
    _processNextInQueue?.call();
  }

  /// Completes the request by resolves the [response] as the result.
  void resolve(Response response) {
    _completer.complete(
      InterceptorState<Response>(
        response,
        InterceptorResultType.resolve,
      ),
    );
    _processNextInQueue?.call();
  }

  /// Completes the request by reject with the [error] as the result.
  ///
  /// Invoking the method will make the rest of interceptors in the queue
  /// skipped to handle the request,
  /// unless [callFollowingErrorInterceptor] is true
  /// which delivers [InterceptorResultType.rejectCallFollowing]
  /// to the [InterceptorState].
  void reject(DioException error,
      [bool callFollowingErrorInterceptor = false]) {
    _completer.completeError(
      InterceptorState<DioException>(
        error,
        callFollowingErrorInterceptor
            ? InterceptorResultType.rejectCallFollowing
            : InterceptorResultType.reject,
      ),
      error.stackTrace,
    );
    _processNextInQueue?.call();
  }
}
  • 在响应拦截器中,我们可以通过 handler.next(response) 方法将响应传递给下一个拦截器或者最终的请求回调函数。如果不调用handler.next(response)方法,那么请求将被中止,也就是说后续的拦截器和回调函数将不会被执行。
  • handler.resolve(response)表示直接将响应返回给请求回调函数
  • handler.reject(error)表示将错误信息返回给请求回调函数等。

异常时 onError

/// Called when an exception was occurred during the request.
///当请求过程中发生异常时调用。
  void onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) {
    handler.next(err);
  }
  • DioException 表示异常信息,包括错误类型、错误消息、错误堆栈等信息;
  • ErrorInterceptorHandler 拦截器的处理程序用于处理请求期间发生的错误。 

自定义拦截器:

class DioLogInterceptor extends Interceptor{

  ///请求前
  @override
  Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    String requestStr = "\n==================== 请求前拦截——REQUEST ====================\n"
        "- URL:\n${options.baseUrl + options.path}\n"
        "- METHOD: ${options.method}\n";


    requestStr += "- HEADER:\n${options.headers.mapToStructureString()}\n";

    final data = options.data;
    if (data != null) {
      if (data is Map)
        requestStr += "- BODY:\n${data.mapToStructureString()}\n";
      else if (data is FormData) {
        final formDataMap = Map()..addEntries(data.fields)..addEntries(data.files);
        requestStr += "- BODY:\n${formDataMap.mapToStructureString()}\n";
      } else
        requestStr += "- BODY:\n${data.toString()}\n";
    }
    print(requestStr);
    return handler.next(options);
  }

}

添加拦截器

//添加一个拦截器
    dio.interceptors.add(new DioLogInterceptor());

错误处理

 当请求过程中发生错误时, Dio 会将 Error/Exception 包装成一个 DioException:

猜你喜欢

转载自blog.csdn.net/weixin_43244083/article/details/132016475
今日推荐