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

2022-05-27 12:13:14
目录
背景实现方案实现步骤交互协议注意点

背景

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

实现方案

既然必须要去实现,那摆在面前的问题就是如何去实现了。最简单粗暴的一个方式,就是通过建立一个Flutter>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>

        {
            "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>