OKHttp看这一篇就够了!

Posted 独见之明

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OKHttp看这一篇就够了!相关的知识,希望对你有一定的参考价值。

写在前面

本文按照一次网络请求的代码实例的顺序来进行源码分析,如果有错误的地方麻烦各位大佬指正~


目录

  • 1.代码实例
  • 2.OkHttpClient
  • 3.Request
  • 4.Call & RealCall
  • 5.Dispatcher
  • 6.Interceptor

    • RetryAndFollowUpInterceptor

    • BridgeInterceptor

    • CacheInterceptor

    • ConnectInterceptor

    • CallServerInterceptor

    • 自定义Interceptor

实例

    /**
* 同步请求
*/
public void synRequest() {
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://...")
.get()
.build();

Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 异步请求
*/
public void asyncRequest() {
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://...")
.get()
.build();

Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println(e.getMessage());
}

@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
}

public static void main(String[] args) {
OkHttpTest test = new OkHttpTest();
test.synRequest();
test.asyncRequest();
}

OkHttpClient

Call的工厂类,用于发送HTTP请求和读取其响应。:

OkHttpClients 应该共享

当创建一个OkHttpClient实例并将其用于所有HTTP调用时,OkHttp的性能最佳。这是因为每个客户端都拥有自己的连接池和线程池,重用连接和线程可减少延迟并节省内存;相反,为每个请求创建客户端都会浪费空闲池上的资源。

使用newBuilder()自定义客户端

您可以使用{@link #newBuilder()}自定义共享的OkHttpClient实例。这将构建共享相同连接池,线程池和配置的客户端。使用构建器方法为特定目的配置派生的客户机。

不需要手动关闭连接

保留的线程和连接如果保持空闲状态,将自动释放。但是,如果编写的应用程序需要主动释放未使用的资源,则可以这样做:

  • 2.使用{@link ConnectionPool#evictAll()}清除连接池。请注意,连接池的守护程序线程可能不会立即退出。client.connectionPool().evictAll();

  • 3.如果您的客户端具有缓存,调用{@link Cache#close()}。请注意,针对关闭的缓存创建调用是错误的,这样做会导致调用崩溃。client.cache().close();

OkHttp还使用守护程序线程进行HTTP/2连接。如果它们保持空闲状态,它们将自动退出。

总的来说,OkHttpClient将我们配置的参数和线程调度部分封装在一起进行管理,提供Call的创建方法,为网络请求提供必要的组件。


Request

在这里插入图片描述

Request类中保存了url、method、headers、body和tags,当创建RealCall时作为参数传递这些数据。


Call & RealCall

首先,我们先来看一下代码上的流程:

OKHttp看这一篇就够了!
在这里插入图片描述

当你看到后面觉得有些乱时,返回来看一下这张图,就会感觉很舒服了~

接下来我们看这句:

  Call call = client.newCall(request);


# OKHttpClient
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}


# 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.eventListener = client.eventListenerFactory().create(call);
return call;
}

可以看到,我们创建的是一个RealCall的实例对象,而RealCall的结构如下:

OKHttp看这一篇就够了!
在这里插入图片描述

同步和异步请求调用了RealCall的execute()、enqueue()方法:

  // Guarded by this.
// 通过该变量来标记RealCall是否被执行过了
private boolean executed;

/**
* 同步请求调用
*/
@Override
public Response execute() throws IOException {
// 每个Call只能被执行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}

captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);

try {
// 将RealCall传递给dispatcher统一管理
client.dispatcher().executed(this);

// 进入拦截器链(各种操作及网络请求)
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// release
client.dispatcher().finished(this);
}
}


/**
* 异步请求调用
*/
@Override
public void enqueue(Callback responseCallback) {
// 每个Call只能被执行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}

captureCallStackTrace();
eventListener.callStart(this);

// 将RealCall传递给dispatcher统一管理
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

// dispatcher的具体调度后文再讲;异步请求被执行时会调用下面的RealCall #execute()

@Override
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
// 进入拦截器链(各种操作及网络请求)
Response response = getResponseWithInterceptorChain();

// 1.responseCallback是发送异步请求时传进来的CallBack
// 2.因为是异步请求,在返回结果时会先判断用户是否已经取消了这次请求
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
e = timeoutExit(e);
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// release
client.dispatcher().finished(this);
}
}

注意这里的两个execute方法不是同一个:
同步请求调用的是声明为public的方法,而异步的调用比较复杂一些:enqueue将Call实例保存在dispatcher中,通过dispatcher来进行统一调度;当dispatcher决定执行Call时,会通过executeService(线程池)来调用Call,即实现Call接口的execute方法。dispatcher的调度在下一节中详细的讲。

这里我们看到了同步请求和异步请求都调用了dispatcher的方法,这个并不是真正的网络请求阶段,而是通过dispatcher对我们想要执行的请求进行管理,详细的代码流程在下文中会讲到,这里只是总结一下;而enqueue操作,通过dispatcher中的executeService调度后,切换到工作线程执行网络请求,调用到RealCall #execute()方法,最后也调用到了RealCall #getResponseWithInterceptorChain()方法。这个方法通过添加多个拦截器,对网络请求进行补全、选择协议、编解码、复用缓存/连接、网络请求等操作,以及添加自定义拦截器,对请求和返回的结果进行加工,最后返回数据。这就是整体的流程,关于拦截器的详细讲解也在下文中。先附上getResponseWithInterceptorChain()的代码:

  Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();

// 自定义拦截器
interceptors.addAll(client.interceptors());

// 从故障中恢复,并根据需要进行重定向
interceptors.add(retryAndFollowUpInterceptor);

// 构建网络请求、根据网络响应构建response
interceptors.add(new BridgeInterceptor(client.cookieJar()));

// 提供缓存的请求、将response写入缓存
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, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}

Dispatcher

首先我们看一下dispatcher的成员变量:

OKHttp看这一篇就够了!
在这里插入图片描述
maxRequest & maxRequestPerHost

maxRequest和maxRequestPerHost用来控制当前可同时进行的网络请求的数量。用这两个变量去控制的原因在于,方便与对其进行修改和管理。因为同步请求是直接进行请求的,而异步请求是通过线程池来进行请求的,我们来看一下线程池的获取:

  public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}

这里可以看到,线程池是直接创建了数量为MAX_VALUE的,而后续的最大数量判断都是使用了前面的两个变量,这样的好处是在对maxRequest进行修改时,excuteService本身并不需要重新实例化。

readyAsyncCalls & runningAsyncCalls & runningSyncCalls

这三个Deque的作用就是保存当前准备运行和正在运行的异步请求、正在运行的同步请求这三种请求的引用,用于统计数量、管理运行以及取消请求等一系列操作。

我们接着之前讲到的,异步请求调用了enqueue之后,到执行Call接口的execute方法之间的过程:

  void enqueue(AsyncCall call) {
synchronized (this) {
// 加入ready队列
readyAsyncCalls.add(call);
}

// 执行异步请求
promoteAndExecute();
}

/**
* Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
* them on the executor service. Must not be called with synchronization because executing calls
* can call into user code.
* 将合格的请求从{@link #readyAsyncCalls}转移到{@link #runningAsyncCalls},并在executor service(线程池)上执行。
* 不能以同步方式调用,因为执行调用可以调用用户代码。
*
* @return true if the dispatcher is currently running calls.
* 如果dispatcher当前正在执行请求,返回true。
*/
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));

List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;

synchronized (this) {
// 遍历readyAsyncCalls中的Call
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();

// 判断是否超过最大限制
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

// 从readyAsyncCalls转移到runningAsyncCalls
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}

// 执行这些请求
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}

return isRunning;
}


/**
* Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
* if the executor has been shut down by reporting the call as failed.
* 尝试在{@code executorService}上使此异步调用入队。
* 如果执行程序已通过将调用报告为失败而被关闭,则这将尝试进行清理。
*/
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;

try {
// 调用Call接口的execute
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
// 被拒绝执行后的清理操作
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}

Interceptor

首先,我们先来看一下OKHttp添加的必须的拦截器;然后再介绍一下自定义拦截器。我们先来回顾一下:

  Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();

// 自定义拦截器
interceptors.addAll(client.interceptors());

// 从故障中恢复,并根据需要进行重定向
interceptors.add(retryAndFollowUpInterceptor);

// 构建网络请求、根据网络响应构建response
interceptors.add(new BridgeInterceptor(client.cookieJar()));

// 提供缓存的请求、将response写入缓存
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, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}

那么拦截器是怎么进行操作的呢?如下面这张图:每个拦截器按照其顺序可以对Request进行处理,又按照反序对Response进行处理。而拦截器的核心函数就是public Response intercept(Chain chain)

在这里插入图片描述

RetryAndFollowUpInterceptor

重定向拦截器,负责从故障中恢复,并根据需要进行重定向。下文的代码中部分需要大篇幅解释的地方添加了注释标志:

  @Override
public Response intercept(Chain chain) throws IOException {
// get request
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();

// 注释1
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;

int followUpCount = 0;
Response priorResponse = null;
while (true) {
// 如果取消,则释放
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}

Response response;
boolean releaseConnection = true;
try {
// 进入链中的下一个拦截器,并catch RouteException
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 尝试通过路由连接失败,请求未被发送
// 注释2
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 尝试与服务器通信失败,该请求可能已发送
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}

// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}

Request followUp;
try {
// 找出响应收到{@code userResponse}而发出的HTTP请求
// 并添加身份验证标头,遵循重定向或处理客户端请求超时
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}

if (followUp == null) {
streamAllocation.release();
return response;
}

closeQuietly(response.body());

// 判断是否超过次数限制
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}

// 若重定向后不是同一连接,则重新创建StreamAllocation
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}

request = followUp;
priorResponse = response;
}
}



注释1:
此类协调三个实体之间的关系:
Connections:与远程服务器的物理套接字连接。这些建立起来可能很慢,因此必须能够取消当前正在连接的连接。
Streams:位于连接上的逻辑HTTP请求/响应对。每个连接都有自己的分配限制,该限制定义该连接可以承载的并发流数。HTTP / 1.x连接一次只能传送1个流,HTTP / 2通常会传送多个流。
Calls:流的逻辑顺序,通常是初始请求及其后续请求。我们希望将单个呼叫的所有流都保留在同一连接上,以实现更好的行为和局部性。

此类的实例代表调用、使用一个或多个连接上的一个或多个流。此类具有用于释放上述每个资源的API:
{@ link #noNewStreams()}防止将来将连接用于新的流。在{@code Connection:close}标头之后,或在连接可能不一致时使用此命令。
{@ link #streamFinished streamFinished()}从此分配中释放活动流。请注意,在给定时间可能只有一个流处于活动状态,因此在使用{@link #newStream newStream()}创建后续流之前,必须调用{@link #streamFinished streamFinished()}。
{@ link #release()}删除呼叫对连接的保留。请注意,如果仍然有流,这将不会立即释放连接。当呼叫完成但其响应主体尚未完全消耗时,就会发生这种情况。

此类支持{@linkplain #cancel异步取消},目的是使影响范围降到最小。如果HTTP / 2流处于活动状态,则取消操作将取消该流,但不会取消共享其连接的其他流。但是,如果TLS握手仍在进行中,则取消操作可能会中断整个连接。



注释2:
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
* 报告并尝试从与服务器通信的故障中恢复。
* 如果{@code e}是可恢复的,则返回true;
* 如果失败是永久的,则返回false。
* 仅在缓冲正文或在发送请求之前发生故障时,才能恢复带有正文的请求。
*/
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {

// 关闭并释放连接
streamAllocation.streamFailed(e);

// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;

// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;

// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;

// For failure recovery, use the same route selector with a new connection.
return true;
}


BridgeInterceptor

构建网络请求、根据网络响应构建response

  @Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();

RequestBody body = userRequest.body();

// 补全头部信息

// 补全Content-Type、Content-Length、Transfer-Encoding
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}

long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}

// 补全Host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}

// 补全Connection
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}

// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
// 如果增加了gzip压缩的字段,还需要负责将返回的response解码
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}

// 补全Cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}

if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}

// 分界线----上面是对Request的修改,下面是对Response的修改
Response networkResponse = chain.proceed(requestBuilder.build());

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);

// 如果添加了gzip,需要进行解码
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}

return responseBuilder.build();
}

CacheInterceptor

提供缓存的请求、将response写入缓存

  @Override
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;

long now = System.currentTimeMillis();

// 给定一个请求和缓存的响应,判断是使用网络,缓存还是同时使用两者
// 选择缓存策略可能会向请求中添加条件(如GET的“ If-Modified-Since”)
// 或向缓存的响应发出警告(如果缓存的数据可能已过时)
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

// 对{@code cacheStrategy}满意的HTTP响应进行跟踪
// 注释1
if (cache != null) {
cache.trackResponse(strategy);
}

// 没有缓存策略,关闭候选缓存
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}

// If we're forbidden from using the network and the cache is insufficient, fail.
// 没有网络策略和缓存策略,返回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 we don't need the network, we're done.
// 不需要进行网络通信,直接返回缓存
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());
}
}

// If we have a cache response too, then we're doing a conditional get.
// 如果已经有一个缓存的response,那么我们正在进行一个有条件的get请求
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()).
// 在合并headers之后、去掉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)) {
// Offer this request to the cache.
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;
}



注释1:
synchronized void trackResponse(CacheStrategy cacheStrategy) {
++this.requestCount;
if (cacheStrategy.networkRequest != null) {
++this.networkCount;
} else if (cacheStrategy.cacheResponse != null) {
++this.hitCount;
}

}


ConnectInterceptor

打开与目标服务器的连接,然后进入下一个拦截器

  @Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();

// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");

// 创建HttpCodec对象,用来编码request、解码response
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

// 用于进行实际的网络IO传输
RealConnection connection = streamAllocation.connection();

return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

这里的关键在于HttpCodec的创建,我们来看streamAllocation.newStream方法:

  public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
···

try {
// co co da yo ~
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}


/**
* 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) {

// co co da yo ~
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);

// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}

// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}

return candidate;
}
}

最后这里就不贴代码了。StreamAllocation中有一个ConnectionPool变量,负责管理Http连接。而这部分代码的目的就是,在创建新的连接之前,需要先查看是否有可以复用的连接,如果有则复用,没有或连接已经失效则重新创建连接。


CallServerInterceptor

进行网络请求

  @Override
public Response intercept(Chain chain) throws IOException {
···

// 写入headers
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);

Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
··· // 100-continue

if (responseBuilder == null) {
···
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

// 写入body信息
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}

// 完成了网络请求的写入
httpCodec.finishRequest();

if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}

// build response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();

int code = response.code();
··· // 100-continue

realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);

if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}

// prevents the connection from being used for new streams in the future
// 禁止该连接被复用
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}

if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}

return response;
}

自定义Interceptor

用户可以自行定义拦截器,来进行对网络请求的统一处理。这里附上两个拦截器:

PostParamsConvertInterceptor:将FormBody转化为json

LogInterceptor:打印请求的各种信息

使用方式也很简单,在OkHttpClient创建时加入:

    OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new PostParamsConvertInterceptor());
if (Utils.isAppDebug()) {
builder.addInterceptor(new LogInterceptor());
}

写在最后

如果文章中有错误的地方,希望各位大佬们批评指正~

If you like this article, it is written by Johnny Deng.
If not, I don't know who wrote it.


以上是关于OKHttp看这一篇就够了!的主要内容,如果未能解决你的问题,请参考以下文章

收集整理,看这一篇就够了

MyBatis增删改查(步骤详细,由浅入深,适合初学者,只看这一篇就够了)

Tcpdump 看这一篇就够了

MyBatis多条件查询看这一篇就够了

@Override 看这一篇就够了

Elasticsearch入门,看这一篇就够了