上一篇文章简要的介绍了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方法说起:
@Override public Call newCall(Request request) { //创建一个具体的请求调用对象RealCall return new RealCall(this, request, false /* for web socket */); } 复制代码
@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)); } 复制代码
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. } } 复制代码
至此,同步请求和异步请求的流程就分析完了,从中我们可以看出还是比较简单,但是请求的具体执行执行流程是怎么实现的,也就是说怎么在拦截器处理链中处理的,这个是框架最核心的部分,我们还没有具体分析,在后面的篇幅中我们将重点介绍。