注册
dio

DIO源码浅析——揭开面纱后是你印象中的样子吗

DIO源码解析


dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等


Dio版本号:4.0.6


基本使用


final dio = Dio();
final result = await dio.get('https://xxxx.ccc');

源码分析


源码分析通常情况下是一个逆推的过程,首先熟悉api的使用,然后通过api的调用思考功能是如何实现的。这里就从Dio()和get()方法作为切入点,看看Dio的内部实现。切忌直接下载源码通读一遍,容易找不到重点。


Dio


查看源码发现Dio是个抽象类,定义了Dio支持的所有功能。有面向对象经验的应该都知道抽象类无法直接实例化,但是这里却可行其实这是dart的factory语法糖,方便开发者使用工厂模式创建对象。


简化的Dio代码,例举出比较具有代表性的属性和方法。


abstract class Dio {
factory Dio([BaseOptions? options]) => createDio(options);
late BaseOptions options;

Interceptors get interceptors;

late HttpClientAdapter httpClientAdapter;

late Transformer transformer;
...
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
});

Future<Response<T>> request<T>(
String path, {
data,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
Options? options,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
});
...
}

1. 工厂方法创建Dio对象


factory Dio([BaseOptions? options]) => createDio(options);
这是上面提到的为何抽象类能实例化对象,就是个语法糖起了作用,跟进去发现createDio(options)这个方法定义在entry_stub.dart里,并且是个空实现。先不深究,反正最后的实现要么是DioForBrowser、要么是DioForNative至于加了什么魔法不是本期的重点

2. BaseOptions


options保存通用的请求信息,比如baseUrl、headers、超时时间等参数。用来设置全局配置。

3. Interceptors


这就是所有http请求框架里都会用到的拦截器在Dio里的实现,里面的关键源码一个线性列表存储所有的拦截器,和重写的下标操作符。在发起请求时会使用Interceptors存储的拦截器按顺序进行拦截处理。

4. HttpClientAdapter


HttpClientAdapter是Dio真正发起请求的地方,他是一个抽象类,实现类通过依赖注入注入进来。Dio这里运用了职责分离的思想进行接耦,Dio定义请求方法和请求拦截等操作,使用HttpClientAdapter建立连接发起请求。这样设计的好处在于,如若对网络请求库有改动的需求可以自己实现一个HttpClientAdapter子类进行替换就行,无需改动原有代码。

5. Transformer


Transformer的作用是在请求前后可以对请求参数,和请求结果进行修改。在请求时生效在请求拦截器之后,响应时发生在响应拦截器之前。对于了解过洋葱模型的同学来说,这很好理解,Transformer处于Interceptors的里面一层。

6. 诸如get、post、request...方法


Dio里定义的方法全部都是抽象方法,需要子类来实现。这里的作用是定义一个通用的请求接口,包含http常用的一些方法。

按照程序看完抽象类就该看实现类了,Android Studio里在抽象类Dio的左边有个向下的箭头,点击一下发现有三个子类。


1. DioMixin


DioMixin也是一个抽象类,实现了Dio接口几乎所有的方法,只有两个属性未实现:




  • HttpClientAdapter




  • BaseOptions


    这两个属性交由DioForNative和DioForBrowser各自进行注入。




class DioForBrowser with DioMixin implements Dio {
DioForBrowser([BaseOptions? options]) {
this.options = options ?? BaseOptions();
httpClientAdapter = BrowserHttpClientAdapter();
}
}

class DioForNative with DioMixin implements Dio {
singleton.
DioForNative([BaseOptions? baseOptions]) {
options = baseOptions ?? BaseOptions();
httpClientAdapter = DefaultHttpClientAdapter();
}
}

这个很好理解,因为native和web的发起请求肯定是不一样的。dio默认使用的http_client来自于dart_sdk暂未直接支持web。所以需要通过创建不同的http_client适配web和native。


好了,到这里基本确定DioMixin这个类就是Dio最重要的实现类了。DioForNative和DioForBrowser只是针对不同平台的适配而已。继续分析DioMixin:


同样从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方法里设置了一下method为‘GET’,然后把参数全数传递给了request方法,继续看看request方法


  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;
...
return fetch<T>(requestOptions);
}

request方法里主要干了两件事



  1. 合并BaseOptions和外部传进来的请求参数
  2. 绑定上传、下载、取消等回调到请求对象

然后将处理好的请求参数交给fetch方法。继续跟进(前方高能,fetch方法是dio的核心了)


  Future<Response<T>> fetch<T>(RequestOptions requestOptions) async {
final stackTrace = StackTrace.current;

if (requestOptions.cancelToken != null) {
requestOptions.cancelToken!.requestOptions = requestOptions;
}

//这里是根据请求参数,简单判断下返回的type。意思是T如果声明了类型,要么是普通文本要么是json对象
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}

//请求拦截包装器:interceptor就是拦截器里的onRequest方法,作为参数传过来
//1.开始分析这个包装器的作用,仅当状态处于next时开始工作
//2.listenCancelForAsyncTask方法作用是,cancelToken的Future和请求的拦截器Future同时执行,cancelToken先执行完成的话就抛出异常终止请求。
//3.创建一个requestHandler,并调用interceptor方法(在request这里就是onRequest方法),然后返回requestHander.future(了解Completer的同学应该都知道,这是可以手动控制future的方法)。这就解释了为何拦截器里的onRequest方法,开发者需要手动调用next等方法进入下一个拦截器。

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;
}
};
}

//响应拦截包装器:
//实现方式参考_requestInterceptorWrapper基本类似,但是要注意这里放宽了state的条件多了一个resolveCallFollowing,这个后续再讲
FutureOr<dynamic> Function(dynamic) _responseInterceptorWrapper(
InterceptorSuccessCallback interceptor,
) {
return (_state) async {
var state = _state as InterceptorState;
if (state.type == InterceptorResultType.next ||
state.type == InterceptorResultType.resolveCallFollowing) {
return listenCancelForAsyncTask(
requestOptions.cancelToken,
Future(() {
return checkIfNeedEnqueue(interceptors.responseLock, () {
var responseHandler = ResponseInterceptorHandler();
interceptor(state.data as Response, responseHandler);
return responseHandler.future;
});
}),
);
} else {
return state;
}
};
}

// 错误拦截包装器
FutureOr<dynamic> Function(dynamic, StackTrace) _errorInterceptorWrapper(
InterceptorErrorCallback interceptor) {
return (err, stackTrace) {
if (err is! InterceptorState) {
err = InterceptorState(
assureDioError(
err,
requestOptions,
),
);
}

if (err.type == InterceptorResultType.next ||
err.type == InterceptorResultType.rejectCallFollowing) {
return listenCancelForAsyncTask(
requestOptions.cancelToken,
Future(() {
return checkIfNeedEnqueue(interceptors.errorLock, () {
var errorHandler = ErrorInterceptorHandler();
interceptor(err.data as DioError, errorHandler);
return errorHandler.future;
});
}),
);
} else {
throw err;
}
};
}

// Build a request flow in which the processors(interceptors)
// execute in FIFO order.

// Start the request flow
// 初始化请求拦截器第一个元素,第一个InterceptorState的type为next
var future = Future<dynamic>(() => InterceptorState(requestOptions));

// Add request interceptors to request flow
// 这是形成请求拦截链的关键,遍历拦截器的onRequest方法,并且使用_requestInterceptorWrapper对onRequest方法进行包装。
//上面讲到_requestInterceptorWrapper返回的是一个future
//future = future.then(_requestInterceptorWrapper(fun));这段代码就是让拦截器形成一个链表,只有上一个拦截器里的onRequest内部调用了next()才会进入下一个拦截器。
interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleRequest
: interceptor.onRequest;
future = future.then(_requestInterceptorWrapper(fun));
});

// Add dispatching callback to request flow
// 发起请求的地方,发起请求时也处在future链表里,方便response拦截器和error拦截器的处理后续。
//1. reqOpt即,经过拦截器处理后的最终请求参数
//2. _dispatchRequest执行请求,并根据请求结果判断执行resolve还是reject
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);
});
}));

//request处理执行完成后,进入response拦截处理器,遍历形成response拦截链表
// Add response interceptors to request flow
interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleResponse
: interceptor.onResponse;
future = future.then(_responseInterceptorWrapper(fun));
});

// 请求拦截链表添加完成后,添加错误的链表
// Add error handlers to request flow
interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleError
: interceptor.onError;
future = future.catchError(_errorInterceptorWrapper(fun));
});

// Normalize errors, we convert error to the DioError
// 最终返回经过了拦截器链表的结果
return future.then<Response<T>>((data) {
return assureResponse<T>(
data is InterceptorState ? data.data : data,
requestOptions,
);
}).catchError((err, _) {
var isState = err is InterceptorState;

if (isState) {
if ((err as InterceptorState).type == InterceptorResultType.resolve) {
return assureResponse<T>(err.data, requestOptions);
}
}

throw assureDioError(
isState ? err.data : err,
requestOptions,
stackTrace,
);
});
}

关于fetch的源码分析在关键点写了注释,各位同学自行享用。像这种比较长的源码分析一直在思考该如何写,分块解析怕代码逻辑关联不上来,索性直接全部拿来写上注释。再通过实例问题解释代码,各位如果有好的建议去分析代码,请在评论区留言


关于拦截器的几个实例问题




  1. 拦截器不手动调用RequestInterceptorHandler.next会怎么样?
    答:根据我们梳理的流程来看,Dio在发起请求时会根据拦截器生成一个future的链表,future只有等到上一个执行完才会执行下一个。如果拦截器里不手动调用next则会停留在链表中的某个节点。




  2. 拦截器onError中可以做哪些操作?




interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleError
: interceptor.onError;
future = future.catchError(_errorInterceptorWrapper(fun));
});

对这段代码进行分析,可以看到onError的执行,是在future链表的catchError(捕获future里的错误)方法中进行的。


onError的方法签名如下
void onError(DioError err,ErrorInterceptorHandler handler)
可以在onError方法调用 next、resolve、reject三个方法处理。

next 使用的completeError,会让future产生一个错误,被catch到交给下一个拦截器处理。


void next(DioError err) {
_completer.completeError(
InterceptorState<DioError>(err),
err.stackTrace,
);
_processNextInQueue?.call();
}

resolve 使用complete会正常返回数据,不会触发catchError,所以跳过后续的onError拦截器


void resolve(Response response) {
_completer.complete(InterceptorState<Response>(
response,
InterceptorResultType.resolve,
));
_processNextInQueue?.call();
}

reject 和next代码类似,但是设置了状态为InterceptorResultType.reject,结合_errorInterceptorWrapper代码看,包装器里只处理err.type == InterceptorResultType.next ||
err.type == InterceptorResultType.rejectCallFollowing条件,其他状态直接抛出异常。所以reject的效果就是抛出错误直接完成请求


  void reject(DioError error) {
_completer.completeError(
InterceptorState<DioError>(
error,
InterceptorResultType.reject,
),
error.stackTrace,
);
_processNextInQueue?.call();
}
//error包装器
FutureOr<dynamic> Function(dynamic, StackTrace) _errorInterceptorWrapper(
InterceptorErrorCallback interceptor) {
return (err, stackTrace) {
if (err is! InterceptorState) {
err = InterceptorState(
assureDioError(
err,
requestOptions,
),
);
}
//仅会处理InterceptorResultType.next和InterceptorResultType.rejectCallFollowing,而reject的类型是reject,所以直接执行elese的throw
if (err.type == InterceptorResultType.next ||
err.type == InterceptorResultType.rejectCallFollowing) {
return listenCancelForAsyncTask(
requestOptions.cancelToken,
Future(() {
return checkIfNeedEnqueue(interceptors.errorLock, () {
var errorHandler = ErrorInterceptorHandler();
interceptor(err.data as DioError, errorHandler);
return errorHandler.future;
});
}),
);
} else {
throw err;
}
};
}


  1. 在onRequest里抛出异常,后续的onRequest和onResponse还会回调吗?
  2. 在onResponse里抛出异常,后续的onResponse还会回调吗?

答:回顾一下请求的流程,发起请求->onRequest(1)->onRequest(2)->onRequest(3)->http请求->onResponse(1)->onResponse(2)->onResponse(3)->catchError(1)->catchError(2)->catchError(3)。

这就很明显无论是onRequest还是onResponse抛出异常都会被catchError(1)给捕获,跳过了后续的onRequest和onResponse。

补充



  • 在requestWrapper、responseWrapper、errorWrapper里都可以看到listenCancelForAsyncTask,第一个参数是cancelToken。这是因为Dio的取消请求是在拦截器里进行的,只要请求还未走完拦截器就可以取消请求。这就有个新的问题,如果咱们未设置拦截器取消请求就无法使用了吗?显然不是。在发起请求的时候还会把cancelToken再次传递进去,监听是否需要取消请求,如果取消的话就关闭连接,感兴趣的同学自行查看相关源码。

  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,
);
...
}


  • InterceptorResultType在拦截器里发挥的作用,上面也提过了其实就是在调用InterceptorHandler的next,resolve,reject时设着一个标记,用于判断是继续下一个拦截器还是跳过后续拦截方法

2. DioForNative 和 DioForBrowser


下面这段代码是DioMixin发起请求的地方,可以看到真正执行http请求的是HttpClientAdapter的fetch方法。那DioForNative和DioForBrowser是针对不同平台的实现,那最简单的方法就是对fetch方法进行定制就好了。上面也提到了他们各自创建了不同的HttpClientAdapter,感兴趣的同学可以看看BrowserHttpClientAdapter和DefaultHttpClientAdapter


 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,
);
...
}

总结


Dio也是一个网络封装库,本身并不负责建立http请求等操作。除此之外还集成了请求拦截,取消请求的功能。采用了面向接口的方式,所以替换http请求库代价很小,只需要自己实现HttpClientAdapter替换下即可。


作者:宇宙星辰老祖
链接:https://juejin.cn/post/7202793493826781241
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册