上一篇文章简要的介绍了OkHttp的API简单使用,通过创建了OkHttpClient和Request这些对象就能对远程请求建立连接,获取数据。本篇文章将对OkHttp的请求流程做更进一步的深入分析,从源码的角度来看看它的请求流程是具体怎么执行的。
请求方式
请求流程
OkHttp提供了两种请求方法,分别是同步和异步。其实两种方式唯一的区别就在于,异步方式是将我们的请求放入了一个线程池来执行的,其具体的底层请求实现机制都是一样的。这里先说一下大概的请求流程,以便在看后续的代码分析过程中能保持清晰的思路。
我们的请求都是通过一个OkHttpClient的对象来发起的,这个对象是个入口类,保存了用户的配置信息和一些上下文环境信息。Call是一个接口,可以抽象成用户发起的请求调用,具体的实现类是RealCall这个对象,Requset就是具体的请求,保存了你要请求的地址,ip,端口等信息。
客户端OkHttpClient发起调用请求,首先创建一个RealCall对象,然后根据用户是选择同步还是异步方式来执行不同流程。如果是同步方式,则直接执行,如果是异步方式,则将请求放入线程池来执行,最后通过一个拦截器链来完成具体的请求过程并获取响应。这个拦截器链是整个OkHttp框架的核心,我会在后面详细地介绍,这里你可以就把它理解为一个执行具体请求的对象即可。用一个图来描述吧,下图就是请求流程的过程图:
下面就深入代码来看看两种不同的执行方式是怎么实现的同步请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com").build();
Response response = client.newCall(request).execute()
复制代码
从OkHpptClient的入口newCall方法说起:
- 执行newCall,创建一个具体的请求调用对象RealCall
@Override public Call newCall(Request request) {
//创建一个具体的请求调用对象RealCall
return new RealCall(this, request, false /* for web socket */);
}
复制代码
- RealCall通过excute方法执行具体的请求
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
//将当前的请求加入runningSyncCalls队列
client.dispatcher().executed(this);
//执行请求获取响应
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
复制代码
在执行请求前会将当前的call加入一个叫做runningSyncCalls的队列中,用于请求调用过程中的状态管理和跟踪处理,RealCall共有三个队列:
-
异步请求准备队列,当异步请求数量达到上限,会将请求保存于此队列中,待后续处理readyAsyncCalls 复制代码
-
当前正在执行的异步请求队列,存储当前正在执行的请求runningAsyncCalls 复制代码
-
当前正在处理的同步请求队列runningSyncCalls 复制代码
- 调用拦截器链完成请求处理
Response result = getResponseWithInterceptorChain(); 复制代码
上面这段代码是整个OkHttp框架中最核心的一句,它内部实现是通过一系列的拦截器组成了拦截器链,来完成请求的具体请求过程,包括重试,请求体处理,响应头处理,响应体处理等逻辑。这里我们不对它进行展开说明,你只需知道我们的请求是通过一个拦截器链来请求和获取响应即可。上面的流程时序图如下所示:
异步请求
调用Call对象的excute方法执行同步请求,调用enqueue(Callback responseCallback)就会执行异步请求,该方法需要传入一个响应回调接口,用于响应请求执行成功或执行失败时的操作。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
//标志状态
executed = true;
}
captureCallStackTrace();
//将请求加入队列,并通过线程池来执行
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
复制代码
- 请求入队 通过dispatch对象,将请求加入队列
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//如果已达到当前最大的处理请求数,则排队等待
readyAsyncCalls.add(call);
}
}
复制代码
首先会判断是否达到了请求上限,这里的请求上限包含两种:一、总的最大并发请求数;二、每个host绑定的最大并发请求数。如果两个都没有达到上限则会直接加入线程池进行处理。再来看看创建线程池的代码
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;
}
复制代码
这里的线程池没有核心线程,也就是说当没有请求时不会有空闲的线程消耗资源,但是最大线程数为Integer.MAX_VALUE,似乎与我们平时的一些规范是冲突的。但还记得前面刚说过的请求上限吗,其实这个上限数就限制了最大能创建的线程数,所以不用担心。
- 如果没有达到请求上限,调用线程池执行请求
executorService().execute(call)
复制代码
执行的call对应的实现类为AsyncCall,实际上它继承了Runable()接口,在这个对象的execute()方法中实现了具体的请求逻辑
@Override 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 {
//完成请求处理,从runningSyncCalls队列中移除当前请求对象,从readyAsyncCalls中出队请求,加入runningSyncCalls,并调用线程池执行
client.dispatcher().finished(this);
}
}
}
复制代码
具体执行请求的方法与同步请求方式中一样,都是调用拦截器链来处理的
Response response = getResponseWithInterceptorChain();
复制代码
-
如果请求达到请求上限,则加入等待队列,这个在前面第一步中请求进入队列时已经说了,参考前面介绍
-
每执行完一个请求,从等待队列中按顺序出队,进入线程池执行。在ASyncalCall对象的process方法中,最后都会执行finally方法体,调用finish方法从等待队列中取出请求并执行
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//从等待队列中出队请求到运行队列,调用线程池执行
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
复制代码
具体看看promoteCalls方法,它完成了从等待队列中取出请求并执行请求的逻辑
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
//从等待队列中取出一个请求
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
//加入运行队列
runningAsyncCalls.add(call);
//执行请求
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
复制代码
总结
至此,同步请求和异步请求的流程就分析完了,从中我们可以看出还是比较简单,但是请求的具体执行执行流程是怎么实现的,也就是说怎么在拦截器处理链中处理的,这个是框架最核心的部分,我们还没有具体分析,在后面的篇幅中我们将重点介绍。