前言
对新技术保持敏锐是一个程序猿的基本素养,其中Flutter是新技术中的佼佼者,那么对于晦涩难懂的框架源码我们该如何学习呢?
以Flutter中的Dio为例,如果想要了解Dio的源码设计,我们从那里开始分析?直接阅读源码?
相信大多数同学阅读源码都存在如下的问题:
- 该从哪处下手
- 学了忘,忘了学
造成这样的原因是因为你没有一个结构化思维,没有理解网络框架的本质。
如果你仍处于以上这种状态,那么接着往下看,这篇文章将非常适合你,我将带领大家对Dio框架进行抽丝剥茧,你可以学到的不仅是框架的设计,同样也是网络框架的基本设计规范,相信大家掌握以后不管是Android还是iOS亦或是Web的网络框架,你都能用同样的系统思维方式去分析。
导读
文章基于Dio 4.0.6进行分析,根据通用的网络请求流程来一步一步拆解Dio框架,不拘泥于源码解析,偏重于系统性分析:
- 第一章 Dio请求流程介绍 系统的向读者介绍通用的的网络请求流程和Dio的架构设计,让读者对其有一个整体的结构化思维。
- 第二章 输入数据 介绍Dio框架中输入数据时涉及的基本类。
- 第三章 数据处理 基于第二章中输入的数据分析Dio中的数据处理流程。
- 第四章 输出数据 简述Dio将数据输出给调用者的过程。
- 第五、六章 基于前面的内容做一个总结。
目录
1.Dio请求流程介绍
网络框架的请求流程和计算机的运行原理本质上是一样的,主要分为三大流程:
- 输入数据
- 数据处理
- 输出数据
输入数据就像是计算机通过I/O接口输入指令及数据,数据处理好比CPU和内存对数据进行存储运算,输出数据则是通过I/O接口输出到电脑屏幕上。
网络框架图
Dio框架图
Dio中 输入数据 通过App初始化配置和调用get或post方法传入的参数的组合。
数据处理 的过程相对复杂,但本质上就是固定步骤的指令操作数据的过程。
输出数据是将数据处理完成后返回给调用者。
下三章我们将分析下这三大流程的具体逻辑。
2.输入数据
Api入口
介绍Dio框架常用Api
Dio框架输入数据主要有下面两个入口:
1.初始化配置
2.业务调用
初始化配置 相当于数据预加载的过程,通常在App启动时进行,其本质是为了在 业务调用 时尽量简单,性能更佳。
Dio中 初始化配置 示例:
final dio = Dio(); // 初始化默认的配置
void configureDio() {
// 通过BaseOptions设置默认配置
dio.options.baseUrl = 'https://api.pub.dev';
dio.options.connectTimeout = Duration(seconds: 5);
dio.options.receiveTimeout = Duration(seconds: 3);
//添加拦截器
//自定义拦截器,请求、返回、异常三种拦截器
dio.interceptors.add(RequestInterceptor());
dio.interceptors.add(ErrorInterceptor());
dio.interceptors.add(ResponceInterceptor());
//日志记录
dio.interceptors.add(LogInterceptor(responseBody: true));
// 设置代理
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
// config the http client
client.findProxy = (uri) {
//proxy all request to localhost:8888
return "PROXY $proxy";
};
// you can also create a new HttpClient to dio
// return new HttpClient();
};
// 通过BaseOptions构造函数设置
final options = BaseOptions(
baseUrl: 'https://api.pub.dev',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
);
final anotherDio = Dio(options);
}
业务调用 示例:
发送一个get请求
void request() async {
Response response;
//get方法的两种方式
response = await dio.get('/test?id=12&name=dio');
print(response.data.toString());
// 另外一种
response = await dio.get(
'/test',
queryParameters: {'id': 12, 'name': 'dio'},
);
print(response.data.toString());
}
//调用时也可通过Options设置一些基本属性
Options options = Options(
//连接服务器超时时间,单位是毫秒.
sendTimeout: this.mConnectTimeout,
//响应流上前后两次接受到数据的间隔,单位为毫秒。
receiveTimeout: this.mReceiveTimeout,
//Http请求头.
headers: mHeaders,
contentType: "application/json",
//接受四种类型 `json`, `stream`, `plain`, `bytes`. 默认是 `json`,
responseType: this.mResponseType,
);
//发送一个post请求
response = await dio.post('/test', data: {'id': 12, 'name': 'dio',
, options: options});
}
通过代码示例,发现Dio输入数据时主要涉及的类有以下几种:
Dio
Dio类作为框架的入口类,主要有如下两个功能:
- 组合了不同类型的配置数据
- 提供get、post等业务调用方法
具体代码如下所示:
import 'entry_stub.dart'
// 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';
abstract class Dio {
//工厂方法创建实现类
factory Dio([BaseOptions? options]) => createDio(options);
//下面是基础配置类
late BaseOptions options;
Interceptors get interceptors;
late HttpClientAdapter httpClientAdapter;
late Transformer transformer;
//下面是主要调用方法,还包含put、download等方法
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
});
Future<Response<T>> post<T>(
String path, {
data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
});
}
可以看到,Dio是一个抽象类,createDio是工厂方法,其实现类通过平台区分导入,在移动端中导入的是 dio_for_native.dart,这个文件中 createDio创建的是DioForNative对象。
class DioForNative with DioMixin implements Dio {
}
DioForNative 中提供的get、post等方法主要实现在其继承的DioMixin中。
除了基本的配置数据外,Dio还主要包含get、post方法,在调用这两个方法时传入的参数数据主要包含如下:
- path--请求路径
- params或data--请求参数
- options--请求时传入的配置可以和基础的BaseOption合并,调用时传入的数据会覆盖之前设置的数据
- cancelToken--用于取消当前请求的标记
- onSendProgress和onReceiveProgress是记录一个发送和获取数据的过程,可用于加载进度条,打印日志等
BaseOption、Option
BaseOption和Option作用类似,都用做基础数据配置,最终会合并成一个对象RequestOptions。
Interceptor
Interceptor的设计借鉴的是Android OkHttp中插值器形式,使用的是责任链模式,这种优秀的设计使得开发不仅能介入网络请求请求前的数据处理,还可以对返回结果或错误异常进行拦截。
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);
}
Interceptor在Dio框架中是通过队列保存的,队列是“FIFO”模式,也就是先添加的Interceptor会优先处理,后续添加相同的逻辑会覆盖之前的处理,通常会在onRequest中添加一些headers等操作,onResponse或onError中对结果处理成调用者想要的方式,记住onResponse和onError是互斥的,只有一个会被调用。
DefaultHttpClientAdapter
DefaultHttpClientAdapter是默认的网络请求配置适配类,这里使用适配器模式将底层网络框架的选择交给开发者,默认实现主要是基于HttpClient(Flutter官方提供的网络库),也可用来设置代理进行抓包,其中核心是fetch方法:
class DefaultHttpClientAdapter implements HttpClientAdapter {
/// [Dio] will create HttpClient when it is needed.
/// If [onHttpClientCreate] is provided, [Dio] will call
/// it when a HttpClient created.
OnHttpClientCreate? onHttpClientCreate;
HttpClient? _defaultHttpClient;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
if (_closed) {
throw Exception(
"Can't establish connection after [HttpClientAdapter] closed!");
}
var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
var reqFuture = _httpClient.openUrl(options.method, options.uri);
}
}
通过 dio.httpClientAdapter = 开发者自行实现的HttpClientAdapter开发者可自行配置网络请求部分,需要自行实现fetch方法。
第二章我们主要介绍了 Dio输入数据 时框架中主要涉及的类,但是对于 http请求 来说,这些数据还需要经过一定的 加工 才能转换成最终的http协议所需要的数据规范,那么第三章将为我们揭开Dio是如何在保证逻辑严谨的情况下进行 数据处理 的。
3.数据处理
上一章介绍输入数据和Http消息报文的数据格式并不相符,要想变成符合Http协议要求的数据格式,就需要将开发者输入的数据转换成如下图规范的消息数据格式,而网络框架数据处理的本质便是如此。
http的消息结构如下:
网络框架 数据处理 流程本质就是get或post方法调用过程,简单的流程如下:
1.将BaseOption和Options合并成RequestOptions对象
2.遍历并调用所有拦截器的onRequst方法,这个方法中重要的参数就是RequestOptions
3.调用dispatch方法开始传输数据
4.遍历并调用所有拦截器onResponce方法,这个方法中有一个重要的参数就是Responce
5.遍历并调用所有拦截器onError方法,这个方法中有一个重要的参数就是DioError
6.最后将结果Response返回给调用者
3.1 数据预处理
数据预处理是将输入的数据进行打包、合并的过程其中拦截器中还可以添加、修改请求配置数据。对应到流程中的1,2两个步骤。
Dio get和post实现方法是在DioMixin类中,可以看到不管是get还是post,都会调用到request方法中,实际开发者直接调用request也是可以的,只是需要多一个请求方法method的参数。
将BaseOption和Options合并成RequestOptions对象
@override
Future<Response<T>> request<T>(
String path, {
data,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
Options? options,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
options ??= Options();
//将BaseOptions和Options合并成RequestOptions对象
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对象和调用时传入的Options对象合并成RequestOptions对象,而RequestOptions将作为后续数据传递过程中的重要媒介。这里Options类有点多,容易对分析框架造成干扰,为避免影响分析,读者请仔细甄别。
遍历并调用所有拦截器的onRequst方法,这个方法中重要的参数就是RequestOptions
@override
Future<Response<T>> fetch<T>(RequestOptions requestOptions) async {
// 对初始化中所有拦截器进行遍历并调用onRequest方法
interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleRequest
: interceptor.onRequest;
future = future.then(_requestInterceptorWrapper(fun));
});
// 请求传输数据部分
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);
});
}));
// 对初始化中所有拦截器进行遍历并调用onResponse方法
interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleResponse
: interceptor.onResponse;
future = future.then(_responseInterceptorWrapper(fun));
});
// 对初始化中所有拦截器进行遍历并调用onError方法
interceptors.forEach((Interceptor interceptor) {
var fun = interceptor is QueuedInterceptor
? interceptor._handleError
: interceptor.onError;
future = future.catchError(_errorInterceptorWrapper(fun));
});
}
第一步合成RequestOptions对象后,RequestOptions对象就作为第二步拦截器中onRequest方法中的重要参数,每一个拦截器都可以对请求配置数据options进行拦截添加或修改。
数据预处理完成后接下来下来就需要知道数据传输的地址了,而弄清楚地址的第一步就是Uri解析
3.2 Uri解析
输入的数据中有一个URL参数,它是BaseUrl和path的组合,比如 www.baidu.com/home/index?id=1234&name=baidu 。
而Uri解析的目的是为了得到如下地址相关的数据:
- http或者https,区分协议类型
- 域名如 www.baidu.com ,用于DNS解析,指定服务器网络地址和主机地址的关键参数
- path如 /home/index,指定获取后台数据的路径
- params如id、name等,针对不同接口传入不同的参数类型,主要是后来用来做逻辑判断
这些数据都保存在一个Uri对象中,,所以本质上Uri解析是将Url拆解成一个对象的形式存储,方便后续获取,Dio中关于Uri解析的关键类在RequestOptions中,关键方法是的uri get方法:
class DefaultHttpClientAdapter implements HttpClientAdapter {
/// [Dio] will create HttpClient when it is needed.
/// If [onHttpClientCreate] is provided, [Dio] will call
/// it when a HttpClient created.
OnHttpClientCreate? onHttpClientCreate;
HttpClient? _defaultHttpClient;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
if (_closed) {
throw Exception(
"Can't establish connection after [HttpClientAdapter] closed!");
}
var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
//建立TCP链接,options.uri表示
var reqFuture = _httpClient.openUrl(options.method, options.uri);
}
}
//通过options.uri便可获得所需的Uri数据
/// generate uri
Uri get uri {
var _url = path;
if (!_url.startsWith(RegExp(r'https?:'))) {
_url = baseUrl + _url;
var s = _url.split(':/');
if (s.length == 2) {
_url = s[0] + ':/' + s[1].replaceAll('//', '/');
}
}
var query = Transformer.urlEncodeMap(queryParameters, listFormat);
if (query.isNotEmpty) {
_url += (_url.contains('?') ? '&' : '?') + query;
}
// Normalize the url.
return Uri.parse(_url).normalizePath();
}
可以看到,通过对输入数据时传入的baseUrl和path参数组合,再经过Uri解析转换,就可以得到消息结构中的部分关键数据了。
但是对于操作系统支持的http协议来说,域名是一个陌生的概念,真正识别的服务器网络地址和主机地址的是ip地址和端口号,那么要想知道域名对应的服务器地址和端口号,就需要进行DNS解析,在下一节将介绍DNS解析相关的内容。
3.3 DNS解析
通过Uri解析我们得到了域名等相关的信息,但不管是操作系统还是其他网络设备,都不能识别域名而只能识别ip地址和端口号,所以需要一种能通过域名查询ip地址(如192.168.1.1)和端口号(如88)的服务,这个服务就是DNS解析。
下面我们解答关于域名和ip地址常见的两个疑问
1,为什么不直接使用ip地址和端口号?
答:当然可以用,但是因为ip地址不易分辨且难以记忆,比如“wwww.baidu.com“就比较符合正常人的思维习惯。2,Http协议为什么不直接使用域名?
答:域名占用的字节数相对ip地址更大,对整体传输的效率会打折扣,且不如ip这种数字地址容易分配。
DNS解析的过程通常在socket中,对于开发者来说并不需要直接接触,但部分优秀的框架也会自行实现DNS解析的逻辑,Dio中进行DNS解析是在HttpClient框架中的socket组件,根据平台(Android/Web)不同实现也不一,这里我们不详细展开,借助一张关于HttpClient的时序图。
在获取到ip地址和端口号以后,接下来就需要通过获取到的信息建立TCP连接了。
3.4 建立TCP连接
准备工作完成后,通过一系列调用来到DefaultHttpClientAdapter的fetch方法中,其中_httpClient.openUrl就是建立TCP连接通道的流程:
class DefaultHttpClientAdapter implements HttpClientAdapter {
/// [Dio] will create HttpClient when it is needed.
/// If [onHttpClientCreate] is provided, [Dio] will call
/// it when a HttpClient created.
OnHttpClientCreate? onHttpClientCreate;
HttpClient? _defaultHttpClient;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
if (_closed) {
throw Exception(
"Can't establish connection after [HttpClientAdapter] closed!");
}
var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
var reqFuture = _httpClient.openUrl(options.method, options.uri);
}
}
下面是时序图:
openUrl方法的参数是请求方式method(get、post等)和Uri解析后生成的uri对象,可以看出Dio框架只是收集整理数据,核心逻辑仍然在HttpClient中。
abstract class HttpClient {
static const int defaultHttpPort = 80;
@Deprecated("Use defaultHttpPort instead")
static const int DEFAULT_HTTP_PORT = defaultHttpPort;
static const int defaultHttpsPort = 443;
@Deprecated("Use defaultHttpsPort instead")
static const int DEFAULT_HTTPS_PORT = defaultHttpsPort;
Future<HttpClientRequest> openUrl(String method, Uri url);
}
HttpClient是一个抽象类,其实现在http_imp.dart文件中_HttpClient类的_openUrl方法。
Future<_HttpClientRequest> _openUrl(String method, Uri uri) {
return _getConnection(uri, uri.host, port, proxyConf, isSecure, profileData)
.then((_ConnectionInfo info) {
_HttpClientRequest send(_ConnectionInfo info) {
profileData?.requestEvent('Connection established');
return info.connection
.send(uri, port, method.toUpperCase(), info.proxy, profileData);
}
// If the connection was closed before the request was sent, create
// and use another connection.
if (info.connection.closed) {
return _getConnection(
uri, uri.host, port, proxyConf, isSecure, profileData)
.then(send);
}
return send(info);
}, onError: (error) {
profileData?.finishRequestWithError(error.toString());
throw error;
});
}
// Get a new _HttpClientConnection, from the matching _ConnectionTarget.
Future<_ConnectionInfo> _getConnection(
Uri uri,
String uriHost,
int uriPort,
_ProxyConfiguration proxyConf,
bool isSecure,
_HttpProfileData? profileData) {
Iterator<_Proxy> proxies = proxyConf.proxies.iterator;
Future<_ConnectionInfo> connect(error, stackTrace) {
if (!proxies.moveNext()) return Future.error(error, stackTrace);
_Proxy proxy = proxies.current;
String host = proxy.isDirect ? uriHost : proxy.host!;
int port = proxy.isDirect ? uriPort : proxy.port!;
return _getConnectionTarget(host, port, isSecure)
.connect(uri, uriHost, uriPort, proxy, this, profileData)
// On error, continue with next proxy.
.catchError(connect);
}
return connect(HttpException("No proxies given"), StackTrace.current);
}
通过层层方法的调用,我们最终找到真正进行TCP连接的是关于Https的SecureSocket和http的Socket的startConnect方法中
external static Future<ConnectionTask<Socket>> _startConnect(host, int port,
{sourceAddress, int sourcePort = 0});
上面的external表示Android/iOS平台有各自的socket代码实现,但最终的逻辑是相同的,有DNS解析和TCP三次握手,其中https的SecureSocket在进行TCP连接之前还增加了一个证书校验过程,也就是https的四次握手。
到这里,TCP连接通道已经建立好了,那么接下来就是数据传输过程了,下一节我们将先介绍Dio框架中基于IP协议的数据传输过程。
3.5 IP传输数据
建立好TCP通道后,IP协议开发发挥作用了,是时候传输数据了
class DefaultHttpClientAdapter implements HttpClientAdapter {
/// [Dio] will create HttpClient when it is needed.
/// If [onHttpClientCreate] is provided, [Dio] will call
/// it when a HttpClient created.
OnHttpClientCreate? onHttpClientCreate;
HttpClient? _defaultHttpClient;
@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future? cancelFuture,
) async {
if (_closed) {
throw Exception(
"Can't establish connection after [HttpClientAdapter] closed!");
}
//创建HttpClient,建立TCPl连接
var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
var reqFuture = _httpClient.openUrl(options.method, options.uri);
late HttpClientRequest request;
request = await reqFuture;
//开始IP传输数据
var future = request.close();
}
}
DefaultHttpClientAdapter中传输数据的方法入口就是request.close(很想吐槽close这个方法名),request建立TCP连接时调用_httpClient.openUrl返回的HttpClientRequest对象,调用流程如下图所示(借图):
// HttpClientRequest的实现类_HttpClientRequest中的close方法
Future<HttpClientResponse> close() {
if (!_aborted) {
// It will send out the request.
super.close();
}
return done;
}
// 上面的super.close()调用的是_HttpClientRequest间接继承的_StreamSinkImpl类中的close方法
Future close() {
if (_isBound) {
throw StateError("StreamSink is bound to a stream");
}
if (!_isClosed) {
_isClosed = true;
var controller = _controllerInstance;
if (controller != null) {
controller.close();
} else {
_closeTarget();
}
}
return done;
}
void _closeTarget() {
_target.close().then(_completeDoneValue, onError: _completeDoneError);
}
通过一系列调用,核心逻辑在 _target这个变量中,_target是StreamConsumer,HttpOutgoing则继承自StreamConsumer,所以核心逻辑就是在http_imp.dart文件中HttpOutgoing的close方法中,HttpOutgoing中保存有socket对象,可用于发送数据。
class _HttpOutgoing implements StreamConsumer<List<int>> {
Future close() {
Future finalize() {
return socket.flush().then((_) {
_doneCompleter.complete(socket);
return outbound;
}, onError: (error, stackTrace) {
_doneCompleter.completeError(error, stackTrace);
if (_ignoreError(error)) {
return outbound;
} else {
throw error;
}
});
}
var future = writeHeaders();
if (future != null) {
return _closeFuture = future.whenComplete(finalize);
}
return _closeFuture = finalize();
}
}
socket.flush()就是发送数据的过程,数据通过TCP建立的通道传输给服务器,服务器接收到客户端输入的数据后,也需要对数据进行并将数据返回给客户端。
3.6 服务器接收并处理数据
服务器接收数据相当于服务器的数据输入,只不过数据是经过客户端Dio框架加工而成。而服务器拿到数据也需要对数据进行处理,处理流程如下:
由于涉及到后台逻辑,此处不做详细展开,有兴趣的同学可以自行google。
3.7 客户端接收并处理返回数据
上一节中后台通过TCP返回数据,客户端通过TCP接收数据,在接收到数据以后就需要对数据进行处理。
首先我们来看下标准的返回数据格式:
可以看到通过socket返回的数据格数如上图所示,如果此时直接返回给调用者,调用方需要不停的进行io操作读取字符串,使用起来异常麻烦,这就需要框架对数据进行加工处理,返回调用者自己想要的数据形式。
在建立TCP连接openUrl时创建了_HttpClientConnection对象,构造函数为Socket注册了onData事件的回调,即_HttpParser用于接收后台返回的数据。因此每当Socket有数据进来时,都会触发_HttpParser的onData进行处理。
_HttpClientConnection(this.key, this._socket, this._httpClient,
[this._proxyTunnel = false, this._context])
: _httpParser = new _HttpParser.responseParser() {
_httpParser.listenToStream(_socket);
_subscription = _httpParser.listen((incoming) {......}
处理完成后,通过层层调用至_HttpClientRequest的_responseCompleter,最终获取到HttpClientResponse对象。
4.输出数据
4.1 返回responce并断开连接
上一章数据处理完成后最终得到的是HttpClientResponse对象,Dio框架则通过将HttpClientResponse包装成Response对象返回给调用者,如果发生网络请求错误或异常则会将错误包装成DioError对象,这其中还涉及到Intercetor队列中onResponce和onError的拦截处理。
void onResponse(
Response response,
ResponseInterceptorHandler handler,
) =>
handler.next(response);
void onError(
DioError err,
ErrorInterceptorHandler handler,
) =>
handler.next(err);
5.其他
至此Dio的大致框架已经清晰,文章并不拘泥了源码的细节,主要是从系统的网络请求流程上分析,除上述流程外,读者对于框架中可能还会存在如下部分疑惑:
- Cookie和Session
- 加密Basice和Digest
- Gzip
- 代理、网关和隧道
- 缓存
- https
限于篇幅,本篇并上面部分做详细介绍,下一篇笔者将基于Dio框架对以上部分做详细分析。
6.总结
综上所述,我们首先介绍了Dio框架的数据输入流程,有初始化配置和业务调用两类,其次介绍了框架对输入数据进行处理的过程,最后就需要将处理完的数据输出给调用者
输入数据
一般分为两种:
- 初始化配置
- 业务调用
数据处理 的过程比较复杂,详细在后续讲解,
Dio的数据处理流程大致分为如下7个步骤:
- 数据预处理
- URL解析
- DNS解析
- 建立TCP连接
- IP传输数据
- 服务器接收并处理数据
- 客户端接收并处理返回数据
输出数据是将完整的数据返回给调用端
文章对Dio框架进行了一个基本的解构,主要是想让读者拥有一个网络框架系统性的思维,你是否想过,Android/iOS/Web的网络框架设计流程是否也是如此?根据上述流程,基于现有的技术栈能否自己手动撸一个网络框架?答案是一定的。
建议对照流程手动分析Dio框架