对OkHttp介绍,我们分为使用篇和源码分析篇两个系列进行介绍。
在上一篇文章中,我们大致梳理了一下OkHttp的请求过程。但是中间涉及到的client.dispatcher().executed(this);
、client.dispatcher().enqueue(new AsyncCall(responseCallback));
和getResponseWithInterceptorChain()
这几个过程我们并没有进入去详细分析,只是提了每一个方法的作用。所以本篇文章的内容就是对这几个方法进行说明。
首先,前两个方法都是通过client.dispatcher()
获得了Dispatcher
对象,然后调用它对应的方法。这里我们就先介绍下Dispatcher
这个OkHttp框架中的任务调度类。
1 Dispatcher
在纤细介绍该类之前,我们先看下为什么通过client.dispathc()
就能获得到Dispatcher对象了,也就是先介绍下该对象的创建过程是怎么样的。
1.1 Dispatcher的创建过程
各位应该还记得我们OkHttp的基本使用方式吧:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
第一步就是创建OkHttpClient对象,我们看下它的构造方法:
public OkHttpClient() {
this(new Builder());
}
创建Builder
后,调用了它的有参构造,我们先看下Builder
的构造过程:
public Builder() {
dispatcher = new Dispatcher();
……
}
这里省略一些代码,但是可以看到,在Builder
的构造方法中,初始化了一个Dispatcher
对象。然后我们在回到上一步流程,调用OkHttpClient的
有参构造:
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
……
}
也省略了无关代码,可以看到,这里就将Builder中的dispather
赋值给了OkHttpClient
中的dispatcher属性。所以我们通过client.dispater()
就能获取到Dispatcher
对象了:
public Dispatcher dispatcher() {
return dispatcher;
}
Dispatcher怎了来的搞清楚了,我们接着就看下它是怎么起到任务调度作用的。
1.2 Dispater简介
在具体跟踪分析上述提到的excuted(Call)
和equeue(AnsycCall)
之前,我们先来简单看下Dispatcher这个类的成员变量,这有助于我们后面的流程分析。
public final class Dispatcher {
// 最大请求数目
private int maxRequests = 64;
// 统一个Host最大请求数目
private int maxRequestsPerHost = 5;
// 线程池 用于执行异步请求任务
private @Nullable ExecutorService executorService;
// 准备发起异步请求的双向队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 正在发起异步请求的双向队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 正在发起同步请求的双向队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
……
}
这里列出了几个比较关键的字段。其中maxRequests
规定了同一个OkHttpClient
中可同时发起的最大请求数,maxRequestsPerHost
规定了统一Host中可同时发起的最大请求数。接着executorService
是一个线程池,最终我们的异步任务就是通过该线程池执行的。从这我们可以看到,并没有在这里直接进行初始化,OkHttp框架进行了一种懒加载策略的,在它的getter方法中可以看到它的创建:
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;
}
可以看到,该线程池的核心线程数为0,保活60s,也就意味着空闲线程在等待60秒后如果还没有使用就会被回收。以Integer.MAX_VALUE
为最大线程数数量,我们也就可以认为该线程池是没有上界限制的,只要有合法请求,就会创建新的线程,这里的合法请求是指没有超过maxRequests
和maxRequestsPerHost
的限制。
剩下还有三个双向队列的属性,注释里面已经说的比较清楚了,这里就不再赘述了。
1.3 Dispatcher任务调度过程
有了上面的基础,就可以进入任务调度过程分析了,这里我们还是先看同步任务,这里贴出RealCall.execute
方法
@Override 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);
}
}
直接跟进去看下Dispatcher.executed(ReallCall)
方法:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
非常简单,就是将传入的RealCall
加入了runningSyncCalls
这个队列中。接着看上面的流程,当请求完成后最终会调用Dispatcher.finishd(ReallCall)
方法,我们进去看下:
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();
}
}
此时这里的参数calls
就是runningSyncCalls
对象,所以这里第一步就是将之前的Call
移除队列。然后执行了promoteAndExecute();
这个方法,该方法如果返回fasle说明当前没有请求任务了,所以后续做了判断,如果没有任务,idleCallback
也不为null的话,就会回调该对象的run
。所以如果我们想在空闲的时候做一些处理,就可以设置IdleCallback
接口。接着进入promoteAndExecute();
方法里面具体看下是怎么处理的:
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
// 从readyAsyncCalls取出任务,加入executableCalls集合
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;
}
// 从executableCalls里面一次取出AsyncCall对象,并调用它的executeOn()方法
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
这个方法里面就是从readyAsyncCalls
队列中取出待执行的异步任务,调用异步任务的executeOn(executorService())方法。其中参数是之前所述的线程池对象——executorService
。到这里就和上篇分析的异步请求流程接上了,这里就不再往下分析了。
到这里,同步调度过程就分析完了,可以看到,同步请求任务并没真正由Dispatcher来调度,只是在其内部维护了一个双向队列而已。所以Dispatcher主要目的还是用来调度异步任务的,接下来看异步调度过程。
还是从ReallCall
的enqueue(Callback)
方法开始跟踪:
@Override 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));
比较简单,通过我们传入的Callback
对象构造了一个AsyncCall
,然后调用了Dispatcher
的enqueue(AsyncCall)
方法,跟进去:
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
就是将call加入到readyAsyncCalls
队列中,然后调用了promoteAndExecute();
这个方法,前面已经分析过了,这里不再赘述。通过上篇文章的分析,我们知道,异步请求,最终在AsyncCall对象中执行excute()
方法的时候,最终也会调用Dispatcher.finish(AsyncCall)
方法,最终会调用finished(Deque<T> calls, T call)
这个方法,这个方法前面已经分析又会去执行promoteAndExecute();
方法,直到任务队列中没有待执行的任务。
到这,整个整个任务的调度就分析完了。接下来我们接着看下上篇没有详细说明的getResponseWithInterceptorChain();
方法。
2 Interceptor调用链
上篇文章我们只是说了getResponseWithInterceptorChain();
会进入到我们的设置的Interceptor中,并最终返回请求的数据。具体是怎么实现的呢?我们跟进去看下:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加开发者应用层自定义的Interceptor
interceptors.addAll(client.interceptors());
//这个Interceptor是处理请求失败的重试,重定向
interceptors.add(retryAndFollowUpInterceptor);
//这个Interceptor就是OkHttp框架自动是添加一些额外的支持
//(如:使用篇里面说到的gzip压缩支持就是在这里面处理)
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());
}
//一个包裹这request的chain
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//这里就把chain传递到第一个Interceptor调用链中了
return chain.proceed(originalRequest);
}
这里创建了一个ArrayList,让后不断的往这个list里面添加Interceptor。至于每个Interceptor的作用,代码注释里面已经说明了。最后通过RealInterceptorChain.proceed(Request)
方法进入了Interceptor的调用链
2.1.5 RealInterceptorChain.proceed()
接着上面的步骤,我们看下怎么通过proceed()
方法就进入了Interceptor的调用链的。分析这个方法之前,先注意上面创建RealInterceptorChain
对象的初始化参数:
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
这里传入了我们的Interceptor的List集合和Request对象,同时初始化index=0(第五个参数)。
接着看下proceed()
方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
……
// 创建下一个Interceptor所需的RealInterceptorChain
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 获取index所在的Interceptor,并调用该Interc的intercept(Chain)方法
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
……
return response;
}
这里暂时只需要我们先把整个调用链的流程搞清楚,所以就只留下了关键代码。其他内容,后续文章分析网络请求的时候,还会详细说明。
根据上面的描述我们知道当前这个Chain对象中的index=0,所以Interceptors.get(index)
是第一个Interceptor,紧接着又使用Request和index+1的索引构建了下一个Chain对象。然后进入第一个Interceptor的intercept(Chain)
方法,传入的是新的Chain
对象。根据之前使用篇里面学习的知识,我们知道在这个方法中,如果我们需要放行一般会调用chain.process(Request)
方法,这样就会进入上面新创建的Chain
的process(Request)
会进入该方法里面,此时index=1的,所以又会取出下一个Interceptor出来,以此类推,一层一层往下执行,最终就形成了Interceptor的调用链。
根据以上分析,我们可以画出大致的调用链的图如下:
这里只做整体流程分析,具体每个Interceptor里面做的事情就不做详述了。最后一个CallServerInterceptor
的intercept(Chain)
方法里面里面是没有调用chain.proceed()
方法的,直接从网络获取Response并返回了,又一层一层的返回到最上面getResponseWithInterceptorChain
方法中。最终就形成了一个“U”型结构的调用链。