OKHttp3源码解析系列
上一篇文章中我们介绍了OkHttp3的同步和异步请求流程,我们分析到不论是同步还是异步请求,都是通过RealCall内部的getResponseWithInterceptorChain方法来执行具体的网络连接的,下面我们来分析看看OkHttp3具体是怎么进行网络请求的。
OkHttp3的拦截器链
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));
//这里参数0,表示的是interceptors列表中的索引。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
getResponseWithInterceptorChain方法中添加了一系列的拦截器Interceptor,包括用户的拦截器等。然后调用了RealInterceptorChain的proceed方法。每个拦截器都有特定的作用,通过责任链模式,每个拦截器完成自己的任务后,不断调用下个拦截器,最后完成网络请求。
//RealInterceptorChain.class
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
...
// Call the next interceptor in the chain.
//这里index+1,所以就会调用interceptors中的下一个拦截器
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;
}
这里可以看到在RealInterceptorChain的proceed方法中,会逐个调用interceptors中的拦截器,调用顺序就是前面添加的顺序。所以RealInterceptorChain可以看作是整个拦截器链的控制器,通过它可以调用链上的下一个拦截器,而每一个拦截器就相当于链上的一环。
(1)用户拦截器:通过Builder的addInterceptor方法添加的拦截器。
(2)RetryAndFollowUpInterceptor:负责失败自动重连和必要的重定向。
(3)BridgeInterceptor:负责将用户的Request转换成一个实际的网络请求Request,再调用下一个拦截器获取Response,然后将Response转换成用户的Response。
(4)CacheInterceptor:负责控制缓存,缓存的逻辑就在这里面。
(5)ConnectInterceptor:负责进行连接主机,在这里会完成socket连接,并将连接返回。
(6)CallServerInterceptor:和服务器通信,完成Http请求。
所以我们可以总结出网络请求的调用流程:
(1)RealInterceptorChain拦截器链的proceed方法-->用户拦截器-->RetryAndFollowUpInterceptor-->BridgeInterceptor-->CacheInterceptor-->ConnectInterceptor-->CallServerInterceptor
(2)当然在CacheInterceptor中,如果发现当前的请求有缓存的话就会直接返回Response了,而不会走后面的调用链。
OkHttp3的缓存策略
上面提到拦截器中有个缓存拦截器CacheInterceptor,它里面主要包含的是缓存的逻辑。下面我们来看看这个缓存策略是怎么样的。
首先我们看CacheInterceptor的构造函数
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
}
从文章开头中我们在RealCall内部添加CacheInterceptor拦截器的方式可知,CacheInterceptor中的InternalCache来自OkHttpClient中
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
...
final @Nullable Cache cache;
final @Nullable InternalCache internalCache;
OkHttpClient(Builder builder) {
//我们在RealCall中初始化CacheInterceptor时调用的就是这个方法
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
}
public static final class Builder {
@Nullable Cache cache;
@Nullable InternalCache internalCache;
/** Sets the response cache to be used to read and write cached responses. */
//这里需要注意的是这个方法并不是public的
void setInternalCache(@Nullable InternalCache internalCache) {
this.internalCache = internalCache;
this.cache = null;
}
/** Sets the response cache to be used to read and write cached responses. */
public Builder cache(@Nullable Cache cache) {
this.cache = cache;
this.internalCache = null;
return this;
}
}
}
从上面的OkHttpClient的源码中我们可以得出以下结论:
(1)OkHttpClient中有2个跟缓存有关的变量,一个是Cache,一个是internalCache。其中我们可以通过Builder来设置Cache,但是不能设置internalCache。
(2)从上面可以看出,默认Cache和internalCache都是null,也就是OkHttpClient没有默认的缓存实现。
(3)缓存拦截器CacheInterceptor中的internalCache来自OkHttpClient的Cache,因为OkHttpClient中的internalCache一直是null,我们没法从外界设置,所以如果我们没有为OkHttpClient设置Cache,那么缓存拦截器中的internalCache就也为null了,也就没法提供缓存功能。
从上面的源码中我们还发现,internalCache虽然不能从外界设置,但是它却是cache的一个内部变量。下面我们来具体看看缓存Cache的实现。
缓存Cache
public final class Cache implements Closeable, Flushable {
...
//internalCache的方法内部调用的都是Cache的方法
final InternalCache internalCache = new InternalCache() {
@Override
public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override
public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
@Override
public void remove(Request request) throws IOException {
Cache.this.remove(request);
}
@Override
public void update(Response cached, Response network) {
Cache.this.update(cached, network);
}
@Override
public void trackConditionalCacheHit() {
Cache.this.trackConditionalCacheHit();
}
@Override
public void trackResponse(CacheStrategy cacheStrategy) {
Cache.this.trackResponse(cacheStrategy);
}
};
//缓存是用DiskLruCache实现的
final DiskLruCache cache;
...
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
//缓存为key-value形式,其中key为请求的URL的md5值
public static String key(HttpUrl url) {
return ByteString.encodeUtf8(url.toString()).md5().hex();
}
@Nullable
Response get(Request request) {
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
...
return response;
}
...
}
这一看我们就明白了,internalCache的确是Cache的一个内部变量,我们设置了Cache也就有了internalCache。而实际上,internalCache是一个接口类型的变量,它的一系列get、put方法,都是调用的Cache的方法,这也是外观模式的一种典型应用。当然这里internalCache从名字也可以看出是给内部其他对象调用的,所以internalCache和Cache的职责很明确,Cache供外部设置,而internalCache供内部调用。
同时我们看到Cache内部是通过DiskLruCache来实现缓存的,缓存的key就是request的URL的md5值,缓存的值就是Response。我们设置自己的缓存时,可以通过Cache的构造函数传入我们想要存放缓存的文件路径,以及缓存文件大小即可。比如:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(new Cache(context.getExternalCacheDir(), 10*1024*1024));
下面我们回到缓存拦截器CacheInterceptor中,看看具体的缓存逻辑。主要的逻辑都在intercept方法中。
CacheInterceptor的缓存逻辑
@Override
public Response intercept(Chain chain) throws IOException {
//如果我们没有设置缓存或是当前request没有缓存,那么cacheCandidate就为null了
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//如果我们没有设置缓存,或是当前request没有缓存,那么cacheCandidate就为null
//获取具体的缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//后面会根据networkRequest和cacheResponse是否为空来做相应的操作
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
}
可以看到intercept方法中会调用缓存策略工厂的get方法获取缓存策略CacheStrategy。我们进入CacheStrategy看看
public final class CacheStrategy {
/** The request to send on the network, or null if this call doesn't use the network. */
public final @Nullable Request networkRequest;
/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
...
public static class Factory {
final long nowMillis;
final Request request;
final Response cacheResponse;
...
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
...
}
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;
}
//根据不同的情况返回CacheStrategy
private CacheStrategy getCandidate() {
// No cached response.
//如果缓存中的Response为null
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
//如果缓存中的response缺少必要的握手信息
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//根据request和response是否能被缓存来生成CacheStrategy
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
//如果Request中的noCache标志位为true或是request的请求头中包含"If-Modified-Since"或是"If-None-Match"标志位
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//如果缓存的response中的immutable标志位为true,则不请求网络
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
...
}
}
}
可以看到在CacheStrategy的内部工厂类Factory中有一个getCandidate方法,会根据具体的情况生成CacheStrategy类返回,是个典型的简单工厂模式。生成的CacheStrategy中有2个变量,networkRequest和cacheResponse,如果networkRequest为null,则表示不进行网络请求;而如果cacheResponse为null,则表示没有有效的缓存。
当我们没有设置缓存Cache时,显然cacheResponse始终都会为null。下面我们继续看intercept中的方法。
//如果networkRequest和cacheResponse都为null,则表示不请求网络而缓存又为null,那就返回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 {
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());
}
}
根据上述源码我们可以总结出缓存拦截器中的缓存策略:
(1)首先会尝试从缓存Cache中获取当前请求request的缓存response,并根据传入的请求request和获取的缓存response通过缓存策略对象CacheStrategy的工厂类的get方法生成缓存策略类CacheStrategy。
(2)CacheStrategy的工厂类的get方法里面会根据一些规则生成CacheStrategy,这里面的规则实际上控制着缓存拦截器CacheInterceptor的处理逻辑。而这些规则都是根据请求的Request和缓存的Response的header头部信息来生成的,比如是否有noCache标志位,是否是immutable不可变的,以及缓存是否过期等。
(3)CacheInterceptor会根据CacheStrategy中的networkRequest和cacheResponse是否为空,来判断是请求网络还是直接使用缓存。
总结
(1)拦截器链和缓存策略都是OkHttp3的亮点所在,此外还有复用连接池等
(2)拦截器链通过责任链的模式,将网络请求过程中的职责功能都分割开,分别用不同的拦截器来完成失败重连、缓存处理、网络连接等问题。而且用户还可以添加自定义的拦截器,非常灵活,满足面向对象的开闭原则。
(3)缓存策略指的是对于请求的响应的缓存。OkHttp中有专门类Cache来实现缓存,Cache中采用了DiskLruCache,以Request的URL的md5为key,相应Response为value。此外Cache中还通过外观模式对外提供了InternalCache接口变量,用于调用Cache中的方法,也满足面向对象的接口隔离原则和依赖倒置原则等。
(4)缓存拦截器中会根据生成的缓存策略类CacheStrategy的2个变量networkRequest和cacheResponse来决定是连接网络还是直接使用缓存。如果2个变量都为空,则直接返回504,请求失败;如果缓存不为空,则直接返回缓存;如果networkRequest不为空,就通过调用RealInterceptorChain的proceed方法将请求继续转发到下一个拦截器。
不错过每一点精彩,欢迎扫码关注