Flutter网络请求框架Dio源码分析以及封装--请求流程分析 前言目的请求流程-构造Dio对象请求流程-构造请求参数请求流程-构建请求流并添加拦截器请求流程-请求分发总结 前言 利用
利用flutter开发app也已经有些时间了,这个过程中最多接触到的就是网络请求相关的代码。自己目前项目中使用的是现在市面上最流行的网络请求库-dio,相对于flutter自带的HttpClient来说,dio使用起来更简单,功能更强大,支持全局配置、Restful api、FORMData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时以及自定义适配器等。
写这篇文章的目的是为了系统了解Dio的工作原理,之前碰到了一个网络请求Cookie持久化的问题,折腾了很久才解决掉,浪费了很多时间。这才发现虽然Dio使用了很长时间,但是底层原理还有很多地方没有搞明白。所以,趁这个机会,从头梳理一下Dio的源码,之后再基于新的理解重新封装下。
首先我们需要导入Dio的库,基于项目中用到的4.0.6版本
dependencies: dio: ^4.0.6
首先,我们从官方文档上给出的一个简单示例开始着手:
import 'package:dio/dio.dart';final dio = Dio();void getHttp() async { final response = await dio.get('https://dart.dev'); print(response);}
要利用Dio发出一个get请求,我们首先需要构造一个Dio的对象实例。我们看下Dio这个类及其构造方法:
abstract class Dio { factory Dio([BaseOptions? options]) => createDio(options); ... }
可以看出,Dio是一个抽象类,Dio的构造方法实际上用了factory-工厂构造函数(当使用factory修饰一个构造器时,DartVM不会总是创建一个新的对象,而是返回一个在内存中已经存在的对象。比如它可能会从缓存中返回一个已有的实例,或者是返回子类的实例),实现的createDio是工厂方法,其实现类通过平台区分导入,在移动端中导入的是 dio_for_native.dart,这个文件中 createDio创建的是DioForNative对象。
// ignore: uri_does_not_exist if (dart.library.html) 'entry/dio_for_browser.dart'// ignore: uri_does_not_exist if (dart.library.io) 'entry/dio_for_native.dart';
Dio createDio([BaseOptions? baseOptions]) => DioForNative(baseOptions);class DioForNative with DioMixin implements Dio { /// Create Dio instance with default [BaseOptions]. /// It is recommended that an application use only the same DIO singleton. DioForNative([BaseOptions? baseOptions]) { options = baseOptions ?? BaseOptions(); httpClientAdapter = DefaultHttpClientAdapter(); }
可以看到,DioForNative除了实现Dio这个抽象类以外还混入了DioMixin这个类,通过Dart中Mixins的原理可以直接调用DioMixin里的方法,Dio提供的get、post等方法主要实现在这个类中,可以理解为除去平台差异通用的逻辑都在这里实现。
构造方法里面有一个可选参数baseOptions,这个是所有网络请求的基础配置信息(每次请求可单独配置会覆盖此配置,主要有baseUrl,connectTimeout,receiveTimeout等等),还有一个httpClientAdapter,这个是Dio与flutter自带的HttpClient的适配器,最终通过httpClientAdapter调用HttpClient发起请求,我们先不深究这个,下面来看dio的get方法:
Future<Response<T>> get<T>( String path, { Map<String, dynamic>? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress, }) { return request<T>( path, queryParameters: queryParameters, options: checkOptions('GET', options), onReceiveProgress: onReceiveProgress, cancelToken: cancelToken, ); }
get是由DioMixin实现的,最终调用了request方法,其他如post等方法也是如此,我们先来看看传入的几个参数:
request方法里面,主要是执行compose方法将初始化时输入的BaseOptions对象和调用时传入的Options对象合并成RequestOptions对象,接着调用fetch方法并传入生成的RequestOptions对象。
var requestOptions = options.compose( this.options, path, data: data, queryParameters: queryParameters, onReceiveProgress: onReceiveProgress, onSendProgress: onSendProgress, cancelToken: cancelToken, ); ... return fetch<T>(requestOptions);
构造一个异步的请求流,并循环遍历向请求流中添加请求拦截器:
// Start the request flow var future = Future<dynamic>(() => InterceptorState(requestOptions)); // Add request interceptors to request flow interceptors.forEach((Interceptor interceptor) { var fun = interceptor is QueuedInterceptor ? interceptor._handleRequest : interceptor.onRequest; future = future.then(_requestInterceptorWrapper(fun)); });
Interceptor是通过队列保存的,队列是“FIFO”模式,也就是先添加的Interceptor会先处理,后续添加的会覆盖之前的处理,通常会在onRequest中添加一些headers等操作,onResponse或onError中对结果处理成调用者想要的方式,onResponse和onError是互斥的
class Interceptor { // 发送请求前拦截 void onRequest( RequestOptions options, RequestInterceptorHandler handler, ) => handler.next(options); //在结果返回给调用者前拦截 void onResponse( Response response, ResponseInterceptorHandler handler, ) => handler.next(response); //发生错误返回给调用者时拦截 void onError( DioError err, ErrorInterceptorHandler handler, ) => handler.next(err);}
可以看到,在遍历拦截器时会判断是否是QueuedInterceptor这个类型,可以理解为一种串行机制。在多个请求同时进入拦截器时只允许一个请求先执行,常用在请求某种token时,因为其他请求可以复用,不用重复请求,这个暂时不深究,正常情况下直接执行onRequest:
/// The callback will be executed before the request is initiated. /// /// If you want to continue the request, call [handler.next]. /// /// If you want to complete the request with some custom data, /// you can resolve a [Response] object with [handler.resolve]. /// /// If you want to complete the request with an error message, /// you can reject a [DioError] object with [handler.reject]. void onRequest( RequestOptions options, RequestInterceptorHandler handler, ) => handler.next(options);
onRequest执行了RequestInterceptorHandler的next方法,实际上是一个Completer对象的complete方法。
/// Handler for request interceptor.class RequestInterceptorHandler extends _BaseHandler { /// Continue to call the next request interceptor. void next(RequestOptions requestOptions) { _completer.complete(InterceptorState<RequestOptions>(requestOptions)); _processNextInQueue?.call(); } ...}
class _BaseHandler { final _completer = Completer<InterceptorState>(); void Function()? _processNextInQueue; Future<InterceptorState> get future => _completer.future; bool get isCompleted => _completer.isCompleted;}
接着来看_requestInterceptorWrapper这个方法:
// Convert the request interceptor to a functional callback in which // we can handle the return value of interceptor callback. FutureOr Function(dynamic) _requestInterceptorWrapper( InterceptorSendCallback 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 as RequestOptions, requestHandler); return requestHandler.future; }); }), ); } else { return state; } }; }
typedef InterceptorSendCallback = void Function( RequestOptions options, RequestInterceptorHandler handler,);
这里是把函数的回调作为方法的参数,这样就实现了把拦截器转换为函数回调,这里做了一层判断,如果state.type 等于 next 的话,那么会增加一个监听取消的异步任务listenCancelForAsyncTask,并把cancelToken传递给了这个任务,接下来他会检查当前的这个拦截器请求是否入队,最后定义了一个请求拦截器RequestInterceptorHandler,并赋值给InterceptorSendCallback的handler,它的future属性,也就是_completer对象的complete方法,即执行拦截器的onRequest。
之后继续对请求流进行操作,添加请求分发。
// Add dispatching callback to request flow future = future.then(_requestInterceptorWrapper(( RequestOptions reqOpt, RequestInterceptorHandler handler, ) { requestOptions = reqOpt; _dispatchRequest(reqOpt) .then((value) => handler.resolve(value, true)) .catchError((e) { handler.reject(e as DioError, true); }); }));
// Initiate Http requests 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<T>( 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)) as T?; if (forceConvert) { headers.set(Headers.contentTypeHeader, contentType); } } else { await responseBody.stream.listen(null).cancel(); } checkCancelled(cancelToken); if (statusOk) { return checkIfNeedEnqueue(interceptors.responseLock, () => ret); } else { throw DioError( requestOptions: reqOpt, response: ret, error: 'Http status error [${responseBody.statusCode}]', type: DioErrorType.response, ); } } catch (e) { throw assureDioError(e, reqOpt); } }
对每一次Http请求进行初始化:
在我们调用get/post等方法时,都会进入到request方法,request 方法主要负责对请求配置参数的统一处理,并调用了fetch 方法,而 fetch 中是构建请求流、添加拦截器、请求分发的操作。下一次我们接着这个流程,看下设置cookie的原理。
--结束END--
本文标题: Flutter网络请求框架Dio源码分析以及封装(一)--请求流程分析
本文链接: https://www.lsjlt.com/news/388560.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0