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办法里首要干了两件事
- 兼并BaseOptions和外部传进来的恳求参数
- 绑定上传、下载、撤销等回调到恳求目标
然后将处理好的恳求参数交给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的源码剖析在要害点写了注释,各位同学自行享受。像这种比较长的源码剖析一直在考虑该如何写,分块解析怕代码逻辑关联不上来,索性直接全部拿来写上注释。再经过实例问题解说代码,各位假如有好的建议去剖析代码,请在谈论区留言
关于阻拦器的几个实例问题
-
阻拦器不手动调用RequestInterceptorHandler.next会怎么样?
答:依据我们整理的流程来看,Dio在建议恳求时会依据阻拦器生成一个future的链表,future只有等到上一个履行完才会履行下一个。假如阻拦器里不手动调用next则会停留在链表中的某个节点。 -
阻拦器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;
}
};
}
- 在onRequest里抛出反常,后续的onRequest和onResponse还会回调吗?
- 在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替换下即可。