OKHttp的源码框架很大,涉及的内容也很多,每个类每行代码都去读不实际,这里主要从以下几个点去研究它的源码实现.
- 任务调度过程
- 拦截器
- 缓存机制
- 连接池复用策略
任务调度器Dispatcher
直接先看源码入口:
//将request传给RealCall,然后将RealCall放入Dispatcher去调度
okHttpClient.newCall(request).enqueue(this);
newCall(request):
public Call newCall(Request request) {
return new RealCall(this, request, false);
}
RealCall.enqueue():
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//将这个请求放入dispatcher去调度,因为是异步请求,所以封装成AsyncCall,如果是同步请求,直接将RealCall本身传给Dispatcher, Dispatcher要么直接触发这个请求,要么就将它添加到等待队列中
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
其实可见,Dispatcher才是调度的角色.
Dispatcher的默认线程池
无界线程池,适合执行执行大量的耗时比较少的任务:
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;
}
Dispatcher异步调度enqueue()
//当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时则把请求加载到runningAsyncCalls中并在线程池中执行,否则就再入到readyAsyncCalls中进行缓存等待。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
AsyncCall异步任务执行过程
由于AsyncCall本身是一个Runnable,将它放入线程池执行,其实就是调用它的excute()方法:
protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
1.首先从拦截链中获取Response
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
//这里依次添加调用者的拦截器、重试拦截器、Bridge拦截器、缓存拦截器、连接拦截器、网络拦截器、CallServer拦截器,这是一个责任链模式
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));
//将请求添加到拦截器链中去处理,每个拦截器的处理结果是要么直接处理后返回Response,要么处理后,再将request往下传递,直至到CallServerInterceptor拦截器终结.
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
2.拦截器链的proceed()处理,假设第一个拦截器就是retryAndFollowUpInterceptor
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//httpCodec为空
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");
}
//httpCodec为空
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
//new一个RealInterceptorChain用于处理下一个拦截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next); //这里内部会一直传递下去,直至拿到最终的Response结果.
// Confirm that the next interceptor made its required call to chain.proceed().
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;
}
拦截器链的处理很简单,就是取出interceptor,然后调用它的intercept()方法,返回Response.所以最终的处理其实还是在拦截器,详细过程如图所示:
缓存机制
缓存策略主要是在CacheInterceptor中实现,具体代码如下所示:
public Response intercept(Chain chain) throws IOException {
//判断该request是否有缓存,缓存使用DiskLruCache来实现,request的url的MD5作为缓存key,
Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
long now = System.currentTimeMillis();
//获取缓存策略对象CacheStrategy,根据request设置的缓存策略创建具体的CacheStrategy.例如如果不允许缓存,则直接返回一个不包含缓存结果的CacheStrategy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest; //request
Response cacheResponse = strategy.cacheResponse; //缓存response
...
//networkRequest为空只有一种情况,即request设置了不允许使用网络,即调用者在确实不需要网络请求,缓存就能够满足需求时设置.
//1. 如果此时缓存也为空,那么直接new一个Response并设置相关参数返回给上层即可.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//如果此时缓存不为空,则将缓存Response返回给上层.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//2.如果没有设置不允许使用网络,那么就直接将request传给下层拦截器去处理
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// 关闭IO流
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
//3.获取到下层返回的结果之后,再做以下判断
if (cacheResponse != null) {
//响应码是304,说明cache的数据是最新的,服务器此时不会返回具体的响应体,此时取出缓存,更新请求时间、接受时间等相关信息,再更新缓存,并将结果返回给上层即可.
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();
cache.trackConditionalCacheHit();
//更新缓存数据
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//4.如果返回码不是304,说明请求结果有更新,此时将缓存结果、新结果封装给Response返回给上层
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
//5.如果缓存策略是可缓存的,则将结果存入缓存
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;
所以可以总结为,缓存策略为:
- 若request设置了不能请求网络,则先从缓存中获取结果,如果有,则返回缓存结果,如果没有,则返回空数据以及504错误;
- 否则直接请求网络结果;
- 拿到网络结果后,判断响应码为304,如果是,说明缓存为最新数据,则只要更新缓存数据的请求时间、响应时间等信息,再将缓存数据返回给上层;否则就直接返回网络请求结果.
- 再判读request是否可缓存的,是则将结果写入缓存,否则删除缓存,最后将结果返回给上层.
连接池复用策略
使用一个ConnectionPool去维护一个连接池,避免每个连接都要重新去执行TCP连接,握手等阶段这些耗时操作.
首先看下ConnectInterceptor中:
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
//这个StreamAllocation在retryAndFollowUpInterceptor中创建,顾名思义,就是分配流的一个类
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//这里调用了streamAllocation的newStream去获取一个HttpCodec,再去看看这里面做了什么东西
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
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);
//3.最后获取连接的HttpCodec对象,它是用来解析Http请求,读取输入流,输出流,并封装Response的一个类
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
findHealthyConnection():
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
//这里是一个死循环,去寻找连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);
...
}
findConnection():
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
//异常判断
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 1. 首先获取StreamAllocation已分配的connection,判断是否不为空且允许复用,合法则可以直接返回该连接用于此次请求复用
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// 2.如果不存在已分配的连接,则以空route从连接池里面取一个,如果能够取到,则直接return该连接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
//如果取不到,则将route赋值给selectedRoute
selectedRoute = route;
}
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
//再次以route去尝试获取一个连接
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) {
route = selectedRoute;
return connection;
}
//3.如果找不到,则创建一个新的连接
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
//4.并且为该连接建立TCP连接以及TLS握手
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
//将该连接放入连接池
Internal.instance.put(connectionPool, result);
//如果另外同时创建了同一个指向的连接,那么就复用该连接,release这个连接
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
可见,每次连接都是首先尝试去复用连接,而非每次新建连接,那么能够省去很多连接建立的时间,大大提高效率.新建了连接之后,会将它存储在连接池,实际上存于一个ArrayDeque中,当然连接池不可能无限大,ConnectionPool内部有一个清理线程去管理这个线程池,每隔一段时间去清理闲置时间最长的连接又或者是超过了keep-alive时间的连接又或者是超过了闲置限制的连接,连接池默认最大闲置连接是5个,keep-alive是5分钟.当超过了限制时,清理线程会根据连接的闲置时间或者数目进行清理,避免浪费连接资源.