一、概述
OkHttp 对于 Android 开发人员来说想必是人尽皆知的一个网络请求框架了,问世之初就火爆了 Android 开发圈,其优异的设计更是让不少技术大佬赞不绝口,由其衍生的各种基于 OkHttp 的网络框架也是层出不穷,同时各种对于 OkHttp 源码分析文章也是数不胜数,更是成为了面试常问的问题,可以说市面上大多 App 都在使用着这套框架。而且 Android 自 6.0 开始也将内部默认的 HttpUrlConnection 换为了 OkHttp。更是确立了 OkHttp 在 Android 开发生态中的地位。
对于每天都在使用的框架,如果只停留在使用而不了解它基本的工作原理,显然是只见树木不见森林的做法,也不利于自我提高,而互联网的包容和开放,更是让我们可以轻松的看到这种优秀的源码,了解大神的思维以及设计一个优秀框架的思想。毕竟站在巨人的肩膀上才能看到更远处的风景。
本文将会从使用着手,不会太纠缠于细枝末节,着重分析 OkHttp 从发起请求到获取响应的总体脉络流程,代码会进行适当精简,只展示核心逻辑
二、一个简单的请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://raw.github.com/square/okhttp/master/README.md")
.build();
Response response = client.newCall(request).execute()
System.out.println("OKHTTP : " + response.body().string());
上述代码是简化后的官方 GET 同步请求的例子,也是我们日常使用的方式,不同的是异步调用的是 enqueue 而已,这么几行代码就发起了一个网络请求,我是着实有点好奇 OkHttp 内部到底做了什么处理,老规矩,让我们先看下 OkHttp 的流程图
通过上图我们可以看到整个流程的关键节点由以下几个核心类组成
OkHttpClient
顾名思义 OkHttpClient 为我们创建一个请求的客户端,内部对 OkHttp 各项参数进行了默认初始化,同时对 OkHttp 的处理逻辑进行统一管理,它通过 Builder 构造器生成,而根据 OkHttp 的官方建议,对于使用来说 OkHttpClient 在全局的实例一个就够了,无须创造多个造成资源浪费
Request 和 Response
Request 对象是我们要发送的具体请求,通过源码我们可以发现,它也是通过 Builder 进行构建,内部包含了符合 Http 协议的必要配置参数
// Request 的构造函数
Request(Builder builder) {
this.url = builder.url; // 请求的 url 地址
this.method = builder.method; // 请求方法 get/pos
this.headers = builder.headers.build(); //请求头
this.body = builder.body; //请求体
this.tags = Util.immutableMap(builder.tags); //请求的 tag 标记用于取消请求
}
Response 则是请求的结果信息,通过源码我们可以看到与 Request 如出一辙,也是通过 Builder 进行构建
//Response 构造函数,省略部分代码
Response(Builder builder) {
this.request = builder.request;//发起的请求
this.protocol = builder.protocol;//协议
this.code = builder.code;// 响应码
this.message = builder.message; //响应信息
this.headers = builder.headers.build(); //响应头
this.body = builder.body //响应body
}
RealCall
RealCall 正如它的名字一样,是真正发起实际请求并处理请求内部一系列逻辑的类,所有的请求都会经过它的处理和调度,所以接下来通过部分代码让我们看看 RealCall 到底做了些什么
RealCall 的实例化过程
//这是OkHttp获取请求结果的一行代码
//这里 OkHttpCient 将构建好的 request 放入 newCall
//函数中并直接执行
Response response = client.newCall(request).execute()
//OkHttpClient 的 newCall 函数主要作用是将当前
//OkHttpClient 和 Request 的引用传给 RealCall
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
// RealCall 类的静态 newRealCall 方法,这里是真正创建
//RealCall 实例对象的例子
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
getResponseWithInterceptorChain 方法
创建完 RealCall 的实例后,我们就可以进行 execute 和 enqueue 操作,也就是同步可异步的请求,但如果我们继续跟进代码就会发现无论是同步还是异步最终都会调用 getResponseWithInterceptorChain() 方法来获取 response,而区别就是同步方法是直接调用,异步方法则是通过封装了一个 AsyncCall 线程来调用。所以我们可以基本确定整个 OkHttp 的核心请求逻辑应该就隐藏在这个方法当中,让我们来看下这方法的源码
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
getResponseWithInterceptorChain 方法的内部逻辑还是很简单的,主要是构建了一个拦截器的集合,将所有的拦截器按照顺序依次添加,然后创建了一个 RealInterceptorChain 实例,通过 proceed 方法按照顺序执行各个拦截器逻辑,跟进源码继续看看
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//省略若干代码...
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//省略若干代码...
return response;
}
proceed 方法内部做的事情也并不复杂,总结起来就是这个方法会根据 interceptors.size 来进行一个循环的拦截器调用(具体从哪里循环后面会讲到),在执行拦截器之前会创建一下拦截器的 RealInterceptorChain 实例,同时 index 也会递增,然后执行当前的拦截器链条并将下一个链条传入,整个责任链就是以这种内嵌的方式层层执行,直到所有链条上的拦截器执行完毕为止
其实从上面的代码我们也可以清晰的看出 OkHttp 与我们以往的网络请求框架的明显区别,以往的网络请求框架其请求过程基本都是封装好的很难做出改变,而 OkHttp 则将请求的逻辑以拦截器的方式切成一个个独立的执行单元,然后通过责任链的设计模式将其串联成一个整体,最终通过层层的处理获取到我们想要的响应。而且拦截器对外是开放的,这使得开发者可以在请求的过程中根据需求定制属于自己的拦截器,可以说这也是 OkHttp 为什么备受推崇的原因之一。
二、OkHttp 的拦截器
从上面的分析中我们可以看到 OkHttp 默认为我们提供了五个拦截器如下
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- CallServerInterceptor
接下来我们依次来分析一下各个拦截器的内部逻辑
2.1、RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor 主要是用来处理连接失败过程中的常见的异常和重定向问题,让我们看下它的核心代码
Request request = chain.request();
//下一个待执行的拦截器链条
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
//这里开启了一个死循环,也是从这里不断的层层执行责任链条上的每一个
//拦截器
while (true) {
//检查连接的准备状态,如果是重复请求或正在请求则终止连接
transmitter.prepareToConnect(request);
//省略部分代码...
Response response;
boolean success = false;
try {
//执行下一个拦截器
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
//当请求发生异常时会尝试重新连接.
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// //当请求发生异常时会尝试重新连接.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
//重试失败,则释放所有资源
transmitter.exchangeDoneDueToException();
}
}
//省略若干代码...
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
//如果没有发生异常则对响应的 code 进行判断,如果是 30X 则构建新的
//request 进行重定向操作,感兴趣的可以看下源码,不是很复杂
Request followUp = followUpRequest(response, route);
//省略若干代码...
}
代码中的注释解释的很清晰 ,这里在着重说一下 recover 这个方法,也是判断是否可以重试的重要条件
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// client 设置为禁止重试
if (!client.retryOnConnectionFailure()) return false;
// 请求的 body 已经发出,则不在进行请求
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// 致命的异常,比如 ProtocolException、SSLHandshakeException 等
if (!isRecoverable(e, requestSendStarted)) return false;
// 没有更多的 Route 尝试
if (!transmitter.canRetry()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
可以看到重试的逻辑共有四个规则,如果都未通过那么重试就算失败了,并且会在 finally 中释放所有资源
2.2、BridgeInterceptor
这个拦截器的内部逻辑其实很简单,它的作用就是如它的名字一般,负责建立一个请求和响应的桥梁, 比如在请求的设置各种常用的 header 例如 Content-Type 、cookie、User-Agent、 Gzip 压缩等等,获取响应时配置响应头以及 Gzip 的解压缩操作,总体就是在请求之前和之后做的各种配置和转换操作,代码很简单就不贴了
2.3、CacheInterceptor
OkHttp 的缓存拦截器的逻辑其实也是万变不离其宗,总体的思路并没有和其它的框架有什么大的不同,也就是先从缓存中拿,缓存没有在从网络中拿,只是 OkHttp 内部对缓存的各种逻辑做了很完善的处理。 但这里要注意的是 OKHttp 默认是不支持缓存的,默认提供的缓存功能为 Cache 类,如果需要缓存则需要在创建 OkHttpClient 的时候手动设置。
这里稍微提一下这个OkHttp提供的 Cache 类,我们知道凡是涉及到缓存功能的无非就是在设置的存储范围内进行增删改查,OkHttp 在这点上与其它的缓存策略并未有什么不同,不信我们看下 Cache 类的大致结构
可以看到几个很关键的方法 get、put、remove、update、initialize,前四个不多说了 initialize 这个方法主要是做缓存之前的初始化操作,比如文件创建,删除以及异常的处理,那么这些增删改查的主要操作其实都是通过 DiskLruCache 类完成,也可以说整个 Cache 类其实就是对于 DiskLruCahe 操作的封装,而 DiskLruCache 则是对 okio包下的 BufferSource 和 BufferedSink 做了封装简化对文件的读写操作。总体流程就是这些,具体的细节感兴趣的读者可以根据这几个关键方法来深入了解 OkHttp 的 Cache 逻辑,这里就不赘述了。
啰嗦了这么多下面我们进入正题
@Override
public Response intercept(Chain chain) throws IOException {
//判断是否配置了缓存,如果有则获取缓存
Response cacheCandidate = cache != null? cache.get(chain.request()) : null;
long now = System.currentTimeMillis();
//获取缓存的策略,策略的判断条件为 当前时间,request 和 response
//其实主要就是解析 response 和 request 各种 header 信息以此来获取缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;//网络请求
Response cacheResponse = strategy.cacheResponse;//缓存响应
if (cache != null) { //如果缓存不为空则直接采用上面的策略
cache.trackResponse(strategy);
}
//省略部分代码...
//网络请求和缓存都为空返回构建的 504 response
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 上面的条件为 false 且网络请求为 null 直接返回缓存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//缓存没有数据,则执行下一个责任链
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 当缓存不为空,但网络返回的 response code = 304
// 直接返回缓存的 response
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//构建网络请求的 response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果缓存对象不为空则说明 OkHttpClient 创建的时候
//配置了缓存对象,那么对于请求的 response 进行缓存
if (cache != null) {
//判断是否可以缓存,主要是判断各种 code 是否满足缓存条件等等
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
//写入磁盘
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//判断请求方法是否为 GET ,非 GET 请求缓存无效并删除
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
通过对代码的逐行分析,我们发现 OkHttp 在缓存的处理上是非常完善的,基本覆盖了常见的缓存问题,整体的逻辑也不是那么复杂,但到这里你会发现我们依然在应用层转悠,具体怎么进行网络请求的,我们依旧是一无所知,所以接下来我们来看看剩下的两个拦截器是怎么完成这个过程的。
2.4、ConnectInterceptor
ConnectInterceptor 可以说是整个 OkHttp 核心中的核心了,众所周知当我们通过一个 url 发起一个请求时会先经过 DNS 进行域名解析获取实际的 IP 地址,然后根据 IP 地址和目标服务器建立连接,在经过三次握手后连接成功,我们就可以发送或接收消息了。 而 ConnectInterceptor 做的就是这些事,它将整个连接过程高度封装,而且对连接做了复用处理,request 与 response 更是全部使用 okio 包下的 I/O 流进行读写操作,大大提升了请求效率。 所以废话不多说,上代码~
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//这里开始建立 Socket 连接,而且如果是非 GET 请求则要做更多的安全检查
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
//执行下一个拦截器
return realChain.proceed(request, transmitter, exchange);
}
可以看到 ConnectInterceptor 的代码并不多,这里我们主要关注的就是 Transmitter 对象中的 newExchange 方法
/** Returns a new exchange to carry a new request and response. */
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
//出现了异常连接被释放了
if (noMoreExchanges) {
throw new IllegalStateException("released");
}
//有别的请求未关闭则无法创建新的请求
if (exchange != null) {
throw new IllegalStateException("cannot make a new request because the previous response "
+ "is still open: please call response.close()");
}
}
//建立连接
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
代码不多但很关键,首先 ExchangeCodec 是一个接口,它的实现类有两个,一个是Http1ExchangeCodec,一个是Http2Exchangecodec,相信从这里你已经明白了,没错,一个对应的是 Http1 协议的连接,一个则是 Http2 协议。而不同的 HttpCodec 代表不同的连接逻辑,虽然它俩的连接逻辑不同,但其实干的事情都一样,就是对 Socket I/O 操作做了封装,然后就是 ExchangeFinder 对象, 这个对象内部实现了连接的复用机制,包括连接的重试等等,不过到这里你可能会有点疑惑好像没看到在哪创建这个类的实例啊,提示一下,到 RetryAndFollowUpInterceptor 中找到 prepareToConnect 方法进入后你就明白了。至于 Exchange 则是对连接的包装类
了解了这些类的基本作用后那么 find 方法的做的事情我们也就基本清楚了,就是根据我们的请求建立 http1/http2 的的连接并返回对应的 ExchangeCodec 对象,我们进去看看
final class ExchangeFinder {
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
//省略若干代码...
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
//省略若干代码...
}
}
可以看到内部调用 findHealthyConnection 方法,并返回一个 RealConnection 对象,并通过 RealConnection 对象的 newCodec 方法创建对应协议的 ExchangeCodec 对象。
这里着重说一下 RealConnection 这个对象,顾名思义这个对象是真正处理 Socket 连接的对象, RealConnection 处理的事情非常之多,包含了 socket 连接的所有细节例如 TCP + TLS 握手、Http/http2 连接的处理等等,并通过 RealConnectionPool 实现了连接的复用逻辑,RealConnectionPool 内部维护着一个线程池和一个 RealConnection 的队列,这个线程池会在后台一直运行,负责清理过期的连接、执行新的连接或已有的连接。
继续跟进 findHealthyConnection 方法
final class ExchangeFinder {
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
//省略若干代码...
return candidate;
}
}
}
注释我故意没有删,因为它已经表明了这个方法的作用,注释的意思是说,找到一个安全的连接,如果不安全就一直在循环中找,直到找到为止,不过说虽然是这么说,当然不可能会无限找的,别忘了我们是有连接时超时限制的。
继续跟进 findConnection 方法
final class ExchangeFinder {
/**
* 返回一个连接的流,如果这个连接在连接池中存在则返回,否则创建
* 一个新的连接返回
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
RealConnection result = null;
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
//如果已经建立了连接,但中间却出现了异常导致连接出现问题
//那么释放连接或创建新的连接
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
//如果已有连接那么直接使用
result = transmitter.connection;
releasedConnection = null;
}
//没有可用的连接
if (result == null) {
//尝试从连接池中获取连接进行复用
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
//如果连接池中也没有那么用其它的 route 重试
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
//如果设置了重试当前 route 那么根据重试的 route 连接
selectedRoute = transmitter.connection.route();
}
}
}
if (result != null) {
//如果连接池有可用的连接,直接使用
return result;
}
// 如果上面的条件都没有获取到连接,那么说明我们要创建一个新的连接
//并通过 DNS 解析获取要连接的 route
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// 如果有新的 route 那么获取所有 route
routes = routeSelection.getAll();
//在此查看连接池中是否有可用的连接如果有直接使用
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
//连接池中没有可用连接,且没有可选择的 route
//通过 next 方法用 DNS 解析出新的 IP 地址用于接下来的连接
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// 通过上面获取的 route 创建一个新的 RealConnection 对象
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 做 TCP+TLS 握手,注意这是一个阻塞操作
//到这一步执行完整个连接算是真正的建立了
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// 这里会再次进行一次判断,查看连接池中是否有可用的连接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
//如果有那么关闭连接池中的连接,返回我们新创建的连接
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
//将上面获取的新的 route 标记位下次重试的 route
nextRouteToTry = selectedRoute;
} else {
//将新创建的连接放入连接池方便复用
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
return result;
}
}
上面的代码就是 OkHttp 建立连接的整个过程,说实话整体的代码是有点复杂的,而且各种层级关系还不少,如果不耐心阅读很容易跟错,这里我也是读了很久才逐渐缕清楚整个连接过程。所以让我们做个复盘总结一下,首先 ConnectInterceptor 的主要作用就是建立 socket 连接并将其包装成 Exchange 对象供下一个拦截器使用, 而 Exchange 其实就是 Http1ExchangeCodec 和 Http2ExchangeCodec 的包装类,负责对 request 和 response 的 header body 进行流的读写操作。建立连接的整个过程如下
- 调用 transmitter的.newExchange 方法获取 Exchange 对象
- newExchange 方法调用 ExchangeFinder.find 方法获取 ExchangeCodec 对象
- find 方法 调用 ExchangeFinder 的 findHealthyConnection 方法获取 RealConnection 对象并调用该对象的 newCodeC 方法创建一个 ExchangeCodec 对象返回
- findHealthyConnection 方法再调用 ExchangeFinder 的 findConnection 方法获取 RealConnection 对象
- findConnection 方法中通过 DNS 解析 IP地址,创建 RealConnection 对象并建立连接后返回 RealConnection 实例,同时实现了连接的复用逻辑
经过上面的一系列调用 ConnectInterceptor 就完成了它的光荣使命,这里在提一个细节,让我们回顾一下以往的代码
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors())
}
//省略...
}
其实刚开始读到这里的时候我是有点疑惑的,interceptors 和 networkInterceptors 有什么区别? 而经过了上面的分析相信你会恍然大悟,很简单, interceptors 是连接之前的处理,而 ConnectInterceptor 执行完之后网络连接已经建立了,那么后续的拦截只能是对连接后的过程进行处理,所以才会叫 networkInterceptors
三、结语
注释里基本对每个方法都做了详细说明,相信到这里我们脑海中对 OkHttp 的连接流程的总体脉络应该有了一个较为清晰的认识,但还没完,Socket 在哪连接的, DNS 解析好像也没看到, ExchangeCodec实例对象是怎么创建的?这些在上述的代码中我们都没看到,所以下面我还会挨个对上述的问题一个一个解密,限于篇幅,想继续了解可以阅读 OkHttp 源码深入分析(二)