OkHttp源码解析
- 话不多说,我们直接上源码分析,先来看一下怎么使用
简单使用
- 创建client
OkHttpClient client = new OkHttpClient.Builder().url("").build();
或者
OkHttpClient client = new OkHttpClient();
//两者的区别仅在于前者使用了创建者模式可以显示设置client的连接配置
- 创建Request
Request request = new Request.Builder().build();
- 生成call
Call call = client.newCall(request);
//一个call表示一个请求
- 请求,这里可以使同步请求也可以是异步请求
//异步
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步
try {
final Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
- 好了,以上就是OKHTTP的简单使用了,我们接下来顺着这个步骤往下走
源码解析
创建Client
- 无论使用构造器创建还是使用创建者模式创建,一个Client被创建好之后,他的一些基本属性也就确定了,这些个属性可能包括任务调度器,拦截器,缓存机制,等,这里先不管,等看到的时候再说
创建Request
- 一个请求创建好之后 ,随之确定的属性有比如说:请求地址,请求方式(get,post等),消息头,等
创建Call
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
this.timeout = new AsyncTimeout() {
@Override protected void timedOut() {
cancel();
}
};
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
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;
}
- 可以看到,这里的Request被封装成了一个RealCall对象,并为他构造出了一个事件监听,这个事件监听默认是空实现的,也就是没有任何代码,这个事件监听我们如果需要的话可以自己去实现,那么就会在任务执行的各个阶段相应的方法得到回调
开始请求
先看异步请求
public void enqueue(Callback responseCallback) {
synchronized (this) {
//一个任务只能请求一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);//开始事件回调,由上可知,这里如果使用默认的话,是没有代码执行的
client.dispatcher().enqueue(new AsyncCall(responseCallback));//调度器将回调封装成一个异步回调对象,并执行enqueue方法
}
- 可以看到,我们的异步回调被封装成一个AsyncCall对象,我们先来看看这个对象的构造方法
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
- 这里可能有点疑问,只是把回调装进任务中?那我们的request呢?这里大家看一下AsyncCall的源码就清楚了
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
void executeOn(ExecutorService executorService) {
boolean success = false;
try {
executorService.execute(this);
success = true;
}
}
@Override
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
}
}
}
- 这里AsyncCall作为RealCall的内部类,天生持有所在任务的所有参数,所以这里不需要再传入了,并且他有是NamedRunnable(Runnable的子类)的子类,我们来看一下NamedRunnable的实现
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override
public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
- 所以这里作为一个异步任务的话,入队之后线程池的执行时是执行 execute()方法的,而这个方法在AsyncCall类里得到了实现,我们看上面的被实现的execute()方法,最重要的方法就一行
Response response = getResponseWithInterceptorChain();
- 可以看到,这个方法就是执行异步任务的核心方法了
- 到这里先不着急分析细节,我们先来看一下任务调度器是怎么将异步任务入队的
client.dispatcher().enqueue(new AsyncCall(responseCallback));
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
- 可以看到,这里将我们的请求(这里暂且就称为请求吧,虽然他本身并没有封装RealCall属性,但是作为内部类他可以访问到RealCall的属性已经足够了)加入到了准备中的同步请求队列。
- 然后再看一下promoteAndExecute()这个方法
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
//这里是遍历正在准备的任务队列
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.
//如果符合要求,就先把这个任务放置在我们的临时集合里面
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);
//拿到每一个刚才加入到运行队列的异步任务,执行他们的executeOn方法
asyncCall.executeOn(executorService());//executorService()方法返回的是调度器持有的线程池
}
return isRunning;
}
- 由上面AsyncCall的代码可知,它已经实现了这个方法,我们来看一下这个方法做的事情
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);
success = true;
}
}
}
- 代码很简洁,就是将任务添加到线程池中
- 这下线程池的运行就是刚才的getResponseWithInterceptorChain()方法了。那么到这里,异步任务的执行机制就分析完了,在分析同步任务之前,我们先来看一下异步任务所在的这个线程池是怎么构造的
executorService = new ThreadPoolExecutor(
0, //核心线程池的大小
Integer.MAX_VALUE, //最大线程池大小
60, //线程池中超过核心线程池数目的空闲线程的最大存活时间
TimeUnit.SECONDS, //上个参数的单位(时间单位)
new SynchronousQueue<Runnable>(), //阻塞任务队列
Util.threadFactory("OkHttp Dispatcher", false)//新建线程工厂
);
- 接下来我们再看一下同步任务的执行机制
再看同步请求
final Response response = call.execute();
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
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 {
client.dispatcher().finished(this);
}
}
- 因为是同步任务,所以我们直接将他加入到正在执行的同步队列,然后调用getResponseWithInterceptorChain()去执行具体的逻辑,
- 执行完毕之后会调用调度器的finish方法
void finished(RealCall call) {
finished(runningSyncCalls, call);
}
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
- 继续执行promoteAndExecute方法保证同步任务不会终止
- 好了,到现在同步异步的执行机制就分析完了,接下来就到了请求逻辑的重头戏了
- 先做一下小总结
小总结
- Dispatcher是保存同步和异步Call的地方,并负责执行同步SyncCall。
- 针对异步请求,Dispatcher使用了两个Deque,一个保存准备执行的请求,一个保存正在执行的请求,为什么要用两个呢?因为Dispatcher默认支持最大的并发请求是64个,单个Host最多执行5个并发请求,如果超过,则Call会先被放入到readyAsyncCall中
重头戏:getResponseWithInterceptorChain()
- 直接进入这个方法吧
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
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, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
- 可以看到,主要就两件事:一,添加拦截器,也就是构建拦截器队列。二,构建拦截器链,执行chain.proceed()方法
- 这里的拦截器我在这里大概解释一下,这个用的应该就是责任链模式,每个拦截器有自己负责的模块,这些个模块可能包括,请求参数的检查,网络连接池的维护,缓存的维护,等
- 所以说,我们接下来的分析其实主要是分析各个模块的功能,每个模块都有两个部分,就是网络请求前,和网络请求后
- 如果有人不理解这个东西,我大概举个例子吧:就好比你是一个富二代,你的每一个生活细节都需要别人去帮你维护,拿吃饭来说,有人帮你做饭(A),有人帮你拿碗(B),有人帮你准备餐具©。A帮你做好饭,告诉B,B拿碗盛饭,将碗递给C,C给你准备餐巾纸,桌子椅子等,然后你吃完之后,C将桌子椅子清理,将碗交给B,B拿到碗之后,将碗清洗,并告知A,此次吃完饭的结果(吃了多少,剩了多少等),A根据你的这次过程,在下次做饭的时候就会做一些调整,这就是责任链模式,每个人都有两个模块,吃饭前,和吃饭后,作为Okhttp的网络请求链也一样,也分为网络请求前,和网络请求后。
- 关于每种拦截器,我们将从这两个方面进行分析
正式进入之前,我们先来看一下proceed()这个方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
return response;
}
- 这里的核心代码就三句,所以我把其他的代码都删掉了,可以看到重点是每个拦截器的intercept方法,我们接下来就重点分析每个拦截器的intercept方法
No.1 RetryAndFollowUpInterceptor
- 这里如果点进去看的话,可以发现,整个方法是被realChain.proceed()这句方法调用给分割开的,分割成了我上面说的两个时期,所以,我在这里就直接先截取前半段来分析
请求之前
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//这里new了一个streamAllocation 对象
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 {
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.
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();
}
}
//先省略后半段
}
- 可以看到,先是new了一个streamAllocation对象:直译就是流分配。流是什么呢?我们知道Connection是一个连接远程服务器的物理Socket连接,而Stream则是基于Connection的逻辑Http 请求/响应对。
- 我们来看一下streamAllocation 对象,这个对象在后面会被调用newStream方法去拿到一个流,这个流就是用来负责数据传输的
- 这里我们再关注一下catch出捕获的几个异常
- RouteException :路由异常,路由连接的时候出现问题,然后通过recover 方法去查询是否可以继续尝试连接,如果可以的话就continue,如果没有就抛出异常
- IOException:IO异常,处理的方式同上
- 如果成功走到finally里面,且没有释放连接,则表明这次请求是成功的,否则释放资源
- 接下来看一下,当请求完成之后的处理
请求之后
// Attach the prior response if it exists. Such responses never have a body.
//如果重定向之后,这里就会被赋值,这个相当于每次收集重定向的response信息
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
//这个方法会通过返回的状态码来检查当前的请求状态,如果对HTTP状态码熟悉的话,那么对这个方法做的事情应该就比较熟悉了,
//这个方法总的来说就是,看你现在返回的这个东西是不是正常的东西,又或者是不是重定向的,是不是需要做代理等,这里我也不是很清楚,不过不影响我们看代码
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
//这表示请求完成,返回响应体
streamAllocation.release();
return response;
}
//下面的这些代码都是在重定向之前做一些保留信息的事情,并继续while循环去做请求
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());
}
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;
- 所以综上所述,这个拦截器是主要处理重定向等一些非常规的返回状态的
- 我们先分析到这里,接下往下看
- 下一个就到BridgeInterceptor了
No.2 BridgeInterceptor
- 先看请求之前
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
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");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
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.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
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());
}
Response networkResponse = chain.proceed(requestBuilder.build());
//先省略后面的
}
- 这个前半部分相信不用过多解释吧,添加一些网络请求必要的参数,然后看请求之后的处理
//这句代码用来接收返回的cookie
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
//这里就可以对返回体做一下压缩的操作
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
No.3 CacheInterceptor
- 请求之前
public Response intercept(Chain chain) throws IOException {
//尝试获取缓存,这里的缓存是DiskLruCache策略,以请求的url做缓存的key
Response cacheCandidate = cache != null? cache.get(chain.request()) : null;
long now = System.currentTimeMillis();
//如果成功获取到了缓存,那么下面这个方法就会解析出上次缓存的全部内容
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
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.
}
//如果禁用网络且缓存不足,那么就返回失败
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 {
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());
}
}
//省略
}
- 可以看到,这部分重点在于获取缓存时候CacheStrategy对象的状态,因为在请求完成之后还会对返回体做缓存,肯定会用到这个类,我们就先来分析一下这个类
事先分析CacheStrategy
- 先来看看new CacheStrategy.Factory(now, chain.request(), cacheCandidate)方法
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
- 可以看到,这个类拿到获取到的缓存之后主要做了一个解析工作,如果缓存不空的话
- 那么,在这里我列一下相关变量的含义
- servedDate,servedDateString :服务器缓存响应时间
- expires :缓存到期时间
lastModified ,lastModifiedString :缓存响应的最后修改日期- etag :缓存相应的标记
- ageSeconds :缓存相应的年龄
- 再看一下get方法
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
//重点是这个getCandidate()
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
//缓存没有响应体
return new CacheStrategy(request, null);
}
// 因为如果是https的话可能需要一些必要的加密方式,所以这里会做判断,如果缺少必要的握手,则删除缓存的响应
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//这段代码的意思大概就是这个缓存不应该存在的,所以我们在这里也不做处理
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//请求的缓存控制,这个最终得到的东西是从Request里面解析出来的,这个是从Request里面去控制是否做缓存的
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//那么到这里就已经把没有缓存的情况排除完了
//这里是从响应体查看缓存控制
CacheControl responseCaching = cacheResponse.cacheControl();
//这个表示缓存的年龄,此响应体的各个时间参数决定
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//以上是先拿到缓存的各项数据,然后构造出一个响应体
//这里有个前提是if语句的第二个条件,这里我根据刚才看的结果和这里的命名猜一下,这里应该是对时间的判断来决定网络问题
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
//因为由刚才外面对这个对象的使用可以得知第一个参数应该是网络的判断
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
//这里是对缓存的再次判定,如果不需要缓存,这里也是直接返回第二个参数为null
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
//到这里就是对缓存的真正处理了,将缓存封装,返回出去
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
- 其实这个缓存机制的核心方法到这里也就完了,这里大多数意思都是自己猜的,大家仅供参考,关于Okhttp缓存机制,网上也有很多,大家也可以去阅读一下,在这里我推荐两篇我自己读的
- Okhttp解析(五)缓存的处理
- OkHttp3源码分析[缓存策略]
- 再来看一下请求完成之后的缓存策略吧
请求之后
//更新之前的缓存
// If we have a cache response too, then we're doing a conditional 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()).
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;
- 接着看下一个ConnectInterceptor
No.4 ConnectInterceptor(核心,连接池)
- 因为这里直接为下一步的最终请求做准备,所以这里并没有请求之后的代码
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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
- 可以看到,这里的主要逻辑在newStream和connection两个方法上面
- 先来看newStream方法
newStream
- 先来看一下streamAllocation的构造方法
public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
EventListener eventListener, Object callStackTrace) {
this.connectionPool = connectionPool; //这里是client的连接池
this.address = address; //这是解析request得到的Address
this.call = call; //这就是call本身了
this.eventListener = eventListener;
this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);//路由选择器
this.callStackTrace = callStackTrace;
}
- 再来看newStream方法
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
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);
}
}
- 重点在findHealthyConnection()方法
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);
//如果这个连接流是一个全新的连接,那么我们就跳过那些检查直接返回
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// 如果不是全新的,就先检查,如果不安全就把他从连接池中丢出,重新查找,否则返回这个安全的
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
- 那么就接着看findConnection是怎么找到的吧
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 查看当前的连接是否可用,此时为null
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
//在这里得到一个新的连接,这个新的连接会直接赋值到this.connection 字段上,并且赋值到result字段
if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
//到这里直接把这个连接返回出去
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
//下面是一个重定向的处理
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
//下面会对重定向的做重连接
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
- 获取到连接之后,我们回到newStream方法,通过resultConnection.newCodec生成了一个HttpCodec对象返回出去,总的来说这个newStream方法是把所有的连接判断的以及连接的三挥四握都做完了,这个具体是有系统自己实现的Socket去做的,这里不做探究,感兴趣的可以自己阅读,然后,将一个正确的connection字段赋值给streamAllocation
- 然后再看connection方法
public synchronized RealConnection connection() {
return connection;
}
-
只是简单的拿到这个字段而已
-
HttpCodec:针对不同的版本,OkHttp为我们提供了HttpCodec1(Http1.x)和HttpCodec2(Http2).
-
一句话概括就是:分配一个Connection和HttpCodec,为最终的请求做准备。
-
我们来看看streamAllocation.newStream到底干了什么
-
在目前的版本下,连接池默认是可以保持5个空闲的连接。这些空闲的连接如果超过5分钟不被使用,则将被连接池移除。
-
接着看下一个拦截器
No.5 CallServerInterceptor
- 这个是最终的拦截器,我们的网络请求也是在这里发出去的,那么这里也不区分请求前还是请求后了,这里直接上代码
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);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
//这里要先判断一个叫做100-continue的协议,说是一般传大数据的时候会使用
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
//然后这里将请求体通过流上传
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
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);
}
//封装
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
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();
}
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;
}
- 这个方法看起来不是特别难懂,通过流去请求数据读取数据,我对网络这块不是特别懂,所以这里也只能看个一知半解
总结
- 到这里Okhttp的代码就算是看完了,里面网络这一块还不是特别懂,这个要慢慢再探究吧。
- 不过对于Okhttp的整体架构还是理解的差不多的。
- 关于几个拦截器,建议大家还是多去看看别人单独分出来的分析资料。应该会详细的多。