OkHttp讲解(一)
OkHttp讲解(二)
OkHttp讲解(三)
1、拦截器 Interceptor
1.1 okhttp的工作流程图
可以看出,Interceptor贯穿了整个请求过程,是在请求执行过程中扮演重要角色。
这是okhttp的请求执行过程,从应用发出request,到应用收到response,期间经历了N个拦截器。
- 蓝色块上方是APPLication Interceptor,也就是应用程序拦截器,即开发者自己自定义的拦截器,代码中的client.interceptors()获取的就是这类拦截器
- 蓝色块下方是NetWork Interceptor,也就是网络拦截器,用来观察单个网络请求和响应,只能调用一次proceed方法
- 蓝色块代表的就是OKHttp提供的拦截器,共有5个,也是我们需要关注的重点
1.2 拦截器的分类
okhttp工作流程图中,橙色框框内的那些拦截器,属于okhttp库内部定义的,一般情况下不会更改。所以这里只讨论开发者能够自定义的拦截器。
分为两类:
1)ApplicationInterceptor(应用拦截器)
2)NetworkInterceptor(网络拦截器)
相同点
- 都能对server返回的response进行拦截
- 这两种拦截器本质上都是基于Interceptor接口,由开发者实现这个接口,然后将自定义的Interceptor类的对象设置到okhttpClient对象中。所以,他们的对象,本质上没什么不同,都是Interceptor的实现类的对象。
- 两者都会被add到OkHttpClient内的一个ArrayList中。当请求执行的时候,多个拦截器会依次执行(list本身就是有序的)。
不同点
- okhttpClient添加两种拦截器的api不同。添加应用拦截器的接口是addInterceptor(),而添加网络拦截器的接口是addNetworkInterceptor().
- 两者负责的区域不同,从最上方图中可以看出,应用拦截器作用于okhttpCore到Application之间,网络拦截器作用于 network和okhttpCore之间
- 在某种特殊情况下(比如:访问了一个url,结果这个url发生了重定向),网络拦截器有可能被执行多次,但是 不论任何情况,application只会被执行一次。
1.3 okhttp库内部定义的拦截器
-
RealCall
在okhttp框架中,当客户端通过OkHttpClient发起同步或异步请求时,okhttp框架将会创建一个RealCall,这个实例将根据客户端提供的Request,发起同步或异步网络请求操作,在RealCall被创建时,将会创建一个Interceptor的具体实现。我们知道,okhttp框架将网络请求的步骤,通过Interceptor接口进行了统一的分层式设计,将每个环节都分成了不同的Interceptor,Interceptor又被称为拦截器,这是该网络框架设计的精髓所在,通过不同的拦截器规则,处理网络请求过程中的不同环节,最终通过链式调用,实现一个完整的网络请求操作。
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
//添加开发者应用层自定义的Interceptor
interceptors.addAll(client.interceptors());
//这个Interceptor是处理请求失败的重试,重定向
interceptors.add(retryAndFollowUpInterceptor);
//这个Interceptor工作是添加一些请求的头部或其他信息
//并对返回的Response做一些友好的处理(有一些信息你可能并不需要)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
interceptors.add(new CacheInterceptor(client.internalCache()));
//这个Interceptor的职责是建立客户端和服务器的连接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加开发者自定义的网络层拦截器
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
//一个包裹这request的chain
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//把chain传递到第一个Interceptor手中
return chain.proceed(originalRequest);
}
-
RetryAndFollowUpInterceptor 重试和失败重定向拦截器
这个拦截器它的作用主要是负责请求的重定向操作,用于处理网络请求中,请求失败后的重试链接。把StreamAllocation对象,传递给后面的拦截器。
private static final int MAX_FOLLOW_UPS = 20;
从这个静态变量可以看出 RetryAndFollowUpInterceptor 失败重连最多20次。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//创建一个新的流
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
//重定向次数
int followUpCount = 0;
// 上一个重试得到的响应
Response priorResponse = null;
while (true) {
//如果RealCall调用了cancel,即取消请求,那就释放资源,抛出异常结束请求
if (canceled) {
//如果取消了则删除连接上的call请求
streamAllocation.release();
throw new IOException("Canceled");
}
// 定义请求的响应
Response response = null;
//// 是否释放连接,默认为true
boolean releaseConnection = true;
try {
//调用下一个拦截器 即BridgeInterceptor;进行网络连接,获取response
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
// 如果没有发送异常,修改标志 不需要重试
releaseConnection = false;
} catch (RouteException e) {
//出现路由连接异常,通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
//recover(...)检测连接是否还可以继续
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
//能恢复连接,修改标志 不释放连接
releaseConnection = false;
//回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
continue;
} catch (IOException e) {//后续拦截器在与服务器通信中抛出IO异常
//判断该异常是否是连接关闭异常
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
////通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
if (!recover(e, requestSendStarted, request)) throw e;
//能恢复连接, 修改标志 不释放连接
releaseConnection = false;
//回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
continue;
} finally {
// 如果releaseConnection为true,说明后续拦截器抛出了其它异常,那就释放所有资源,结束请求
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 走到这里,说明网络请求已经完成了,但是响应码并不一定是200
// 可能是其它异常的响应码或者重定向响应码
// 如果priorResponse 不等于null,说明前面已经完成了一次请求
// 那就通过上一次的response构建新的response,但是body为null.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//对response进行响应码的判断,如果需要进行重定向,那就获取新的Request
Request followUp = followUpRequest(response);
// 如果为null,那就没必要重新请求,说明已经有了合适的Response,直接返回
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
//关闭,忽略任何已检查的异常
closeQuietly(response.body());
//检测重连次数是否超过20次,如果超过就抛出异常,避免消耗客户端太多资源
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//如果该请求体被UnrepeatableRequestBody标记,则不可重试
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
//判断重连前的Request与重新构建的Request是否有相同的连接,即host、port、scheme是否一致
if (!sameConnection(response, followUp.url())) {
// 如果不是相同的url连接,先释放之间的,再创建新的StreamAllocation
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()), callStackTrace);
} else if (streamAllocation.codec() != null) {
// 如果相同,但是本次请求的流没有关闭,那就抛出异常
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
//把重定向的请求赋值给request,以便再次进入循环执行
request = followUp;
priorResponse = response;
}
}
/**
* 判断当与服务器通信失败时,连接能否进行恢复
* 返回true,表示可以进行恢复
* 返回false 表示不能恢复,即不能重连
*/
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
//根据抛出的异常,做出连接、连接路线的一些处理,并且释放连接,关闭连接
streamAllocation.streamFailed(e);
// 判断开发者是否禁用了失败重连
// 在构建OKHttpClient的时候可以通过build进行配置
//如果禁用,那就返回false,不进行重连
if (!client.retryOnConnectionFailure()) return false;
// 如果不是连接关闭异常,且请求体被UnrepeatableRequestBody标记,那不能恢复
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// 根据异常判断是否可以重连
if (!isRecoverable(e, requestSendStarted)) return false;
// 判断还有没有多余线路进行连接
// 如果没有,返回false
if (!streamAllocation.hasMoreRoutes()) return false;
// 走到这里说明可以恢复连接,尝试重连
return true;
}
-
BridgeInterceptor 桥接和适配拦截器
主要是补充用户创建请求当中缺少的一些必要的请求头。BridgeInterceptor 为用户构建的一个 Request 请求转化为能够进行网络访问的请求,同时将网络请求回来的响应 Response 转化为用户可用的 Response。比如,涉及的网络文件的类型和网页的编码,返回的数据的解压处理等等。
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
//组织Request Header包括这是keep-alive, Cookie添加,gzip等
....
//传递
Response networkResponse = chain.proceed(requestBuilder.build());
//组织Response Header 包括cookie保存更新,Gzip解压等
....
return responseBuilder.build();
}
-
CacheInterceptor 缓存拦截器
如果当前未使用网络,并且缓存不可以使用,通过构建者模式创建一个Response响应,抛出504错误
。如果有缓存 但是不能使用网络 ,直接返回缓存结果。这是在进行网络请求之前所做的事情,当网络请求完成,得到下一个拦截器返回的response之后,判断response的响应码是否是HTTP_NOT_MODIFIED = 304,(未改变)是则从缓存中读取数据。
@Override
public Response intercept(Chain chain) throws IOException {
//通过request从缓存中获取响应
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
/**
* CacheStrategy 是一个缓存策略类 比如强制缓存 对比缓存等 它决定是使用缓存还是进行网络请求
* 其内部维护了Request、Response
* 如果Request为null表示不使用网络
* 如果Response为null表示不使用缓存
*/
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//根据缓存策略获取缓存Request和Response
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//根据缓存策略,更新统计指标:请求次数、使用网络请求次数、使用缓存次数
if (cache != null) {
cache.trackResponse(strategy);
}
// 能从缓存中获取响应但是缓存策略是不使用缓存,那就关闭获取的缓存
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 根据策略,不使用网络,又没有缓存的直接报错,并返回错误码504
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();
}
// 如果缓存策略是不使用网络但是可以使用缓存,那就通过缓存策略的缓存构建响应并返回
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//缓存不可用或者缓存过期,网络获取
Response networkResponse = null;
try {
//前面两个都没有返回,继续执行下一个Interceptor,即ConnectInterceptor
networkResponse = chain.proceed(networkRequest);
} finally {
// 如果发生了IO异常或者其它异常,关闭缓存避免内存泄漏
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 如果缓存策略是可以使用缓存
if (cacheResponse != null) {
// 且网络响应码是304 HTTP_NOT_MODIFIED说明本地缓存可以使用
// 且网络响应是没有响应体的
// 这时候就合并缓存响应和网络响应并构建新的响应
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();
// 在合并标头之后但在剥离Content-Encoding标头之前更新缓存
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 走到这里说明缓存策略是不可以使用缓存或本地缓存不可用
// 那就通过网络响应构建响应对象
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//对数据进行缓存
if (cache != null) {
// 如果响应有响应体且响应可以缓存 那就将响应写入到缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 缓存响应的部分信息
CacheRequest cacheRequest = cache.put(response);
// 缓存响应体并返回响应
return cacheWritingResponse(cacheRequest, response);
}
//通过请求方法判断需不需要进行缓存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
// 不合法就删除缓存
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
//更新缓存
return response;
}
-
ConnectInterceptor 连接拦截器
在 okhttp底层是通过 socket 的方式于服务端进行连接的,并且在连接建立之后会通过 okio 获取通向 server 端的输入流 Source 和输出流 Sink。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
//获取可复用流
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//创建输出流
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
//根据HTTP/1.x(keep-alive)和HTTP/2(流复用)的复用机制,发起连接
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
-
CallServerInterceptor 网络服务拦截器,OkHttp核心拦截器,网络交互的关键
主要负责将请求写入到 IO 流当中,并且从 IO 流当中获取服务端返回给客服端的响应数据。
CallServerInterceptor 在 ConnectInterceptor 拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpStream 它包括通向服务器的输入流和输出流。而接下来的 CallServerInterceptor 拦截器的功能使用 HttpStream 与服务器进行数据的读写操作的
okhttp的拦截器就是在intercept(Chain chain)的回调中对Request和Response进行修改,然后直接返回了response 而不是进行继续递归,具体执行RealConnection里面是通过OKio实现的。在okhttp中,网络连接也是一个拦截器(CallServerInterceptor),他是最后一个被调用的,负责将request写入网络流中,并从网络流中读取服务器返回的信息写入Response中返回给客户端。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
//发送请求的时间戳
long sentRequestMillis = System.currentTimeMillis();
//写入请求头信息
httpCodec.writeRequestHeaders(request);
//发送header数据
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
//根据是否支持100-continue,发送body数据
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
...
}
//结束请求
httpCodec.finishRequest();
if (responseBuilder == null) {
//读取响应头信息
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
//发送请求的时间
.sentRequestAtMillis(sentRequestMillis)
//接收到响应的时间
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
//response处理
...
return response;
}
那我们再来看下OkHttp网络请求的整体接口图
2、 Interceptor 关联类分析
2.1 StreamAllocation 的成员变量
简介
StreamAllocation是用来协调Connections、Streams和Calls这三个实体的。
- Connections:连接到远程服务器的物理套接字,这个套接字连接可能比较慢,所以它有一套取消机制。
- Streams:定义了逻辑上的HTTP请求/响应对,每个连接都定义了它们可以携带的最大并* 发流,HTTP/1.x每次只可以携带一个,HTTP/2每次可以携带多个。
Calls:定义了流的逻辑序列,这个序列通常是一个初始请求以及它的重定向请求,对于同一个连接,我们通常将所有流都放在一个调用中,以此来统一它们的行为。
HTTP通信 执行 网络请求Call 需要在 连接Connection 上建立一个新的 流Stream,我们将 StreamAllocation 称之 流 的桥梁,它负责为一次 请求 寻找 连接 并建立 流,从而完成远程通信。
public final Address address;//地址
private Route route; //路由
private final ConnectionPool connectionPool; //连接池
private final Object callStackTrace; //日志
// State guarded by connectionPool.
private final RouteSelector routeSelector; //路由选择器
private int refusedStreamCount; //拒绝的次数
private RealConnection connection; //连接
private boolean released; //是否已经被释放
private boolean canceled //是否被取消了
比较重要的方法, 通过ConnectInterceptor
中的HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
方法调用
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//获取一个连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
//实例化HttpCodec,如果是HTTP/2则是Http2Codec否则是Http1Codec
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
2.2 RealConnection
- okhttp是底层实现框架,与httpURLconnection是同一级别的。OKHttp底层建立网络连接的关键就是RealConnection类。RealConnection类底层封装socket,是真正的创建连接者。分析这个类之后就明白了OKHttp与httpURLconnection的本质不同点。
- RealConnection是Connection的实现类,代表着链接socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,而且通过RealConnection代表是连接socket链路,RealConnection对象意味着我们已经跟服务端有了一条通信链路了,在这个类里面实现的三次握手。
- 在OKHttp里面,记录一次连接的是RealConnection,这个负责连接,在这个类里面用socket来连接,用HandShake来处理握手。
//链接的线程池
private final ConnectionPool connectionPool;
private final Route route;
//下面这些字段,通过connect()方法开始初始化,并且绝对不会再次赋值
/** The low-level TCP socket. */
private Socket rawSocket; //底层socket
private Socket socket; //应用层socket
//握手
private Handshake handshake;
//协议
private Protocol protocol;
// http2的链接
private Http2Connection http2Connection;
//通过source和sink,大家可以猜到是与服务器交互的输入输出流
private BufferedSource source;
private BufferedSink sink;
//下面这个字段是 属于表示链接状态的字段,并且有connectPool统一管理
//如果noNewStreams被设为true,则noNewStreams一直为true,不会被改变,并且表示这个链接不会再创新的stream流
public boolean noNewStreams;
//成功的次数
public int successCount;
//此链接可以承载最大并发流的限制,如果不超过限制,可以随意增加
public int allocationLimit = 1;
RealConnection的connect方法,connect()里面进行了三次握手
public void connect(。。。) {
//如果协议不等于null,抛出一个异常
if (protocol != null) throw new IllegalStateException("already connected");
。。 省略部分代码。。。。
while (true) {//一个while循环
//如果是https请求并且使用了http代理服务器
if (route.requiresTunnel()) {
connectTunnel(...);
} else {//
//直接打开socket链接
connectSocket(connectTimeout, readTimeout);
}
//建立协议
establishProtocol(connectionSpecSelector);
break;//跳出while循环
。。省略部分代码。。。
}
//当前route的请求是https并且使用了Proxy.Type.HTTP代理
public boolean requiresTunnel() {
return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
}
普通连接的建立过程为建立TCP连接,建立TCP连接的过程为
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
//根据代理类型来选择socket类型,是代理还是直连
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
//连接socket
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
throw new ConnectException("Failed to connect to " + route.socketAddress());
}
source = Okio.buffer(Okio.source(rawSocket));//从socket中获取source 对象。
sink = Okio.buffer(Okio.sink(rawSocket));//从socket中获取sink 对象。
}
Okio.source(rawSocket)
和Okio.sink(rawSocket)
的原码
public static Source source(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
Source source = source(socket.getInputStream(), timeout);
return timeout.source(source);
}
public static Sink sink(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
Sink sink = sink(socket.getOutputStream(), timeout);
return timeout.sink(sink);
}
建立隧道连接的过程
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout)
throws IOException {
//1、创建隧道请求对象
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
int attemptedConnections = 0;
int maxAttempts = 21;
//一个while循环
while (true) {
//尝试连接词说超过最大次数
if (++attemptedConnections > maxAttempts) {
throw new ProtocolException("Too many tunnel connections attempted: " + maxAttempts);
}
//2、打开socket链接
connectSocket(connectTimeout, readTimeout);
//3、请求开启隧道并返回tunnelRequest
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
//4、成功开启了隧道,跳出while循环
if (tunnelRequest == null) break; /
//隧道未开启成功,关闭相关资源,继续while循环
//当然,循环次数超限后抛异常,退出wiile循环
closeQuietly(rawSocket);
rawSocket = null;
sink = null;
source = null;
}
}
//隧道请求是一个常规的HTTP请求,只是请求的内容有点特殊。最初创建的隧道请求如
private Request createTunnelRequest() {
return new Request.Builder()
.url(route.address().url())
.header("Host", Util.hostHeader(route.address().url(), true))
.header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
.header("User-Agent", Version.userAgent())
.build();
}
2.3 ConnectionPool
管理http和http/2的链接,以便减少网络请求延迟。同一个address将共享同一个connection。该类实现了复用连接的目标。
//这是一个用于清楚过期链接的线程池,每个线程池最多只能运行一个线程,并且这个线程池允许被垃圾回收
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
//每个address的最大空闲连接数。
private final int maxIdleConnections;
private final long keepAliveDurationNs;
//清理任务
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
//链接的双向队列
private final Deque<RealConnection> connections = new ArrayDeque<>();
//路由的数据库
final RouteDatabase routeDatabase = new RouteDatabase();
//清理任务正在执行的标志
boolean cleanupRunning;
//创建一个适用于单个应用程序的新连接池。
//该连接池的参数将在未来的okhttp中发生改变
//目前最多可容乃5个空闲的连接,存活期是5分钟
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
//保持活着的时间,否则清理将旋转循环
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
- 1、主要就是connections,可见ConnectionPool内部以队列方式存储连接;
- 2、routDatabase是一个黑名单,用来记录不可用的route,但是看代码貌似ConnectionPool并没有使用它。所以此处不做分析。
- 3、剩下的就是和清理有关了,所以executor是清理任务的线程池,cleanupRunning是清理任务的标志,cleanupRunnable是清理任务。
2.4 RealInterceptorChain 拦截器链
- 当发送一个请求的时候,实质OkHttp会通过一个拦截器的链来执行OkHttp的请求。
- 这就是所谓的拦截器链,执行 RetryAndFollowUpInterceptor => 执行 BridgeInterceptor => 执行 CacheInterceptor => 执行 ConnectInterceptor => 执行 CallServerInterceptor => 响应到
ConnectInterceptor => 响应到 CacheInterceptor => 响应到 BridgeInterceptor => 响应到 RetryAndFollowUpInterceptor
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
//在RetryAndFollowUpInterceptor中new的
private final StreamAllocation streamAllocation;
//在ConnectInterceptor中new的
private final HttpCodec httpCodec;
//在ConnectInterceptor中new的
private final RealConnection connection;
//标识应该取拦截器链表里面的第几个拦截器
private final int index; //通过index + 1
private int calls; //通过call++
private final Request request;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
@Override public Connection connection() {
return connection;
}
public StreamAllocation streamAllocation() {
return streamAllocation;
}
public HttpCodec httpStream() {
return httpCodec;
}
@Override public Request request() {
return request;
}
//执行继续拦截操作
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
/**
* 依次取出拦截器链表中的每个拦截器去获取Response
*/
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
// 1、迭代拦截器集合
if (index >= interceptors.size()) throw new AssertionError();
//2、记录本方法调用次数,创建一次实例,call加1
calls++;
//如果已经为该Request创建了stream,就不再继续创建了
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// 如果已经为该Request创建了stream,那该方法只能调用一次
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
//创建新的拦截器链对象, 并将计数器+1
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
//取出下一个拦截器
Interceptor interceptor = interceptors.get(index);
//执行拦截器的intercept方法获取结果,并将新的拦截器链对象传入
Response response = interceptor.intercept(next);
// 确保该方法只能调用一次
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
}
网友评论