1.寫在前面
工作以來,大部分時間都在忙於各種業務。業務的實現固然重要,不過我仍然堅信注重基礎知識的積累才是正道。想想用了這麼久的OkHttp竟然沒有仔細品讀他的原始碼,深感慚愧。是時候研究一下了。
2.關於OkHttp
2.1 簡介
引用專案官方的一句話:An HTTP & HTTP/2 client for Android and Java applications
。OkHttp是一個處理網路請求的高效能框架,由Square公司貢獻(該公司還貢獻了Picasso)
專案首頁地址
2.2 優勢
- 共享Socket,減少對伺服器的請求次數
- 通過連線池,減少了請求延遲;
- 支援GZIP來減少資料流量;
- 快取響應資料來減少完全重複的網路請求;
- 使用 OkHttp 無需重寫您程式中的網路程式碼。OkHttp實現了幾乎和java.net.HttpURLConnection一樣的API;
- OkHttp會從很多常用的連線問題中自動恢復。如果您的伺服器配置了多個IP地址,當第一個IP連線失敗的時候,會自動嘗試下一個IP。
3.開始啃原始碼
注:本文程式碼梳理基於 v3.10.0 版本,建議從github上clone一份程式碼然後跟著本文一起梳理一遍。
3.1 先描述下大體的分析步驟
本文會以請求流程為主線梳理OKHttp的原始碼,之所以不深入程式碼細節是因為過分深鑽程式碼實現本身沒有太多的指導意義,並且容易讓初學者陷入只見樹木不見森林的尷尬場面。由於OkHttp發起請求的時候從是否同步的角度來講可以分為同步或非同步。所有接下來的原始碼梳理也從這兩個方面來分析。先上一張流程圖:
上圖來自Piasy的部落格。這張圖對OkHttp的流程的詳細程度讓我實在找不出重畫的理由,所以照搬過來啦(其實是我懶)。3.2 同步請求
3.2.1 如何發起同步請求?
完整程式碼如下:
OkHttpClient client = new OkHttpClient
.Builder().build();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
複製程式碼
3.2.2 同步請求涉及到的類
按照3.2.1小節的demo,我們先來分析用到的這幾個類分別扮演了什麼樣的角色,然後按照請求的流程將所有知識串聯起來。
3.2.2.1 Request類
引用一下官方的類描述: An HTTP request. Instances of this class are immutable if their is null or itself immutable.
很明顯,這個類代表一個HTTP請求。來看下他的原始碼:
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
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;
}
...
public static class Builder {
HttpUrl url;
String method;
Headers.Builder headers;
RequestBody body;
Object tag;
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
Builder(Request request) {
this.url = request.url;
this.method = request.method;
this.body = request.body;
this.tag = request.tag;
this.headers = request.headers.newBuilder();
}
public Builder url(HttpUrl url) {
if (url == null) throw new NullPointerException("url == null");
this.url = url;
return this;
}
...
複製程式碼
上述程式碼經過篩選,我們們只看關鍵的部分。由程式碼可見,Request
的主要作用是用來描述一次HTTP請求,這個類中包含請求的地址、請求方法、請求體等型別資料,沒什麼難度。強調一點:構建Request這個類的使用需要使用內部類Builder,很明顯,典型的建造器模式。
3.2.2.2 OkHttpClient類
顧名思義,這個類是OKHttp的一個“客戶端角色”,按照官方介紹,他其實是一個“Call”的工廠。什麼是“Call”呢?其實是一次請求的過程,這裡先略過,後面再介紹。一起來看下OkHttpClient的原始碼。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
...
}
...
public static final class Builder {
Dispatcher dispatcher;
@Nullable Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable Cache cache;
@Nullable InternalCache internalCache;
SocketFactory socketFactory;
@Nullable SSLSocketFactory sslSocketFactory;
@Nullable CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
public Builder() {
dispatcher = new Dispatcher();
...
}
}
複製程式碼
上述程式碼經過精簡,同樣的,我們們只挑重點的看,OkHttpClient同樣提供了建造器模式來建立一個例項,主要對攔截器、讀寫超時等屬性進行配置。這裡要注意,OkHttpClient的Builder已經為我們提供了預設的配置,我們只需要按需重寫自定義的屬性就好。
3.2.2.3 Call類
直白來講Call代表一次請求的過程,按照官方描述,這個類代表一個已經準備好將要被執行的請求。並且同一個Call例項不可以被執行兩次。這裡注意一點,Call可以通過原型模式複製一個與自己相同內容的物件。
/**
* A call is a request that has been prepared for execution. A call can be canceled. As this object
* represents a single request/response pair (stream), it cannot be executed twice.
*/
public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}
複製程式碼
3.2.3 同步請求流程
按照3.2.1小節的demo,一起來梳理一下同步請求的流程,順便把3.2.2小節中提到的知識點串聯一下。
發起請求的時候,典型應用程式碼為:Response response = client.newCall(request).execute();
所以一起來看下OkHttpClient
的newCall
方法做了什麼事情:
OkHttpClient的newCall方法內容:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製程式碼
上述程式碼可以看出newCall
方法直接呼叫了RealCall.newRealCall
方法。這個RealCall
類實現了Call
這個介面。來看下RealCall.newRealCall
的內容。
RealCall.newRealCall方法體:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
複製程式碼
上述程式碼很清晰,newRealCall
方法直接new了一個RealCall
物件並且返回了。
分析到這裡,我們們接著最開始的典型應用程式碼分析Response response = client.newCall(request).execute();
綜合上述的程式碼分析,這段程式碼可以替換成虛擬碼Response response = RealCall.execute();
。所以下面一起來看下RealCall
中的execute
方法吧。
RealCall.execute方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
複製程式碼
重點分析上述程式碼兩個地方:
client.dispatcher().executed(this);
,這句程式碼中用client中的Dispatcher對當前Call進行了一次標記。這個方法內部用一個名叫runningSyncCalls
的Deque對當前的Call進行了儲存。由於Dispatcher在同步呼叫的過程中並不是主角,所以這裡先佔個坑後面分析非同步呼叫的時候在分析一下。Response result = getResponseWithInterceptorChain();
我個人認為,這句呼叫才是同步呼叫乃至整個OkHttp的靈魂所在。下面重點分析一下。
來看下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);
}
複製程式碼
想必大家即使沒有閱讀過OkHttp的原始碼,一定也有所耳聞,OkHttp的精髓在於攔截器的使用。沒錯getResponseWithInterceptorChain
這個方法就是負責將責任鏈上的每一個攔截器進行串聯的方法。該方法的邏輯也比較通俗易懂,將開發者定義的攔截器與OkHttp內建的攔截器封裝成一個List然後塞給了RealInterceptorChain,最後執行RealInterceptorChain的proceed方法。那這個RealInterceptorChain是什麼呢?通俗一點來講,這個類是一個具體的攔截器鏈,沒錯這是典型的責任鏈模式。
RealInterceptorChain類程式碼:
/**
* A concrete interceptor chain that carries the entire interceptor chain: all application
* interceptors, the OkHttp core, all network interceptors, and finally the network caller.
*/
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
...
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
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);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
}
複製程式碼
一開始分析到這裡的時候,我有點蒙圈。弄了好久沒弄懂這個責任鏈是怎麼執行的。最後還是搬出了斷點打法,終於看到了光明。再複雜的責任鏈(由於本人比較水,所以覺得OkHttp的責任鏈是比較複雜的,歡迎拍磚)把握好兩點就夠了:
- 怎樣獲取的責任鏈上下一環的例項
- 責任鏈是怎樣序列執行的
帶著上述的問題進行斷點大法,終於找到了以下核心程式碼:
// Call the next interceptor in the chain.
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);
複製程式碼
這段程式碼使用遞迴呼叫,如果責任鏈上還有下一環的攔截器例項,則取出該例項,並且呼叫該例項的intercept方法。前面分析getResponseWithInterceptorChain
這個方法的時候對攔截器進行過介紹。OkHttp中所有攔截器都需要實現Interceptor
這個介面。
這個介面唯一的方法便是Response intercept(Chain chain) throws IOException;
。
由於是遞迴呼叫,那什麼時候結束呢?帶著這個問題,我們再次看下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);
}
複製程式碼
從方法體可以看到,OkHttp在新增了開發者自定義的Interceptor後,先後新增了RetryAndFollowUpInterceptor...CallServerInterceptor
等攔截器。這裡先給出一個結論,攔截器中的責任鏈在CallServerInterceptor這個類中結束。這個結論是怎麼得到的呢?來看下OkHttp第一個原生攔截器RetryAndFollowUpInterceptor
的程式碼:前面提到過OKHttp所有的攔截器都實現了Interceptor
介面。所以直接來看下Interceptor
中核心的方法intercept
吧。
RetryAndFollowUpInterceptor攔截器中的intercept方法:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
}
...
}
}
複製程式碼
看到沒response = realChain.proceed(request, streamAllocation, null, null);
通過這句程式碼,開始呼叫RealInterceptorChain
的proceed
方法,而RealInterceptorChain
方法的proceed
具體邏輯是取出責任鏈上下一個攔截器並執行該攔截器的intercept
方法,這樣形成了一個具體的迴圈邏輯,責任鏈開始完美的執行。繼續說下責任鏈的結束,由於CallServerInterceptor
這個攔截器是責任鏈的最後一環,我們看下他的程式碼。
CallServerInterceptor的intercept方法
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest();
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 省略部分檢查程式碼
return response;
複製程式碼
上述程式碼可以看到,在CallServerInterceptor
攔截器中,會呼叫HttpCodec
這個類來完成最終的請求。這裡簡單說下HttpCodec這個介面,在OkHttp中有兩個實現類Http1Codec與Http2Codec
,這兩個實現類對應著Http1.x與Http2.x的請求。他們內部的具體請求都依賴於Okio,而Okio內部也是基於Socket的封裝。回到原來的話題,CallServerInterceptor的intercept方法執行後並沒有再次呼叫RealInterceptorChain的process
方法,而是直接將Response
這個響應體返回了。所以攔截器的責任鏈到這裡終止尋找下一環,並且將Response
逆向返回。這個時候程式碼執行完了攔截器的責任鏈,最終再次返回到RealCall的execute方法中
。
用張圖總結一下攔截器鏈的呼叫過程:
至此OkHttp的同步流程就介紹完了。
3.3 非同步請求
3.3.1 如何發起非同步請求
非同步請求的典型程式碼
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//TODO 失敗的回撥函式
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//TODO 成功的回撥函式
}
});
複製程式碼
3.3.2 非同步請求涉及到的類
3.3.2.1 Dispatcher類
同步請求的時候我們們短暫的遇到過這個類,但是通過請求中這個類不是主角,他僅僅將同步請求的Call儲存到了一個名叫runningSyncCalls
的ArrayDeque
中。但是在非同步請求中,Dispatcher類還是承擔了很重要的角色。
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService;
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
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);
}
}
public synchronized void cancelAll() {
for (AsyncCall call : readyAsyncCalls) {
call.get().cancel();
}
for (AsyncCall call : runningAsyncCalls) {
call.get().cancel();
}
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
...
}
複製程式碼
從上述Dispatcher的原始碼來看,該類用final
關鍵字修飾,表示該類不可以被繼承。他的內部有個public synchronized ExecutorService executorService()
方法。很明顯這個方法設定了一下ThreadPoolExecutor
執行緒池。非同步的任務都將放到這個執行緒池中執行。這與同步呼叫直接通過攔截器鏈的方式還是有明顯差別的。
3.3.2.2 AsyncCall類
這個類繼承自NamedRunnable
類,而NamedRunnable
類其實是對Runnable
的簡單封裝,主要是為當前執行的執行緒設定一下名稱。這裡就不展開對NamedRunnable
的介紹了。下面看下AsyncCall
的原始碼。
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;
}
@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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
複製程式碼
當執行緒池呼叫AsyncCall
的時候,execute
方法會被執行,看下execute
的內容,天啦嚕,還是通過getResponseWithInterceptorChain
方法發起的請求,這跟同步呼叫是一樣一樣滴。
3.3.3 非同步請求流程梳理
還是從3.3.1小節的demo開始,一起來梳理一下非同步流程。
開始呼叫的時候跟同步流程一樣,都是經過client.newCall(request)
的呼叫,很明顯這一步已經走到了RealCall
內部。再來看下RealCall
的enqueue
方法。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製程式碼
enqueue
方法會檢查當前這個Call
是否被請求過,如果已經被請求過,則會丟擲異常。最後,該方法會將AsyncCall
物件直接交給Dispatcher
來執行,上面3.3.2對Dispatcher
的介紹已經分析過了,Dispatcher
的enqueue
方法會直接用executorService
方法初始化的執行緒池執AsyncCall
這個經過封裝的 Runnable
。在AsyncCall
內部會完成開發者自定義的Callback
回撥。至此非同步流程梳理完畢。
寫在最後
本篇是我第一次寫原始碼分析類的部落格,寫的可能比較亂。歡迎拍磚。
前段時間參加了掘金的JTalk線下沙龍,滴滴的子成大佬提出的“跳出舒適期”觀念讓我感受頗多。畢竟多接觸些新的技術面對自己的發展總是有好處的。
戒驕、戒躁、戒安逸。共勉~
About Me
contact way | value |
---|---|
weixinjie1993@gmail.com | |
W2006292 | |
github | https://github.com/weixinjie |
blog | https://juejin.im/user/57673c83207703006bb92bf6 |