Flutter dio 使用 注意事项

Posted -SOLO-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter dio 使用 注意事项相关的知识,希望对你有一定的参考价值。

dio 配置抓包代理

需要通过以下代码才能设置代理。

  //是否开启抓包功能
  static const bool isProxyEnable = true;
  //设置代理服务器地址和端口
  static const String proxy = "192.168.7.134:8888";
  init()
  ...
//配置可以通过Fiddler抓包
    if(isProxyEnable)
      (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) 
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) 
          return isProxyEnable &&DebugModelUtil.isDebugMode;
        ;
        client.findProxy = (url) 
          return  'PROXY $proxy' ;
        ;
      ;
      
     

以上方法使用了一个工具用来判断app在debug模式还是release模式下

import 'package:flutter/foundation.dart';

bool _debug = kDebugMode; //constant下的一个常量

bool _release = kReleaseMode; //constant下的一个常量

///用于判断是否在debug模式下
class DebugModelUtil 
  static bool get isDebugMode 
    return _debug;
  


post请求 参数需要放在data中

错误情况
测试代码如下,如果放在queryParameters 中,将会拼接到url中。

 Map<String, dynamic> params = ;
                    params['name']='zhangshan&a b c';
                    params['age']=24;
                    params['language']='中文';

                     HttpUtil.dio.post(
                        'http://192.168.1.1',
                        queryParameters: params,);

fiddler抓包如下

正确情况

测试代码

					 Map<String, dynamic> params = ;
                    params['name'] = 'zhangshan&a b c';
                    params['age'] = 24;
                    params['language'] = '中文';
                    HttpUtil.dio.post(
                      'http://192.168.1.1',
                      data: params,
                    );

fiddler抓包结果

指定content-type

在默认情况下,dio的content-type使用的是 application/json; charset=utf-8 而要使用其他的content-type。需要如下使用,有两种方法

通过header指定

代码如下

 				 Map<String, dynamic> params = ;
                    params['name'] = 'zhangshan&a b c';
                    params['age'] = 24;
                    params['language'] = '中文';
                    HttpUtil.dio.post('http://192.168.1.1',
                        data: params,
                        options: Options(headers: 
                          Headers.contentTypeHeader:
                              Headers.formUrlEncodedContentType
                        ));

抓包结果如下:

通过content-type指定

代码如下

				 Map<String, dynamic> params = ;
                    params['name'] = 'zhangshan&a b c';
                    params['age'] = 24;
                    params['language'] = '中文';
                    HttpUtil.dio.post('http://192.168.1.1',
                        data: params,
                        options: Options(
                            contentType: 'application/x-www-form-urlencoded'));

抓包结果

以上两种方法需要注意的点如下

  • 内容不一样,header中使用的是一个Headers中的常量内容是application/x-www-form-urlencoded;charset=utf-8 而contentType中并没有charset这部分。

  • 如果指定了类型为application/x-www-form-urlencoded 则dio会对data中的数据自动编码。比如中文和空格。在抓包结果中可以看到。

  • multipart/form-data 不支持这种方式,下面会介绍。

使用 multipart/form-data (附带上传文件)

上面介绍的方法对multipart/form-data 这种类型不支持。需要单独编码。因为这种类型比较复杂,还可以上传文件。

测试代码

 					Map params = ;
                    params['name']='zhangshan&a b c';
                    params['age']=24;
                    params['language']='中文';
                    //上传文件
                    var mtp= MultipartFile.fromString('abcdef',filename: 'text.file') ;
                    //携带其他参数
                    var formData = FormData.fromMap('file':mtp,...params);
                    HttpUtil.dio.post(
                        'http://newapp.jyb.cn/app_pub/',
                        data: formData,
                      );

抓包结果

MultipartFile类具有很多方便的静态方法,可以很轻松的获取一个文件。

指定返回类型

如果你请求的时候指定了返回类型,dio会自动帮你转化,比如你指定类型为一个json。则拿到的response.data 属性就会是一个Map。 这个主要是通过设置option的responseType来实现的。
代码如下

					 Map params = ;
                    params['name']='zhangshan&a b c';
                    params['age']=24;
                    params['language']='中文';

                    HttpUtil.dio.post(
                        'http://newapp.jyb.cn/app_pub/',
                        data: params,
                        options: Options(responseType: ResponseType.json)
                      );

附带一个网络访问工具的封装

import 'dart:io';

import 'package:connectivity/connectivity.dart';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:dio_http_cache/dio_http_cache.dart';
import 'package:dio_log/interceptor/dio_log_interceptor.dart';
import 'package:tibet_wxb/common/http/mock_data_interceptor.dart';
import 'package:tibet_wxb/common/util/dev_model_check.dart';
import 'package:tibet_wxb/common/util/web_util.dart';

typedef JsonParseFun<T> = T Function(Map<String, dynamic> json);
typedef StringParseFun<T> = T Function(String str);

enum DioMethod 
  get,
  post,
  put,
  delete,
  patch,
  head,


class HttpUtil 
  static late Dio _dio;
  static const int _connectTimeout = 30 * 1000; //15s
  static const int _receiveTimeout = 30 * 1000;
  static const int _sendTimeout = 30 * 1000;
  static DioCacheManager? _dioCacheManager;
  //是否开启抓包功能
  static const bool isProxyEnable = true;
  //设置代理服务器地址和端口
  static const String proxy = "192.168.7.134:8888";

  static init() 
    /// 全局属性:请求前缀、连接超时时间、响应超时时间
    var options = BaseOptions(
      /// 请求的Content-Type,默认值是"application/json; charset=utf-8".
      /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
      /// 可以设置此选项为 `Headers.formUrlEncodedContentType`,  这样[Dio]就会自动编码请求体.
      responseType: ResponseType.json,
      validateStatus: (status) 
        // 不使用http状态码判断状态,使用AdapterInterceptor来处理(适用于标准REST风格)
        return true;
      ,
      connectTimeout: _connectTimeout,
      receiveTimeout: _receiveTimeout,
      sendTimeout: _sendTimeout,
    );
    _dio = Dio(options);
    //添加网络日志监听
    _dio.interceptors.add(DioLogInterceptor());
    //添加网络缓存
    if (PlatformUtil.isInMobile) 
      _dioCacheManager = DioCacheManager(CacheConfig());
      _dio.interceptors.add(_dioCacheManager!.interceptor);
    
    //添加对模拟数据的处理
    _dio.interceptors.add(MockDataInterceptor());

    //配置可以通过Fiddler抓包
    if(isProxyEnable)
      (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) 
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) 
          return isProxyEnable &&DebugModelUtil.isDebugMode;
        ;
        client.findProxy = (url) 
          return  'PROXY $proxy' ;
        ;
      ;
    
  

  static void clearCache() 
    _dioCacheManager!.clearAll();
  
  static Dio get dio
    return _dio;
  

//只获取string
  static Future<String> requestString<T>(
    String path, 
    DioMethod method = DioMethod.get,
    Map<String, dynamic>? params,
    Map<String, dynamic>? headers,
    data,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  ) async 
    options??=Options();
    var str = await request<String>(
      path,
      stringParseFun: (str)=>str,
      method: method,
      params: params,
      headers: headers,
      data: data,
      cancelToken: cancelToken,
      options: options.copyWith(
        responseType: ResponseType.plain
      ),
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );

    return str ?? "";
  

  /// 对网络访问的统一封装
  /// 如果请求的数据是string的话,那么需要传入stringParseFun 方法
  /// 如果返回的数据格式是json的话,需要传入jsonParseFun 方法
  static Future<T?> request<T>(
    String path, 
    JsonParseFun<T>? jsonParseFun,
    StringParseFun<T>? stringParseFun,
    DioMethod method = DioMethod.get,
    Map<String, dynamic>? params,
    Map<String, dynamic>? headers,
    data,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  ) async 
    const _methodValues = 
      DioMethod.get: 'get',
      DioMethod.post: 'post',
      DioMethod.put: 'put',
      DioMethod.delete: 'delete',
      DioMethod.patch: 'patch',
      DioMethod.head: 'head'
    ;

    options ??= Options();
    options=options.copyWith(method: _methodValues[method],headers: headers);
    print('options content type is $options.contentType');
    try 
      Response response;
      response = await _dio.request(path,
          data: data,
          queryParameters: params,
          cancelToken: cancelToken,
          options: options,
          onSendProgress: onSendProgress,
          onReceiveProgress: onReceiveProgress);
      if (response.statusCode == 200) 
        if (stringParseFun != null) 
          return stringParseFun(response.data);
        

        if (response.data is Map && jsonParseFun != null) 
          return jsonParseFun(response.data);
        
       else 
        throw DioError(
            requestOptions: response.requestOptions,
            type: DioErrorType.other,
            response: response,
            error: "服务器错误:状态码为" + response.statusCode.toString());
      
     on DioError catch (e) 
      onErrorInterceptor(e);
      rethrow;
    
  

  // 对错误添加更好的语义化处理

  static void onErrorInterceptor(DioError err) async 
    // 异常分类
    switch (err.type) 
      // 4xx 5xx response
      case DioErrorType.response:
        err.requestOptions.extra["errorMsg"] = err.response?.data ?? "连接异常";
        break;
      case DioErrorType.connectTimeout:
        err.requestOptions.extra["errorMsg"] = "连接超时";
        break;
      case DioErrorType.sendTimeout:
        err.requestOptions.extra["errorMsg"] = "发送超时";
        break;
      case DioErrorType.receiveTimeout:
        err.requestOptions.extra["errorMsg"] = "接收超时";
        break;
      case DioErrorType.cancel:
        err.requestOptions.extra["errorMsg"] =
            err.message.isNotEmpty ? err.message : "取消连接";
        break;
      case DioErrorType.other:
      default:
        var connectivityResult = await (Connectivity().checkConnectivity());
        //判断是否有网络
        if (connectivityResult == ConnectivityResult.none) 
          err.requestOptions.extra["errorMsg"] = "网络未连接";
          break;
        
        err.requestOptions.extra["errorMsg"] = "连接异常";
        break;
    
  


对模拟数据的支持

使用拦截器实现

import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'dart:convert';

class MockDataInterceptor extends Interceptor

  static const String local_path='http://127.0.0.1/';

  @override
  Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async 
    var path = options.path;

    if(path.startsWith(local_path))
      try
        String relPath= "assets/mock_data/"+path.substring(local_path.length);
        String jsonStr=await rootBundle.loadString(relPath);
        Response response=Response(requestOptions: options);
        response.data=json.decode(jsonStr);
        response.statusCode=200;
        handler.resolve(response);
      catch (e)
        DioError dioError=DioError(requestOptions: options,error: "解析模拟数据错误$e");
        handler.reject(dioError);
      

    else
      handler.next(options);
    
  

以上是关于Flutter dio 使用 注意事项的主要内容,如果未能解决你的问题,请参考以下文章

flutter中dio网络get请求使用总结

使用 Dio/bloc Flutter 处理错误

Flutter网络请求Dio库的使用及封装

Flutter:使用 dio 包获取请求发送 JSON 正文

Flutter进行HTTP请求并保存登陆状态(dio)

Flutter进行HTTP请求并保存登陆状态(dio)