Flutter 将Dio请求转发原生网络库的实现方案

 更新时间:2022年05月27日 10:18:40   作者:且听风吟zzz  
这篇文章主要介绍了Flutter 将Dio请求转发原生网络库,需要注意添加NativeNetInterceptor,如果有多个拦截器,例如LogInterceptors等等,需要将NativeNetInterceptor放到最后,需要的朋友可以参考下

背景

首先看到标题,相信大家已经血压飙升了,既然都用了Flutter,怎么还用原生的网络库呢?这不是多此一举么。emmmmm,当我看到这个需求的时候一样恼火。但因为我们项目中使用的Dio库不支持查询DNSSSL等所需时间(至少我没找到),而Okhttp却可以。因此,不得以只能借助原生网络库去实现此功能。

实现方案

既然必须要去实现,那摆在面前的问题就是如何去实现了。最简单粗暴的一个方式,就是通过建立一个Flutter Channel将网络请求的所有信息传到原生,但脑补一波后,发现实现起来太过麻烦,一堆header,body,data等信息都需要自己组装,并且致命的是需要改动现有项目的请求方式。因此为了避免项目大改,最后看中了Dio提供的interceptors(拦截器)功能,在拦截器中,我们可以对网络请求进行拦截,拦截后再通过FlutterChannel发给原生进行网络请求。这样既避免了项目改动,也省去了数据组装,很容易的达到了目标。

实现步骤

  • 首先定义一个拦截器(NativeNetInterceptor),用来转发网络请求,在onRequest中,我们调用了NativeNet.sendRequestToNative方法,用来接收接口中的参数(url、header、body、data、等等等),方便下一步转发。catch模块主要用来捕获异常,如果是DioError,直接reject,如果是其他异常,可以调用handler.next(options)继续走Dio请求,当作兜底方案。这里我注释掉了。
class NativeNetInterceptor extends Interceptor {
  final Dio dio;
  final NativeNetOptions options;
  NativeNetInterceptor({
    required this.dio,
    NativeNetOptions? options,
  }) : options = options ?? NativeNetOptions();
  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    try {
      Response response = await NativeNet.sendRequestToNative(options);
      return handler.resolve(response, true);
    } catch (e) {
      if (e.runtimeType == DioError) {
        return handler.reject(e as DioError);
      } else {
        // TODO:如果担心原生有问题,可打开下方注释。
        // handler.next(options);
        rethrow;
      }
    }
  }
}
  • NativeNet的实现,这里主要是将Dio options中所带的信息发送给原生,并将原生返回信息进行组装。
class NativeNet {
  static const MethodChannel _channel = MethodChannel('nativeNet');

  static Future<Response> sendRequestToNative(RequestOptions options) async {
    final String url = options.baseUrl + options.path;
    Map channelParams = _getChannelParams(
      url,
      options.method,
      queryParameters: options.queryParameters,
      data: options.data,
      header: options.headers,
      timeoutInterval: options.connectTimeout,
    );
    return await _sendRequestToNative(options, channelParams);
  }

  static Future<Response> _sendRequestToNative(
      RequestOptions options, Map params) async {
    final Map nativeResponse =
        await _channel.invokeMapMethod('net', params) ?? {};
    final Response response = Response(requestOptions: options);
    if (nativeResponse.containsKey('headers')) {
      final Map nativeResponseHaders = nativeResponse['headers'];
      nativeResponseHaders.forEach((key, value) {
        response.headers.set(key, value);
      });
    }
    response.statusCode = nativeResponse['statusCode'] ?? -1;
    response.statusMessage = nativeResponse['statusMessage'] ?? '无message';
    if (Platform.isAndroid) {
      if (nativeResponse.containsKey('data')) {
        String jsonData = nativeResponse['data'];
        try {
          Map<String, dynamic> data = convert.jsonDecode(jsonData);
          response.data = data;
        } on FormatException catch (e) {
          ///转换异常后手动构造一下
          debugPrint("Http FormatException");
          Map map = {};
          map["data"] = jsonData;
          response.data = map;
        }
      } else {
        response.data = {};
      }
    } else {
      Map<String, dynamic> data =
          Map<String, dynamic>.from(nativeResponse['data'] ?? {});
      response.data = data;
    }

    if (nativeResponse.containsKey('error')) {
      //网络请求失败
      Map? errorData = nativeResponse['error'];
      throw DioError(
          requestOptions: options, response: response, error: errorData);
    }
    return response;
  }
  static Map<String, dynamic> _getChannelParams(
    String url,
    String method, {
    Map<String, dynamic>? queryParameters,
    Map<String, dynamic>? data,
    Map<String, dynamic>? header,
    num? timeoutInterval,
    num? retryCount,
  }) {
    Map<String, dynamic> channelParams = {
      'url': url,
      'method': method,
    };
    if (queryParameters != null) {
      channelParams['queryParameters'] = queryParameters;
    }
    if (data != null) {
      channelParams['data'] = data;
    }
    if (header != null) {
      channelParams['header'] = header;
    }
    if (retryCount != null) {
      channelParams['retryCount'] = retryCount;
    }
    if (timeoutInterval != null) {
      channelParams['timeoutInterval'] = timeoutInterval;
    }
    return channelParams;
  }
}
  • 原生实现,这里贴出安卓代码,请求完之后,无论成功失败,都调用result.success将处理好的数据返回给flutter。具体的ANDROID/IOS 网络请求这里就略过了,相信大家都用的很成熟了。
class NativeNetPlugin : FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel
    private var gson: Gson = Gson()
    private val mTag = "Android NativeNetPlugin"

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "nativeNet")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == "net") {
            val channelParams = call.arguments as Map<String, Any>

            var method = ""
            if (channelParams.containsKey("method")) {
                method = channelParams["method"] as String
            }

            var url = ""
            if (channelParams.containsKey("url")) {
                url = channelParams["url"] as String
            }

            var header = HashMap<String, String>()
            if (channelParams.containsKey("header")) {
                header = channelParams["header"] as HashMap<String, String>
            }
            val headers = HttpHeaders()
            headers.headersMap = getMapValueForLinkedHashMap(header)

            // params
            var queryParameters = HashMap<String, String>()
            if (channelParams.containsKey("queryParameters")) {
                queryParameters = channelParams["queryParameters"] as HashMap<String, String>
            }
            //post body
            var data = HashMap<String, Any>()
            if (channelParams.containsKey("data")) {
                data = channelParams["data"] as HashMap<String, Any>
            }

            //超时时间
            var timeoutInterval: Int = 1000 * 15
            if (channelParams.containsKey("timeoutInterval")) {
                timeoutInterval = channelParams["timeoutInterval"] as Int
            }
            val mTimeOut =
                Timeout(timeoutInterval, timeoutInterval, timeoutInterval, TimeUnit.MILLISECONDS)

            //重试次数
            var retryCount = 0
            if (channelParams.containsKey("retryCount")) {
                retryCount = channelParams["retryCount"] as Int
            }

            when (method) {
                "POST" -> {
                    ...
                    //请求成功/失败后调用此方法,
                    result.success(dealResponse(response))
                    
                }
                "GET" -> {
                   ...
                }

                "DELETE" -> {
                   ...
                }

                "PUT" -> {
                   ...
                }

                "HEAD" -> {
                   ...
                }
                else -> {
                    result.notImplemented()
                }
            }
        } else {
            result.notImplemented()
        }
    }

    private fun dealResponse(
        response: Response<String>
    ): Map<String, Any?> {
        val map = HashMap<String, Any?>()

        if (BuildConfig.DEBUG) {
            Log.e(mTag, "dealResponse isSuccessful: ${response.code()}")
            Log.e(mTag, "dealResponse code: ${response.code()}")
            Log.e(mTag, "dealResponse message: ${response.message()}")
            Log.e(mTag, "dealResponse body: ${response.body()}")
            Log.e(mTag, "dealResponse headers: ${response.headers()}")
        }

        map["statusCode"] = response.code()

        response.message()?.let {
            map["statusMessage"] = it
        } ?: let {
            map["statusMessage"] = ""
        }

        response.body()?.let {
            map["data"] = it
        } ?: let {
            map["data"] = ""
        }

        response.headers()?.let {
            map["headers"] = it.toMap()
        } ?: let {
            map["headers"] = HashMap<String, Any>()
        }

        if (response.code() != 200) {
            //失败
            val errorMap = HashMap<String, Any?>()
            response.exception?.let {
                errorMap["code"] = response.code()
                errorMap["domain"] = it.toString()
                errorMap["description"] = it.message
            } ?: let {
                errorMap["code"] = response.code()
                errorMap["domain"] = map["statusMessage"]
                errorMap["description"] = "HttpException"
            }
            map["error"] = errorMap
        }
        return map
    }

    //map 转 LinkedHashMap
    private fun getMapValueForLinkedHashMap(dataMap: Map<String, String>): LinkedHashMap<String, String> {
        val returnMap: LinkedHashMap<String, String> = LinkedHashMap()
        if (dataMap.isNullOrEmpty()) {
            return returnMap
        }
        val iterator = dataMap.keys.iterator()
        while (iterator.hasNext()) {
            val objKey = iterator.next()
            val objValue = dataMap[objKey]
            returnMap[objKey] = objValue.toString()
        }
        return returnMap
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

4.最后调用dio.interceptors.add(NativeNetInterceptor(dio: dio));将我们写好的NativeNetInterceptor加入Dio拦截器。

交互协议

Request(Flutter To Native)

{
    "url": "https://www.baidu.com", // 必传 请求地址
    "method": "GET", //必传 请求方式 GET POST HEAD PUT DELETE
    "queryParameters":{}, //可选 query参数
    "data":{}, //可选 body参数
    "header":{}, //可选 请求头
    "timeoutInterval":15000, //可选 (毫秒) 链接超时时常
    "retryCount": 0 //可选 重试次数,默认0
}

Response(Native To Flutter)

// native返回 成功
{
    "data":{}, //接口原始返回内容
    "headers":{}, //返回头
    "statusCode":200, //http状态码
    "statusMessage":"请求成功" //http状态码对应文案
}
// native返回 失败
{
    "data":{}, //接口原始返回内容
    "headers":{}, //返回头
    "statusCode":404, //http状态码
    "statusMessage":"找不到对象", //http状态码对应文案
    "error":{
    "code":-3001, //错误码
    "domain":"URLDmain", // 错误大分类
    "description":"错误详情" // 错误详情
    }
}

注意点

添加NativeNetInterceptor,如果有多个拦截器,例如LogInterceptors等等,需要将NativeNetInterceptor放到最后。

到此这篇关于Flutter 将Dio请求转发原生网络库的文章就介绍到这了,更多相关Flutter Dio请求转发原生网络库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Flutter Image组件如何处理图片加载过程中的错误

    详解Flutter Image组件如何处理图片加载过程中的错误

    在Flutter中,Image组件可以通过监听加载过程中的错误来处理图片加载过程中的错误,本文小编将给大家详细介绍了Flutter Image组件是如何处理图片加载过程中的错误,文中有详细的代码示例供大家参考,需要的朋友可以参下
    2023-10-10
  • Android如何添加控件监听器(三种方式)

    Android如何添加控件监听器(三种方式)

    本文主要介绍了Android如何添加控件监听器(三种方式),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Android多线程下载示例详解

    Android多线程下载示例详解

    这篇文章主要为大家详细介绍了Android多线程下载示例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 基于Android studio3.6的JNI教程之opencv实例详解

    基于Android studio3.6的JNI教程之opencv实例详解

    这篇文章主要介绍了基于Android studio3.6的JNI教程之opencv实例详解,本文通过实例代码截图的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • 花样使用Handler与源码分析

    花样使用Handler与源码分析

    今天小编就为大家分享一篇关于花样使用Handler与源码分析,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Android实现QQ侧滑菜单效果

    Android实现QQ侧滑菜单效果

    这篇文章主要为大家详细介绍了Android实现QQ侧滑菜单效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • Android SharedPreferences的使用分析

    Android SharedPreferences的使用分析

    本篇文章小编为大家介绍,Android SharedPreferences的使用分析。需要的朋友参考下
    2013-04-04
  • Android系统自带样式 (android:theme)

    Android系统自带样式 (android:theme)

    Android系统中自带样式分享,需要的朋友可以参考下
    2013-01-01
  • Android一次性退出多个Activity的方法

    Android一次性退出多个Activity的方法

    这篇文章主要介绍了Android一次性退出多个Activity的方法,结合实例形式分析了Activity的创建,继承,遍历与关闭等实现步骤与相关技巧,需要的朋友可以参考下
    2016-02-02
  • 基于Android Service 生命周期的详细介绍

    基于Android Service 生命周期的详细介绍

    本篇文章小编为大家介绍,基于Android Service 生命周期的详解。需要的朋友参考下
    2013-04-04

最新评论