前语

由于公司产品是原生+Flutter的混合开发模式,关于安卓来说网络恳求分别走的Android的Okhttp和Flutter的Dio。可是这个做法会存在一些坏处:

1,网络恳求的流程需要保证两套,原生一套流程,Flutter一套流程,我这儿指的流程是指例如重试,缓存等机制,例如我想搞个token过期跳转登录页面,我也得写两份代码.

2, 使用Charles抓包工具有点费事,咱们知道Flutter的Dio是忽略的手机署理设置的,想要走网络署理得在代码里边配置,但原生的网络组件不一样,使用Charles抓包就很简略.

出于以上两点考虑,我想把现在使用中现存的网络恳求和新增网络恳求统一托付到原生层走网络恳求,Flutter只收网络恳求结果解析展现ui即可.

在介绍大布景后,咱们需要先了解Dio, Dio自身是供给自定义网络恳求的办法的,这儿所谓的自定义是指自己去完结网络恳求的发送和接纳。

关于Dio网络恳求流程

首先咱们创立一个很简略的get恳求

如何把Flutter的网络请求代理到原生层进行

由于get内部的代码比较多,这儿只挑选几个有代表性的办法说下

get里边终究会调用到dio_mixin.dart中的fetch办法,望文生义,它便是发送网络恳求的地方,办法内部有个_dispatchRequest.

如何把Flutter的网络请求代理到原生层进行

_dispatchRequest里边的内容理解关于咱们这个需求非常重要,由于在这儿完结了实践的网络恳求的发送(便是包括socket相关的)咱们想替换实践的网络恳求到原生也要从这儿做文章.

Future<Response<dynamic>> _dispatchRequest<T>(RequestOptions reqOpt) async {
   //重视点一
   final responseBody = await httpClientAdapter.fetch(
        reqOpt,
        stream,
        cancelToken?.whenCancel,
      )
   final headers = Headers.fromMap(responseBody.headers);
      // Make sure headers and responseBody.headers point to a same Map
      //重视点二
      responseBody.headers = headers.map;
      final ret = Response<dynamic>(
        headers: headers,
        requestOptions: reqOpt,
        redirects: responseBody.redirects ?? [],
        isRedirect: responseBody.isRedirect,
        statusCode: responseBody.statusCode,
        statusMessage: responseBody.statusMessage,
        extra: responseBody.extra,
      );
  if (statusOk || reqOpt.receiveDataWhenStatusError == true) {
        //重视点三
        Object? data = await transformer.transformResponse(
          reqOpt,
          responseBody,
        );
        // Make the response as null before returned as JSON.
        if (data is String &&
            data.isEmpty &&
            T != dynamic &&
            T != String &&
            reqOpt.responseType == ResponseType.json) {
          data = null;
        }
        ret.data = data;
      }
}

上面标明晰三个值得重视的地方,这是我认为比较值得重视的点。

重视点一

经过httpClientAdapter获得responseBody目标,这儿httpClientAdapter我后面会详细讲到它的作用,这儿能够先理解为,它内部会完结http网络恳求的逻辑,执行完后就会回来一个responseBody

重视点二

创立一个response目标,实践上responseBody还不是终究回来的恳求应对,由于它只代表网络衔接建立的的response,它里边有个stream,同来读取socket衔接后的服务端回来的body,咱们需要获取stream的内容后封闭衔接.这儿咱们先创立response目标,并赋予一些已知的应对字段。

重视点三

之前说了这儿实践上是读取stream的内容(经过一个transformer),作为咱们实践网络应对使用的data内容. 这个data便是服务端回来的body了,咱们通常恳求结果用到的code , msg , json的协议内容这些就在这儿了.

看到这儿咱们实践上会有一个完结该需求大致的思路,咱们想要把实践的网络恳求署理到原生,就需要重视点一去把网络恳求的进程替换掉,让实践的网络恳求发生在原生,并回来一个response.这儿dio支撑供给自己的httpClientAdapter和transformer,这下就好办了,我直接搞一套自己的httpClientAdapter和transformer衔接到原生的不就行了.

不过在进行动手开发之前,咱们必需要先搞清楚HttpClientAdapter和HttpClient

HttpClientAdapter和HttpClient

HttpClientAdapter是Dio和HttpClient的一个桥梁

Dio:它用于完结标准和友爱的API给开发者使用

HttpClient:它用于在Dio中实践发送和接纳网络恳求

咱们能够经过HttpClientAdapter供给自己的HttpClient而不是默许的。

HttpClientAdapter实践上是一个抽象类,它不供给详细完结。

如何把Flutter的网络请求代理到原生层进行

这儿默许工厂办法创立的是IOHttpClientAdapter.

如何把Flutter的网络请求代理到原生层进行

回头来看看HttpClientAdapter里边有一个fetch办法

如何把Flutter的网络请求代理到原生层进行

fetch办法按照注释来说,它便是用来发送真实的网络恳求的地方,那么在IOHttpClientAdapter中又是如何完结的呢?这儿我直接跳过细节部分,看下要害的代码,它的fetch办法会调用到一个_fetch

如何把Flutter的网络请求代理到原生层进行

_configHttpClient会用来创立一个HttpClient,它用于在Dio中实践发送和接纳网络恳求。

如何把Flutter的网络请求代理到原生层进行

看完结,这儿分了两步

1,我如果有createHttpClient函数的赋值,我这儿直接用createHttpClient办法创立HttpClient

2,如果没有,便是直接调用HttpClient的结构,那它便是创立的是_HttpClient

如何把Flutter的网络请求代理到原生层进行

_HttpClient实践上是默许Dart io供给的Http的完结办法. HttpClient用于与Http服务端进行数据交换,发送Http恳求到Http服务端然后接纳response,还会维护一些状态,例如包括sessioncookies。

HttpClient包括发送HttpClientRequest到Http服务端然后接纳HttpClientResponse。 至于HttpClient内部细节我就不打开分析了,它里边无非就完结了http协议这些,也不是这篇文章重视的内容.

大致计划

其实了解了Dio的流程后,思路也比较清晰了,Dio供给了自定义HttpClientAdapter的办法,我这边自定义了一个NativeClientAdapter重写了一下fetch的完结(发送网路恳求的地方),在fetch中不走默许的httpclient, 直接走methodchannel把恳求参数发给原生(methodchannel是官方供给原生和flutter通讯的办法之一),原生拿到后发送网络恳求,恳求结果回来给flutter, 然后再给flutter上层做数据转化即可.

代码完结

class NativeNetDelegate {
  final dio = Dio();
  NativeNetDelegate(String gateway) {
    dio.httpClientAdapter = NativeClientAdapter();
    dio.transformer = NativeTransformer();
    dio.options.baseUrl = Base.baseUrl + gateway;
  }
}

这儿我自定义一个Dio, 使用了自己的NativeClientAdapter和NativeTransformer。NativeTransformer用来转化原生回来的数据.

class NativeClientAdapter implements HttpClientAdapter {
  static const _tag = "NativeClientAdapter";
  /// 这儿重写fetch办法,不走默许httpclient的完结逻辑,把恳求参数直接发送给原生做处理.
  @override
  Future<ResponseBody> fetch(
      RequestOptions options,
      Stream<Uint8List>? requestStream,
      Future<void>? cancelFuture,
      ) async {
    NativeRequestOption nativeRequestOption =
        NativeRequestOption().compose(options);
    vlog.d("$_tag fetch request $nativeRequestOption");
    dynamic result =
        await FlutterMethodHelper.instance.sendHttpRequest(nativeRequestOption.toJson());
    vlog.d("$_tag fetch response result $result");
    /// http code
    int httpCode = result['httpCode'];
    /// 协议 json data
    String data = result['data'];
    return NativeResponseBody(
      fakeStream(),
      httpCode,
    )..data = data;
  }
  /// 由于这儿走的署理,直接回来了response中的data部分,所以这个不存在使用stream读取data
  Stream<Uint8List> fakeStream() async* {
  }
  /// Dio封闭时候调用,由于是把网络恳求转发给原生处理,这儿不存在释放资源
  /// 概况参考 [Dio] close办法
  @override
  void close({bool force = false}) {
  }
}
class NativeTransformer implements Transformer {
  static const String _tag = "NativeTransformer";
  ///这儿由于署理到原生,所以不做任何Stream的读取Request的内容
  @override
  Future<String> transformRequest(RequestOptions options) async {
    return "";
  }
  ///这儿直接回来原生网络恳求回来的协议json,便是response的body.
  @override
  Future<dynamic> transformResponse(RequestOptions options, ResponseBody responseBody) async {
    if (responseBody is NativeResponseBody) {
      if (responseBody.data != null) {
        /// 这儿转化成json给上层使用
        Map<String, dynamic> data = jsonDecode(responseBody.data!);
        vlog.d("$_tag $data");
        return data;
      } else {
        return "";
      }
    } else {
      throw DioException(
          requestOptions: options,
          message:
              "no support responseBody type, only support NativeResponseBody in NativeTransformer");
    }
  }
}
class NativeResponseBody extends ResponseBody {
  String? data;
  NativeResponseBody(super.stream, super.statusCode);
}

上面咱们用到一个NativeRequestOption,这其实是一个协议内容类,咱们经过转发这个目标到原生层,让原生层解析并发送网络恳求即可.

class NativeRequestOption {
  String? baseUrl;
  String? path;
  String? method;
  String? data;
  Map<String, dynamic>? queryParameters;
  NativeRequestOption();
  factory NativeRequestOption.fromJson(Map<String, dynamic> json) => _$NativeRequestOptionFromJson(json);
  Map<String, dynamic> toJson() => _$NativeRequestOptionToJson(this);
  NativeRequestOption compose(RequestOptions options) {
    baseUrl = options.baseUrl;
    path = options.path;
    method = options.method;
    data = options.data?.toString();
    queryParameters = options.queryParameters;
    return this;
  }
  @override
  String toString() {
    return 'NativeRequestOption{baseUrl: $baseUrl, path: $path, method: $method, data: $data, queryParameters: $queryParameters}';
  }
}

最终,原生层的代码我就不贴了,无非是收到NativeRequestOption后再发送网络恳求,收到网络结果后再发送回flutter解析一个结构即可.

{
“httpCode“ : 1001data” : 事务协议内容(便是code, data, msg)
}

结语

这篇文章最重要的仍是供给一个思路,代码完结因人而异,因需求而异,能够依据自己的需求去自定义HttpClientAdapter。 当然Dio还供给了阻拦器功能,咱们在阻拦器里边直接阻拦恳求往原生转发也是可行的,这儿就不做这个计划的探讨了. 觉得有用的别忘了点赞,谈论加保藏哦~.