最近在看 Okhttp 的源码。不得不说源码设计的很巧妙,从中能学到很多。其实网上关于 Okhttp 的文章已经很多了,自己也看了很多。但是俗话说得好,好记性不如烂笔头,当你动手的时候,你会发现你在看的时候没有注意到的很多细节。 本次要分析的 Okhttp 版本是 3.8.1,在 gradle 中引用如下: implementation 'com.squareup.okhttp3:okhttp:3.8.1' implementation 'com.squareup.okio:okio:1.7.0' 之所以选择分析3.8.1,是因为最新版是采用 Kotlin 写的,因为本人 Kotlin 实力不允许,所以只能分析 Java 版本。 使用示例 1、发起一个异步 GET 请求,代码具体如下: 复制代码 String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .get()//默认就是GET请求,可以不写 .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "onFailure: "); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "onResponse: " + response.body().string()); } }); 复制代码 2、发起一个同步 GET 请求,代码具体如下: 复制代码 String url = "http://wwww.baidu.com"; OkHttpClient okHttpClient = new OkHttpClient(); final Request request = new Request.Builder() .url(url) .build(); final Call call = okHttpClient.newCall(request); new Thread(new Runnable() { @Override public void run() { try { Response response = call.execute(); Log.d(TAG, "run: " + response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }).start(); 复制代码 可以看到两个请求基本大同小异,总结下过程如下: 先创建 OkHttpClient 实例; 构造 Request 实例,传入 url 等相关参数; 通过前两步中的实例对象构建 Call 对象; 异步请求通过 Call#enqueue(Callback) 方法来提交异步请求,同步请求通过 Call#execute() 直接获取 Reponse ; 通过示例,大家简单了解 Okhttp 中的一些对象,下面开始梳理整个请求逻辑。先从 OkHttpClient 开始。 OkHttpClient 当我们发起请求的时候,需要先构造 okHttpClient 对象,代码具体如下: 复制代码 public OkHttpClient() { this(new Builder()); } 复制代码 可以发现是使用了 builder 建造者模式;来看看里面的内容 复制代码 public Builder() { dispatcher = new Dispatcher(); // 调度器 protocols = DEFAULT_PROTOCOLS; // 协议 connectionSpecs = DEFAULT_CONNECTION_SPECS; //传输层版本和连接协议 eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); cookieJar = CookieJar.NO_COOKIES; //cookie socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; //证书链 proxyAuthenticator = Authenticator.NONE; //代理身份验证 authenticator = Authenticator.NONE; //本地身份验证 connectionPool = new ConnectionPool(); //链接池 复用连接 dns = Dns.SYSTEM; //域名 followSslRedirects = true; followRedirects = true; //本地重定向 retryOnConnectionFailure = true; connectTimeout = 10_000; readTimeout = 10_000; //读取超时 writeTimeout = 10_000; //写入超时 pingInterval = 0; } 复制代码 OkHttpClient 内部已经实现了 OkHttpClient(Builder builder),如果我们不需要配置 client,okhttp 已将帮我们默认实现了配置。总结起来主要有以下几点: 里面包含了很多对象,其实 OKhttp 的很多功能模块都包装进这个类,让这个类单独提供对外的 API,这种外观模式的设计十分的优雅,叫做外观模式。 而内部模块比较多,就使用了 Builder 模式(建造器模式),通常用于参数比较多情况。 它的方法只有一个:newCall 返回一个 Call 对象(一个准备好了的可以执行和取消的请求)。 Request 接下来,我们看 Request 请求类,主要包含下面几个属性: url,请求方法名,请求头部,请求体,从属性就可以判断出 Request 主要作用。 复制代码 Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tag = builder.tag != null ? builder.tag : this; } 复制代码 Call HTTP请求任务封装。可以说我们能用到的操纵基本上都定义在这个接口里面了,所以也可以说这个类是 Okhttp 类的核心类了。我们可以通过 Call 对象来操作请求了。而 Call 接口内部提供了 Factory 工厂方法模式 (将对象的创建延迟到工厂类的子类去进行,从而实现动态配置),下面是 Call 接口的具体内容: 复制代码 public interface Call extends Cloneable { /** Returns the original request that initiated this call. */ Request request(); /** * Invokes the request immediately, and blocks until the response can be processed or is in * error. * *

To avoid leaking resources callers should close the {@link Response} which in turn will * close the underlying {@link ResponseBody}. * *

@{code
   *
   *   // ensure the response (and underlying response body) is closed
   *   try (Response response = client.newCall(request).execute()) {
   *     ...
   *   }
   *
   * }
* *

The caller may read the response body with the response's {@link Response#body} method. To * avoid leaking resources callers must {@linkplain ResponseBody close the response body} or the * Response. * *

Note that transport-layer success (receiving a HTTP response code, headers and body) does * not necessarily indicate application-layer success: {@code response} may still indicate an * unhappy HTTP response code like 404 or 500. * * @throws IOException if the request could not be executed due to cancellation, a connectivity * problem or timeout. Because networks can fail during an exchange, it is possible that the * remote server accepted the request before the failure. * @throws IllegalStateException when the call has already been executed. */ Response execute() throws IOException; /** * Schedules the request to be executed at some point in the future. * *

The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually * immediately unless there are several other requests currently being executed. * *

This client will later call back {@code responseCallback} with either an HTTP response or a * failure exception. * * @throws IllegalStateException when the call has already been executed. */ void enqueue(Callback responseCallback); /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */ void cancel(); /** * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain * #enqueue(Callback) enqueued}. It is an error to execute a call more than once. */ boolean isExecuted(); boolean isCanceled(); /** * Create a new, identical call to this one which can be enqueued or executed even if this call * has already been. */ Call clone(); interface Factory { Call newCall(Request request); } } 复制代码 RealCall RealCall 继承自 Call,是真正发起请求的的实体类。RealCall 主要方法: 同步请求 :client.newCall(request).execute(); 异步请求: client.newCall(request).enqueue(); 下面我们来看看里面具体的内容: 复制代码 RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { final EventListener.Factory eventListenerFactory = client.eventListenerFactory(); this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // TODO(jwilson): this is unsafe publication and not threadsafe. this.eventListener = eventListenerFactory.create(this); } 复制代码 可以发现,其内部持有了 client,原始请求,以及请求事件回调 Listener 等。我们看下请求的回调 Listener 的具体内容: 复制代码 public void fetchStart(Call call) { } public void dnsStart(Call call, String domainName) { } public void dnsEnd(Call call, String domainName, List inetAddressList, Throwable throwable) { } public void connectStart(Call call, InetAddress address, int port) { } public void secureConnectStart(Call call) { } public void secureConnectEnd(Call call, Handshake handshake, Throwable throwable) { } public void connectEnd(Call call, InetAddress address, int port, String protocol, Throwable throwable) { } public void requestHeadersStart(Call call) { } public void requestHeadersEnd(Call call, Throwable throwable) { } public void requestBodyStart(Call call) { } public void requestBodyEnd(Call call, Throwable throwable) { } public void responseHeadersStart(Call call) { } public void responseHeadersEnd(Call call, Throwable throwable) { } public void responseBodyStart(Call call) { } public void responseBodyEnd(Call call, Throwable throwable) { } public void fetchEnd(Call call, Throwable throwable) { } 复制代码 可以看到 OkHttp 的回调做得非常细致,有各种各样的回调,不管你想不想用,都帮你考虑到了呢。这样我们可以监听具体的回调,然后做一些操作。 接下去就要开始讲异步请求的具体步骤呢。先从异步请求讲起,这也是我们最常用的。 复制代码 // RealCall @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 (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的 call,可以调用如下方法:进行克隆 复制代码 @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state. @Override public RealCall clone() { return RealCall.newRealCall(client, originalRequest, forWebSocket); } 复制代码 利用 dispatcher 调度器,来进行实际的执行 client.dispatcher().enqueue(new AsyncCall(responseCallback)),在上面的 OkHttpClient.Builder 可以看出已经初始化了 Dispatcher。 心细的读者可能发现一个问题了,那就是这里 enqueue 明明是一个封装了 responseCallback 的 AsyncCall ,怎么就会变成加入队列执行请求了呢?这个下面我会进行解释。 Dispatcher Dispatcher 是 Okhttp 的调度器,用来管理控制请求的队列。内部通过线程池来确保队列的有序运行。先看下 enqueue 方法的具体内容: 复制代码 synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } 复制代码 可以看到内部存在两个队列,一个是正在运行的队列 runningAsyncCalls,另一个是 readyAsyncCalls 队列。如果当前运行数小于最大运行数,并且当前请求的host小于最大请求个数,那么就会直接加入运行队列,并运行。如果超了,就会加入准备队列。 复制代码 /** Ready async calls in the order they'll be run. */ private final Deque readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque runningAsyncCalls = new ArrayDeque<>(); /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque runningSyncCalls = new ArrayDeque<>(); 复制代码 实际上还有一个同步队列,没有给同步队列做限制,只要一加入就开始执行请求。 当请求队列完成请求后需要进行移除,看下 finished 的代码逻辑: 复制代码 private void finished(Deque 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; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } 复制代码 可以看到,这是使用了泛型,不用关心具体传入的队列是哪一个,直接就可以移除。promoteCalls 为 true 代表是异步请求队列,还得从 readyAsyncCalls 队列里面取出一个队列添加到 runningAsyncCalls 队列里面去执行请求。 复制代码 private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator 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. } } 复制代码 通过上述代码,关于调度器的功能作用就基本理清了。 AsyncCall AsyncCall 是 RealCall 里面的内部类,继承自 NamedRunnable,是自定义的Runnable,可以为线程设置 name。内部代码具体如下: 复制代码 public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); } 复制代码 可以发现,在 run 方法内部调用了execute 方法,这个方法就是真正的发起请求的逻辑。下面我们看下 AsyncCall 中的该方法得具体内容: 复制代码 @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 { client.dispatcher().finished(this); } } } 复制代码 获取响应数据最终是是通过 getResponseWithInterceptorChain() 来获取的。然后通过回调将 Response 返回给用户。 值得注意的 finally 执行了client.dispatcher().finished(this) 通过调度器移除队列。移除得逻辑在前面也已经讲过了。 下面看下 getResponseWithInterceptorChain 方法内部的具体逻辑: 复制代码 //Realcall 核心代码 开始真正的执行网络请求 Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. // 责任链 List interceptors = new ArrayList<>(); // 在配置okhttpClient 时设置的intercept 由用户自己设置 interceptors.addAll(client.interceptors()); // 负责处理失败后的重试与重定向 interceptors.add(retryAndFollowUpInterceptor); // 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息 // 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。 interceptors.add(new BridgeInterceptor(client.cookieJar())); // 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响