1. OkHttp官網介紹:
OkHttp: An HTTP+HTTP/2 client for Android and Java applications. 該庫支援 HTTP1.0、HTTP1.1、HTTP2.0 以及 SPDY ,都在類Protocol 中宣告。
public enum Protocol {
/**
* An obsolete plaintext framing that does not use persistent sockets by default.
*/
HTTP_1_0("http/1.0"),
/**
* A plaintext framing that includes persistent connections.
*
* <p>This version of OkHttp implements <a href="https://tools.ietf.org/html/rfc7230">RFC
* 7230</a>, and tracks revisions to that spec.
*/
HTTP_1_1("http/1.1"),
/**
* Chromium's binary-framed protocol that includes header compression, multiplexing multiple
* requests on the same socket, and server-push. HTTP/1.1 semantics are layered on SPDY/3.
*
* <p>Current versions of OkHttp do not support this protocol.
*
* @deprecated OkHttp has dropped support for SPDY. Prefer {@link #HTTP_2}.
*/
SPDY_3("spdy/3.1"),
/**
* The IETF's binary-framed protocol that includes header compression, multiplexing multiple
* requests on the same socket, and server-push. HTTP/1.1 semantics are layered on HTTP/2.
*
* <p>HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support {@linkplain
* CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} , present in Java 8+ and Android 5+. Servers
* that enforce this may send an exception message including the string {@code
* INADEQUATE_SECURITY}.
*/
HTTP_2("h2");
}
複製程式碼
2. OkHttp的基本使用:
(1) Step One: 構建OkHttpClient物件
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
(2) Step Two: 構建Request物件
Request request = new Request.Builder().url("xxxxx").build();
(3) Step Three: 通過上兩步建立的物件生成Call
Call newCall = okHttpClient.newCall(request);
(4) Step Four: 使用Call傳送非同步或同步請求,獲取Response物件。
// 同步請求:
Response response = newCall.execute();
// 非同步請求:
newCall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
複製程式碼
(5) 取消網路請求:
newCall.cancel();
(6) 注意事項:
- OkHttp傳送非同步請求,CallBack回撥依舊執行在子執行緒,所以不能直接進行UI更新操作。
- 同一個Call只能執行一次同步或者非同步網路請求。
3. OkHttp流程圖
從整體來看,我們通過構建OkHttpClient物件,並呼叫其newCall (Request) 方法生成一個真正用於執行網路請求的Call例項。call.execute()進行同步網路請求,call.enqueue()進行非同步網路請求。但不管是同步還是非同步,在網路請求前,先將這個請求放入到dispatcher的請求佇列中,然後getResponseWithInterceptorChain()來鏈式呼叫各攔截器(如下圖所示)獲取Response物件,最後將這次請求從佇列中移除。4. 核心程式碼分析
4.1 OkHttpClient建立(Builder模式):
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
public Builder() {
dispatcher = new Dispatcher();
...
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
複製程式碼
public OkHttpClient build() {
return new OkHttpClient(this);
}
複製程式碼
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
...
}
複製程式碼
其中在Builder構造器內建立了Dispatcher物件,最終通過Builder.build()將Dispatcher傳遞給OkHttpClient,所以我們一定要記住OkHttpClient中存在Dispatcher物件。當然,既然OkHttpClient採用Builder模式建立例項,就允許我們以鏈式呼叫的方式對OkHttpClient進行配置,正如下面所示,但這不是本文關注的重點。
- connectTimeout() 設定連線超時時間
- cache() 設定快取檔案並配置快取大小
- addInterceptor() 新增應用層攔截器(請求鏈式呼叫流程圖.PNG 圖中 “自定義應用層攔截器”)
- addNetworkInterceptor() 新增網路層攔截器(請求鏈式呼叫流程圖.PNG 圖中 “自定義網路層攔截器”)
- ...
4.2 Request建立(Builder模式):
Request request = new Request.Builder().url("xxxxx").build();
和上面建立OkHttpClient一樣,依舊Builder模式允許使用者靈活配置請求。
- url() 新增網路請求地址
- addHeader() 新增網路請求頭資訊
- cacheControl() 設定本次請求的快取方式
- get() post() put() delete() ... 設定請求的方式,支援restful風格
4.3 Call物件生成:
Call newCall = okHttpClient.newCall(request);
OkHttpClient.class 中的newCall() 方法:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製程式碼
RealCall.class 中的newRealCall() 方法:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
...
return call;
}
複製程式碼
到此處為止,我們通過OkHttpClient.newCall(Request)生成一個newRealCall物件,這個物件包含了OkHttpClient和Request引用,所以我們完全可以在RealCall類中做剩餘工作了,而事實也正是如此。
4.4 開始同步(非同步)網路請求:
RealCall 的同步方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed"); // 這裡可以解釋為什麼每個call只能執行一次。
executed = true;
}
try {
client.dispatcher().executed(this); // 將RealCall存到之前強調的OkHttpClient的dispatcher中
Response result = getResponseWithInterceptorChain(); // 真正向伺服器傳送網路請求的程式碼,後面會具體說明。
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) { }
finally {
client.dispatcher().finished(this); // 執行請求後將RealCall從dispatcher中移除
}
}
複製程式碼
同步請求,通過dispatcher對RealCall儲存和移除邏的輯相當簡單,只是維護了一個集合用於管理。
Dispatcher.class中的同步請求的新增和移除方法:
public final class Dispatcher {
...
// 同步請求集合
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
// 新增同步請求
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
// 移除同步請求
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
...
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls(); // 同步請求,此處不會被呼叫。
}
...
}
...
}
複製程式碼
RealCall的非同步方法:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed"); // 這裡可以解釋為什麼每個call只能執行一次。
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback)); // AsyncCall為RealCall的內部類,實現Runnable介面,用於執行緒池的排程。
}
複製程式碼
先不講client.dispatcher().enqueue(xx)具體程式碼實現,我們先看一下AsyncCall的結構。
final class RealCall implements Call {
...
final class AsyncCall extends NamedRunnable {
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
@Override protected void execute() { ... }
}
}
複製程式碼
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();
}
複製程式碼
AsyncCall作為RealCall的內部類,AsyncCall引用RealCall的例項物件,同時AsyncCall實現了Runnable介面,一旦開始執行就會呼叫AsyncCall 的execute()方法。知道了AsyncCall 的基本結構,就可以看client.dispatcher().enqueue(new AsyncCall(responseCallback)) 內部具體實現了。
public final class Dispatcher {
private int maxRequests = 64; // 同時進行的非同步網路請求最大數
private int maxRequestsPerHost = 5; // 同一個網路請求主機地址允許最大請求個數
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); // 非同步請求快取佇列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); // 非同步請求執行佇列
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;
}
synchronized void enqueue(AsyncCall call) { // 新增非同步請求。
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { // 判斷能否放入非同步網路請求執行佇列
runningAsyncCalls.add(call); // 將非同步請求新增到執行佇列
executorService().execute(call); // 執行非同步網路請求
} else {
readyAsyncCalls.add(call); // 將非同步請求新增到等待佇列
}
}
...
}
複製程式碼
執行非同步網路請求交由ThreadPoolExecutor處理,執行runnable的run方法,之前先看過了AsyncCall的結構,runable的具體實現是通過AsyncCall的execute()方法處理的,具體程式碼如下:
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(); // 真正向伺服器傳送網路請求的程式碼,後面會具體說明。
if (retryAndFollowUpInterceptor.isCanceled()) { // 呼叫了call.cancel()方法取消網路請求
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled")); // 通過CallBack進行失敗的回撥
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response); // 通過CallBack進行成功的回撥
}
} catch (IOException e) {
...
} finally {
client.dispatcher().finished(this); // 非同步請求結束後,從執行佇列中移除請求
}
}
複製程式碼
非同步請求的移除操作
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
...
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); //對執行佇列中移除請求
if (promoteCalls) promoteCalls(); // 將非同步請求準備佇列中的將請求放入執行佇列中,做補位操作
...
}
...
}
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return;
if (readyAsyncCalls.isEmpty()) return;
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;
}
}
複製程式碼
5. 鏈式呼叫傳送網路請求
之前只是知道通過getResponseWithInterceptorChain() 真正向伺服器傳送網路請求,但是沒有做具體的分析,因為這塊相對來說比較獨立,可以單獨提出來講,廢話不多說,先具體看看該方法內部實現。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
複製程式碼
RealInterceptorChain中的proceed()方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
...
// 1. 獲取獲截器鏈中的第一個攔截器
// 2. 通過index + 1,去掉攔截器鏈中的第一個攔截器獲得新的攔截器鏈
// 3. 呼叫原攔截器鏈中第一個攔截器的intercept()方法,並傳入新的攔截器鏈
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}
複製程式碼
這裡面的具體程式碼實現簡單粗暴,無非是按順序新增不同的攔截器,用於分級處理Request和Response,最後建立了一個RealInterceptorChain物件,用於順序執行每個攔截器中的intercept()方法。
接著看其中一個攔截器RetryAndFollowUpInterceptor中intercept()方法
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
... // 加工處理網路請求體
response = realChain.proceed(request, streamAllocation, null, null); // 將請求傳遞給下一個攔截器
... // 加工處理響應體
return response;
}
複製程式碼
可以看到每個攔截器做的事無非是加工請求物件,將請求交由下一個攔截器處理,當然最後一個攔截器就不需要下交請求,而是直接向伺服器傳送網路請求,最後對響應加工處理並返回。