第一章 OkHttp3源码解析 - 请求流程
第二章 OkHttp3源码解析 - 拦截器
第三章 OkHttp3源码解析 - 连接机制和缓存机制
在Android蛮荒年代,我们做网络请求通常会选用HttpURLConnection或者Apache HTTP Client,这两者均支持HTTPS、流的上传和下载、配置超时和连接池等特性,但随着业务场景的复杂化以及对流量消耗的优化需求,OkHttp应运而生。
现在谈起网络请求,大家肯定下意识想到的就是 okHttp 或者 retrofit 这样的三方请求库。 Google官方 也将源码当中的 HttpURLConnection 底层实现改成 okhttp 了,同时 retrofit 的底层也是 okhttp,足以说明其在日常开发中的重要性。
官方网站:https://github.com/square/okhttp
本文基于okhttp3.12.13源码进行分析
api 'com.squareup.okhttp3:okhttp:3.12.13'
技能树:Builder建造者模式、责任链模式、http、线程池、库里面对代码的封装和组合等。
// 1.创建client
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.cookieJar(CookieJar.NO_COOKIES)
.callTimeout(10000, TimeUnit.MILLISECONDS)
.build();
// 2.创建request
Request request = new Request.Builder()
.url("http://10.34.12.156:68080/admin-api")
.addHeader("Content-Type", "application/json")
.get();
.build();
// 3.发起请求
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {// 失败回调
}
@Override
public void onResponse(Call call, Response response) throws IOException {// 成功回调
}
});
核心对象:
OkHttpClient 和 Request 都是通过建造者模式构建的,这样的好处就在于用户可以根据自己的需求轻松简洁的配置一些可选参数,而不必通过传统方式将不需要的参数写成 null 。
从上述使用示例中,我们可以发现 OkHttpClient相当于是个上下文或者说是大管家,它接到我们给的任务以后,将具体的工作分发到各个子系统中去完成。Okhttp的子系统层级结构图如下所示:
在整个Okhttp的系统中,我们还要理解以下几个关键角色:
我们首先来分析连接的请求与响应流程,这样我们就可以对整个OkHttp系统有一个整体的认识。
OkHttp的整个请求与响应流程如下所示:
Okhttp的整个请求与响应的流程就是Dispatcher不断从Request Queue里取出请求(Call),根据是否已经存在缓存,从内存缓存或者服务器获取请求的数据。
仔细看一下这个流程图,是不是很像计算机网络的OSI七层模型,Okhttp正式采用这种思路,利用拦截器Interceptor将整套框架纵向分层,简化了设计逻辑,提升了框架扩展性。
我们带着几个需要重点关注的问题,去源码中一探究竟,这样效率会更高。
Question:
1.Dispatcher是如何进行请求调度的?
2.各个拦截器是如何实现的?
3.连接与连接池是如何建立和维护的?
我们先来看一下具体的函数调用链,请求与响应的序列图如下所示:
OkHttp请求分为同步和异步两种,同步请求通过调用Call.exectute()方法直接返回当前请求的Response,异步请求调用Call.enqueue()方法将请求(AsyncCall)添加到请求队列中去,并通过回调(Callback)获取服务器返回的结果。
我们将 OkhttpClient 中的 newCall() 作为入口,开启整个同步请求的过程,
详情见OkHttpClient.java
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
// 这里是构建一个 RealCall 对象
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall 实现了 Call 接口,我们先来看看 Call 接口:
public interface Call extends Cloneable {
// 同步请求方法
Response execute() throws IOException;
// 异步请求方法
void enqueue(Callback responseCallback);
// OkHttpClient实现了Factory接口,所以才有newCall方法
interface Factory {
Call newCall(Request request);
}
}
现在我们通过 newCall() 得到了一个 RealCall 对象,然后就能通过 RealCall 当中的 execute() 和 enqueue() 进行网络请求。
详情见:RealCall.java
@Override public Response execute() throws IOException {
synchronized (this) {// // 一个call对象只能执行一次execute方法
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
// 这里主要是个监听器,表示开始进行网络请求了
eventListener.callStart(this);
// 重点关注这块
try {
//通过分发器进行任务分发,其实这里还体现不出分发器的效果,仅仅是将当前请求加入到一个同步队列当中
client.dispatcher().executed(this);
// 通过 getResponseWithInterceptorChain() 获得相应结果
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.java
public final class Dispatcher {
// 正在执行的同步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
// 简单的将当前请求加入到同步请求队列中
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
// finish方法 --> 前面说的首尾工作方法,但是在同步请求中用处不大
void finished(RealCall call) {
finished(runningSyncCalls, call);
}
private <T> void finished(Deque<T> calls, T call) {
顾名思义,分发器空闲时得回调
Runnable idleCallback;
synchronized (this) {
// 对于完成请求的 call ,在这里移除掉
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
//这里是进行收尾工作的,主要体现在异步请求中,因此这个方法先放一放
boolean isRunning = promoteAndExecute();
//进行空闲回调方法
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
}
可以看到,在整个同步请求的过程中,分发器仅仅是将当前的请求加入到一个同步请求队列中,请求完成后再将其移除。因为在同步请求中 finished() 方法只有一个回调作用,因此我们将它放一放,重点看一看异步请求中的 finished()。
实际业务中,异步请求往往较多。异步请求就比同步请求稍微复杂了一点,我们仍然是从 RealCall 中看起。
RealCall.java
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {//一个call对象只能执行一次execute方法
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
// 这里面依旧加上了监听
eventListener.callStart(this);
// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
AsyncCall 是 RealCall 的内部类, 它实现了 Runnable 接口,主要是为了能在线程池中去执行它的 run() 方法。
AsyncCall.java
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
// 将asyncCall添加到线程池中去执行的方法
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 线程池去执行当前AsyncCall对象的run方法
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
// 收尾工作,其实内部调用的是 promoteAndExecute()
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
@Override protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
// getResponseWithInterceptorChain() 获得请求的响应结果
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
// 请求成功的回调
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
e = timeoutExit(e);
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} catch (Throwable t) {
cancel();
if (!signalledCallback) {
IOException canceledException = new IOException("canceled due to " + t);
// 请求失败的回调
responseCallback.onFailure(RealCall.this, canceledException);
}
throw t;
} finally {
// 进行收尾工作,相比同步请求的finished方法,这儿更重要
client.dispatcher().finished(this);
}
}
}
现在我们回到 Dispatcher 中去。
Dispatcher.java
public final class Dispatcher {
// 准备执行的异步请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 正在执行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
void enqueue(AsyncCall call) {
synchronized (this) {
// 添加当前asyncCall加到准备执行的异步请求队列中
readyAsyncCalls.add(call);
}
// dispatcher进行分发call任务的方法
promoteAndExecute();
}
// 关键方法,dispatcher进行任务分发的方法,进行收尾工作时,也是调用的它
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
// 需要开始执行的任务集合
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {// 迭代等待执行异步请求
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
// 正在执行异步请求的总任务数不能大于64个,否则直接退出这个循环,不再将请求加到异步请求队列中
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
// 同一个host的请求数不能大于5,否则直接跳过此call对象的添加,去遍历下一个asyncCall对象
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
// 加到需要开始执行的任务集合中
executableCalls.add(asyncCall);
// 将当前call加到正在执行的异步队列当中
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
// 遍历每一个集合中的asyncCall对象,将其添加到线程池中,执行它的run方法
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
}
现在我们通过 Dispatcher 将 AsyncCall 对象通过挑选,加到了线程池中。挑选的限制有两个:
//Dispatcher.java
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
接下来,我们再回头看看将 AsyncCall 对象加到线程池后的一些细节:
//Dispatcher.java
// 将asyncCall添加到线程池中去执行的方法
private boolean promoteAndExecute() {
for(...){
asyncCall.executeOn(executorService());
}
}
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 这里是之前自定义了创建了一个ExecutorService
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
// 这里也是会执行收尾工作
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
public synchronized ExecutorService executorService() {
if (executorService == null) {
//这里的corePoolSize是 0 !!这里的corePoolSize是 0
// !!阻塞队列是 SynchronousQueue!!阻塞队列是 SynchronousQueue
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
我们先来看 executeOn() 方法,它的主要工作就是执行添加到线程池的 AsyncCall 对象的 run() 方法,去进行网络请求。其次我们目光移动到 finally 语句块,会发现每次执行完 run() 方法后,即完成网络请求后,都会去执行这个 finished() 方法。前面讲到过,内部其实是再次调用了 promoteAndExecute() 方法。那这是为什么呢?
还记得到我们从准备执行的异步队列中挑选一些 AsyncCall 对象拿到线程池中执行吗?如果记得,那你是否还记得我们是有挑选条件的,正因如此,可能在准备执行的异步请求队列中会有一些 AsyncCall 对象不满足条件仍然留在队列里!那我们难道最后就不执行这些网络请求了吗?当然不是!原来每完成一次网络请求就会再次触发 Dispatcher 去分发 AsyncCall 对象!
然后我们再来看看这里用到的线程池是一个什么样的线程池。在上面我贴出来的代码中可以看到,这个线程池的 corePoolSize 是 0,BlockingQueue 是 SynchronousQueue,这样构建出来的线程池有什么特殊之处吗?熟悉线程池的同学都应该知道,当任务数超过了 corePoolSize 就会将其加到阻塞队列当中。也就是说这些任务不会立马执行,而我们的网络请求可不想被阻塞着,因此这里的 corePoolSize 就设置成了 0。BlockingQueue 设置成 SynchronousQueue 也是类似道理,SynchronousQueue 是不储存元素的,只要提交的任务数小于最大线程数就会立刻新起线程去执行任务。
总结一下整个 okhttp 网络请求的整个过程:
okhttp的又一大特点是整个请求流程是由拦截器一层层分发下去,最后得到结果再一层层返回上来。 如图:
okhttp 内置了五大拦截器,这五大拦截器各司其职,通过责任链模式将请求逐层分发下去,每层完成自己这层该做的事,最后拿到相应结果逐层往上返回结果:
注意:这五个是系统内置的拦截器,我们也可以通过 addInterceptor() 加入我们自己写的拦截器.
关于拦截器,详细可看 第二章 OkHttp3源码解析 - 拦截器。