关于flutter:Flutter-Dio源码分析三深度剖析

7次阅读

共计 7848 个字符,预计需要花费 20 分钟才能阅读完成。

文章系列

Flutter Dio 源码剖析(一)–Dio 介绍

Flutter Dio 源码剖析(二)–HttpClient、Http、Dio 比照

Flutter Dio 源码剖析(三)– 深度分析

Flutter Dio 源码剖析(四)– 封装

视频系列

Flutter Dio 源码剖析(一)–Dio 介绍视频教程

Flutter Dio 源码剖析(二)–HttpClient、Http、Dio 比照视频教程

Flutter Dio 源码剖析(三)– 深度分析视频教程

Flutter Dio 源码剖析(四)– 封装视频教程

源码仓库地址

github 仓库地址

介绍

在后面两篇文章中咱们说了 Dio 的介绍以及对 HttpClientHttpDio 这三个网络申请的剖析,这章节次要是对Dio 源码的剖析。

从 post 申请来进行剖析

var response = await Dio().post('http://localhost:8080/login', queryParameters: {
  "username": "123456",
  "password": "123456"
});

post 办法

post 办法有七个参数,在该函数中调用了 request 办法,并没有做任何解决,接下来咱们看下request 办法。

  1. path: 申请的 url 链接
  2. data: 申请数据,例如上传用到的 FromData
  3. queryParameters: 查问参数
  4. options: 申请选项
  5. cancelToken: 用来勾销发送申请的 token
  6. onSendProgress: 网络申请发送的进度
  7. onReceiveProgress: 网络申请接管的进度
@override
Future<Response<T>> post<T>(
  String path, {
  data,
  Map<String, dynamic>? queryParameters,
  Options? options,
  CancelToken? cancelToken,
  ProgressCallback? onSendProgress,
  ProgressCallback? onReceiveProgress,
}) {
  return request<T>(
    path,
    data: data,
    options: checkOptions('POST', options),
    queryParameters: queryParameters,
    cancelToken: cancelToken,
    onSendProgress: onSendProgress,
    onReceiveProgress: onReceiveProgress,
  );
}

request 办法

request 接管了post 办法中传进来的参数。

第一步:合并选项

通过调用compose 办法来进行选项合并。

compose 函数执行流程
  1. 首先判断queryParameters 是否为空,不为空则增加到一个query 长期变量中
  2. options 中的headers 全副拿进去存到长期变量_headers 中进行不辨别大小写的映射,并删除headers 中的 contentTypeHeader
  3. 如果 headers 不为空,则把 headers 中的全副属性增加到长期变量_headers 中并把contentTypeHeader 赋值到一个长期变量 _contentType 中。
  4. options 中的自定义字段extra 赋值给一个长期变量
  5. method 对立转换成大写字母
  6. 创立一个 RequestOptions 并传入下面解决过的参数并返回
compose 源码
RequestOptions compose(
  BaseOptions baseOpt,
  String path, {
    data,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) {var query = <String, dynamic>{};
  if (queryParameters != null) query.addAll(queryParameters);
  query.addAll(baseOpt.queryParameters);

  var _headers = caseInsensitiveKeyMap(baseOpt.headers);
  _headers.remove(Headers.contentTypeHeader);

  var _contentType;

  if (headers != null) {_headers.addAll(headers!);
    _contentType = _headers[Headers.contentTypeHeader];
  }

  var _extra = Map<String, dynamic>.from(baseOpt.extra);
  if (extra != null) {_extra.addAll(extra!);
  }
  var _method = (method ?? baseOpt.method).toUpperCase();
  var requestOptions = RequestOptions(
    method: _method,
    headers: _headers,
    extra: _extra,
    baseUrl: baseOpt.baseUrl,
    path: path,
    data: data,
    connectTimeout: baseOpt.connectTimeout,
    sendTimeout: sendTimeout ?? baseOpt.sendTimeout,
    receiveTimeout: receiveTimeout ?? baseOpt.receiveTimeout,
    responseType: responseType ?? baseOpt.responseType,
    validateStatus: validateStatus ?? baseOpt.validateStatus,
    receiveDataWhenStatusError:
    receiveDataWhenStatusError ?? baseOpt.receiveDataWhenStatusError,
    followRedirects: followRedirects ?? baseOpt.followRedirects,
    maxRedirects: maxRedirects ?? baseOpt.maxRedirects,
    queryParameters: query,
    requestEncoder: requestEncoder ?? baseOpt.requestEncoder,
    responseDecoder: responseDecoder ?? baseOpt.responseDecoder,
    listFormat: listFormat ?? baseOpt.listFormat,
  );

  requestOptions.onReceiveProgress = onReceiveProgress;
  requestOptions.onSendProgress = onSendProgress;
  requestOptions.cancelToken = cancelToken;

  requestOptions.contentType = _contentType ??
    contentType ??
    baseOpt.contentTypeWithRequestBody(_method);
  return requestOptions;
}

第二步:调用 fetch

判断用户是否敞开申请,敞开则退出,未敞开调用 Fetch 办法

request 源码
 @override
  Future<Response<T>> request<T>(
    String path, {
    data,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {options ??= Options();
    var requestOptions = options.compose(
      this.options,
      path,
      data: data,
      queryParameters: queryParameters,
      onReceiveProgress: onReceiveProgress,
      onSendProgress: onSendProgress,
      cancelToken: cancelToken,
    );
    requestOptions.onReceiveProgress = onReceiveProgress;
    requestOptions.onSendProgress = onSendProgress;
    requestOptions.cancelToken = cancelToken;

    if (_closed) {
      throw DioError(
        requestOptions: requestOptions,
        error: "Dio can't establish new connection after closed.",
      );
    }

    return fetch<T>(requestOptions);
  }

Fetch 办法

第一步:申请参数赋值

判断如果传递进来的requestOptions.cancelToken 不为空的状况下,则把传递进来的requestOptions 进行赋值。

if (requestOptions.cancelToken != null) {requestOptions.cancelToken!.requestOptions = requestOptions;}
第二步:响应数据设定

如果申请回来的参数不是动静类型并且不是 bytesstream的形式,则进行判断该返回值类型是否是字符串,为真返回 UTF- 8 的编码类型,否则返回字符串类型

if (T != dynamic &&
    !(requestOptions.responseType == ResponseType.bytes ||
      requestOptions.responseType == ResponseType.stream)) {if (T == String) {requestOptions.responseType = ResponseType.plain;} else {requestOptions.responseType = ResponseType.json;}
}
第三步:构建申请流并增加拦截器

1、构建一个申请流,InterceptorState是一个外部类,外面总共与两个属性T data 以及 InterceptorResultType type,用于以后拦截器和下一个拦截器之间传递状态所定义。

2、按 FIFO 程序执行,循环遍历向申请流中增加申请拦截器,拦截器中最次要的有RequestInterceptor 申请前拦挡和 ResponseInterceptor 申请后拦挡的两个实例。

var future = Future<dynamic>(() => InterceptorState(requestOptions));

interceptors.forEach((Interceptor interceptor) {future = future.then(_requestInterceptorWrapper(interceptor.onRequest));
});
第四步:拦截器转换为函数回调

这里次要做的一步操作是把函数的回调作为办法的参数,这样就实现了把拦截器转换为函数回调,这里做了一层判断,如果 state.type 等于 next 的话,那么会减少一个监听勾销的异步工作,并把cancelToken 传递给了这个工作,接下来他会查看以后的这个拦截器申请是否入队,最初定义了一个申请拦截器的变量,该拦截器外面有三个次要的办法别离是next()、resole()reject(),最初把这个拦截器返回进来。

FutureOr Function(dynamic) _requestInterceptorWrapper(
  void Function(
    RequestOptions options,
    RequestInterceptorHandler handler,
  )
  interceptor,
) {return (dynamic _state) async {
    var state = _state as InterceptorState;
    if (state.type == InterceptorResultType.next) {
      return listenCancelForAsyncTask(
        requestOptions.cancelToken,
        Future(() {return checkIfNeedEnqueue(interceptors.requestLock, () {var requestHandler = RequestInterceptorHandler();
            interceptor(state.data, requestHandler);
            return requestHandler.future;
          });
        }),
      );
    } else {return state;}
  };
}
第五步:构建申请流调度回调

调度回调和增加拦截器转换为函数回调,不同的是调度回调外面进行了申请散发。

future = future.then(_requestInterceptorWrapper((
  RequestOptions reqOpt,
  RequestInterceptorHandler handler,
) {
  requestOptions = reqOpt;
  _dispatchRequest(reqOpt).then((value) => handler.resolve(value, true),
    onError: (e) {handler.reject(e, true);
    },
  );
}));
第六步:申请散发

1、申请散发函数外面会调用_transfromData 进行数据转换,最终转换进去的数据是一个 Stream 流。

2、调用网络申请适配器进行网络申请 fetch 办法,这里阐明下该适配器定义有两个,别离如下:

2.1、BrowserHttpClientAdapter 是调用了 html_dart2js 的库进行了网络申请,该库是将dart 代码编译成可部署的 JavaScript

2.2、DefaultHttpClientAdapter 是采纳零碎申请库 HttpClient 进行网络申请。

3、把响应头赋值给长期变量responseBody 并通过fromMap 转换成 Map<String, List<String>> 类型

4、初始化响应类,并对返回的数据进行赋值解决。

5、判断如果是失常返回就对ret.data 变量进行数据格式转换,失败则勾销监听响应流

6、查看申请是否通过cancelToken 变量勾销了,如果勾销了则间接抛出异样

7、最初在进行申请是否失常,如果失常则查看是否入队并返回,否则间接抛出申请异样DioError

Future<Response<T>> _dispatchRequest<T>(RequestOptions reqOpt) async {
  var cancelToken = reqOpt.cancelToken;
  ResponseBody responseBody;
  try {var stream = await _transformData(reqOpt);
    responseBody = await httpClientAdapter.fetch(
      reqOpt,
      stream,
      cancelToken?.whenCancel,
    );
    responseBody.headers = responseBody.headers;
    var headers = Headers.fromMap(responseBody.headers);
    var ret = Response(
      headers: headers,
      requestOptions: reqOpt,
      redirects: responseBody.redirects ?? [],
      isRedirect: responseBody.isRedirect,
      statusCode: responseBody.statusCode,
      statusMessage: responseBody.statusMessage,
      extra: responseBody.extra,
    );
    var statusOk = reqOpt.validateStatus(responseBody.statusCode);
    if (statusOk || reqOpt.receiveDataWhenStatusError == true) {var forceConvert = !(T == dynamic || T == String) &&
        !(reqOpt.responseType == ResponseType.bytes ||
          reqOpt.responseType == ResponseType.stream);
      String? contentType;
      if (forceConvert) {contentType = headers.value(Headers.contentTypeHeader);
        headers.set(Headers.contentTypeHeader, Headers.jsonContentType);
      }
      ret.data = await transformer.transformResponse(reqOpt, responseBody);
      if (forceConvert) {headers.set(Headers.contentTypeHeader, contentType);
      }
    } else {await responseBody.stream.listen(null).cancel();}
    checkCancelled(cancelToken);
    if (statusOk) {return checkIfNeedEnqueue(interceptors.responseLock, () => ret)
        as Response<T>;
    } else {
      throw DioError(
        requestOptions: reqOpt,
        response: ret,
        error: 'Http status error [${responseBody.statusCode}]',
        type: DioErrorType.response,
      );
    }
  } catch (e) {throw assureDioError(e, reqOpt);
  }
}

download 办法

download 办法的执行流程和 post 一样,只是接管的数据类型以及逻辑解决上不一样,会把下载的文件保留到本地,具体实现流程在 src>entry>dio_fornative.dart 文件中,这里不在做过多的赘述。

总结

在咱们进行 get() post() 等调用时,都会进入到 request 办法,request 办法次要负责对申请参数以及自定义申请头的对立解决,并调用了fetch 办法,而 fetch 中是对响应数据设定、构建申请流、增加拦截器、申请散发的操作。

正文完
 0