okhttp原始碼分析(一)——基本流程(超詳細)
1.okhttp原始碼分析(一)——基本流程(超詳細)
2.okhttp原始碼分析(二)——RetryAndFollowUpInterceptor過濾器
3.okhttp原始碼分析(三)——CacheInterceptor過濾器
4.okhttp原始碼分析(四)——ConnectInterceptor過濾器
5.okhttp原始碼分析(五)——CallServerInterceptor過濾器
前言
最近算是入了原始碼的坑了,什麼東西總想按住ctrl看看原始碼的模樣,這段時間在研究okhttp的原始碼,發現okhttp的原始碼完全不是簡簡單單的幾天就可以啃下來的,那就一步一步來吧。
這篇部落格主要是從okhttp的總體流程分析原始碼的執行過程,對okhttp原始碼有大體上的理解,從全域性上看出okhttp的設計思想。
分析
1.OkHttpClient
既然是流程分析,使用過okhttp的都瞭解,首先需要初始化一個OkHttpClient物件。OkHttp支援兩種構造方式
1.預設方式
public OkHttpClient() {
this(new Builder());
}
可以看到這種方式,不需要配置任何引數,也就是說基本引數都是預設的,呼叫的是下面的建構函式。
OkHttpClient(Builder builder) {...}
2.builder模式,通過Builder配置引數,最後通過builder()方法返回一個OkHttpClient例項。
public OkHttpClient build() {
return new OkHttpClient(this);
}
OkHttpClient基本上就這樣分析完了,裡面的細節基本上就是用於初始化引數和設定引數的方法。所以也必要將大量的程式碼放上來佔內容。。。,這裡另外提一點,從OkHttpClient中可以看出什麼設計模式哪?
1.builder模式
2.外觀模式
2.Request
構建完OkHttpClient後就需要構建一個Request物件,檢視Request的原始碼你會發現,你找不多public的建構函式,唯一的一個建構函式是這樣的。
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;
}
這意味著什麼,當然我們構建一個request需要用builder模式進行構建,那麼就看一下builder的原始碼。
public Builder newBuilder() {
return new Builder(this);
}
//builder===================
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 Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
其實忽略其他的原始碼,既然這篇部落格只是為了從總體流程上分析OkHttp的原始碼,所以我們主要著重流程原始碼上的分析。從上面的原始碼我們會發現,request的構建也是基於builder模式。
3.非同步請求
這裡注意一下,這裡分析區分一下同步請求和非同步請求,但其實實質的執行流程除了非同步外,基本都是一致的。
構建完Request後,我們就需要構建一個Call,一般都是這樣的Call call = mOkHttpClient.newCall(request);
那麼我們就返回OkHttpClient的原始碼看看。
/**
* 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 */);
}
可以看到,這裡實質上呼叫的是RealCall中的newRealCall方法,但是這裡需要注意一點,那就是方法前面的@Override
註解,看到這個註解我們就要意識到,這個方法不是繼承就是實現介面。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {...}
可以看到OkhttpClient實現了Call.Factory介面。
//Call.java
interface Factory {
Call newCall(Request request);
}
從介面原始碼我們也可以看出,這個介面其實並不複雜,僅僅是定義一個newCall用於建立Call的方法,這裡其實用到了工廠模式的思想,將構建的細節交給具體實現,頂層只需要拿到Call物件即可。
回到主流程,我們繼續看RealCall中的newRealCall方法。
final class RealCall implements Call {
...
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;
}
...
}
可以看到RealCall實現了Call介面,newRealCall這是一個靜態方法,new了一個RealCall物件,並建立了一個eventListener物件,從名字也可以看出,這個是用來監聽事件流程,並且從構建方法我們也可以看出,使用了工廠模式
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
//預設建立一個retryAndFollowUpInterceptor過濾器
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
重點來了,可以看到,在RealCall的建構函式中,除了基本的賦值外,預設建立一個retryAndFollowUpInterceptor過濾器,過濾器可以說是OkHttp的巨大亮點,後續的文章我會詳細分析一些過濾器吧(能力有限,儘量全看看)。
現在Call建立完了,一般就到最後一個步驟了,將請求加入排程,一般的程式碼是這樣的。
//請求加入排程
call.enqueue(new Callback()
{
@Override
public void onFailure(Request request, IOException e)
{
}
@Override
public void onResponse(final Response response) throws IOException
{
//String htmlStr = response.body().string();
}
});
可以看到這裡呼叫了call的enqueue方法,既然這裡的call->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));
}
1.首先利用synchronized加入了物件鎖,防止多執行緒同時呼叫,這裡先判斷一下executed是否為true判斷當前call是否被執行了,如果為ture,則丟擲異常,沒有則設定為true。
2.captureCallStackTrace()
private void captureCallStackTrace() {
Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
}
可以看到這裡大體上可以理解為為retryAndFollowUpInterceptor加入了一個用於追蹤堆疊資訊的callStackTrace,後面有時間再詳細分析一下這部分吧,不影響總體流程理解。
3.eventListener.callStart(this);
可以看到前面構建的eventListener起到作用了,這裡先回調callStart方法。
4.client.dispatcher().enqueue(new AsyncCall(responseCallback));
這裡我們就需要先回到OkHttpClient的原始碼中。
public Dispatcher dispatcher() {
return dispatcher;
}
可以看出返回了一個僅僅是返回了一個DisPatcher物件,那麼就追到Dispatcher原始碼中。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
這裡先對Dispatcher的成員變數做個初步的認識。
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...
}
可以看到,這裡用三個佇列ArrayDeque
用於儲存Call物件,分為三種狀態非同步等待,同步running,非同步running。
所以這裡的邏輯就比較清楚了。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
當正在執行的非同步佇列個數小於maxRequest(64)並且請求同一個主機的個數小於maxRequestsPerHost(5)時,則將這個請求加入非同步執行佇列runningAsyncCall,並用執行緒池執行這個call,否則加入非同步等待佇列。這裡可以看一下runningCallsForHost方法。
/** Returns the number of running calls that share a host with {@code call}. */
private int runningCallsForHost(AsyncCall call) {
int result = 0;
for (AsyncCall c : runningAsyncCalls) {
if (c.host().equals(call.host())) result++;
}
return result;
}
其實也是很好理解的,遍歷了runningAsyncCalls,記錄同一個Host的個數。
現在來看一個AsyncCall的原始碼,這塊基本上是核心執行的地方了。
final class AsyncCall extends NamedRunnable {
。。。
}
看一個類,首先看一下這個類的結構,可以看到AsyncCall繼承了NameRunnable類。
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();
}
可以看到NamedRunnable是一個抽象類,首先了Runnable介面,這就很好理解了,接著看run方法,可以看到,這裡將當前執行的執行緒的名字設為我們在構造方法中傳入的名字,接著執行execute方法,finally再設定回來。所以現在我們理所當然的回到AsyCall找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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
終於,找到了Response的身影,那麼就意味著執行網路請求就在getResponseWithInterceptorChain()方法中,後面的程式碼其實基本上就是一些介面回撥,回調當前Call的執行狀態,這裡就不分析了,這裡我們重點看一下getResponseWithInterceptorChain()這個方法,給我的感覺這個方法就是okHttp的精髓。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
//失敗和重定向過濾器
interceptors.add(retryAndFollowUpInterceptor);
//封裝request和response過濾器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//快取相關的過濾器,負責讀取快取直接返回、更新快取
interceptors.add(new CacheInterceptor(client.internalCache()));
//負責和伺服器建立連線
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//配置 OkHttpClient 時設定的 networkInterceptors
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);
}
可以看到,這裡首先new了一個Interceptor的ArrayList,然後分別加入了各種各樣的Interceptor,所以當我們預設建立okHttpClient時,okHttp預設會給我們實現這些過濾器,每個過濾器執行不同的任務,這個思想太屌了有木有,每個過濾器負責自己的任務,各個過濾器間相互不耦合,高內聚,低耦合,對拓展放開巴拉巴拉等一系列設計思想有木有,這裡可以對比一下Volley原始碼中的思想,Volley的處理是將快取,網路請求等一系列操作揉在一起寫,導致使用者對於Volley的修改只能通過修改原始碼方式,而修改就必須要充分閱讀理解volley整個的流程,可能一部分的修改會影響全域性的流程,而這裡,將不同的職責的過濾器分別單獨出來,使用者只需要對關注的某一個功能項進行理解,並可以進行擴充修改,一對比,okHttp在這方面的優勢立馬體現出來了。這裡大概先描述一下幾個過濾器的功能:
retryAndFollowUpInterceptor——失敗和重定向過濾器
BridgeInterceptor——封裝request和response過濾器
CacheInterceptor——快取相關的過濾器,負責讀取快取直接返回、更新快取
ConnectInterceptor——負責和伺服器建立連線,連線池等
networkInterceptors——配置 OkHttpClient 時設定的 networkInterceptors
CallServerInterceptor——負責向伺服器傳送請求資料、從伺服器讀取響應資料(實際網路請求)
新增完過濾器後,就是執行過濾器了,這裡也很重要,一開始看比較難以理解。
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方法,這裡注意一下0這個引數。
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;
}
第一眼看,腦袋可能會有點發麻,稍微處理一下。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
。。。
// 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);
。。。
return response;
}
這樣就很清晰了,這裡index就是我們剛才的0,也就是從0開始,如果index超過了過濾器的個數丟擲異常,後面會再new一個RealInterceptorChain,而且會將引數傳遞,並且index+1了,接著獲取index的interceptor,並呼叫intercept方法,傳入新new的next物件,這裡可能就有點感覺了,這裡用了遞迴的思想來完成遍歷,為了驗證我們的想法,隨便找一個interceptor,看一下intercept方法。
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
。。。暫時沒必要看。。。
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
可以看到這裡我們拿了一個ConnectInterceptor的原始碼,這裡得到chain後,進行相應的處理後,繼續呼叫proceed方法,那麼接著剛才的邏輯,index+1,獲取下一個interceptor,重複操作,所以現在就很清楚了,這裡利用遞迴迴圈,也就是okHttp最經典的責任鏈模式。
4.同步請求
非同步看完,同步其實就很好理解了。
/**
* 同步請求
*/
@Override public Response execute() throws IOException {
//檢查這個call是否執行過
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//回撥
eventListener.callStart(this);
try {
//將請求加入到同步佇列中
client.dispatcher().executed(this);
//建立過濾器責任鏈,得到response
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);
}
}
可以看到基本上流程都一致,除了是同步執行,核心方法走的還是getResponseWithInterceptorChain()方法。
到這裡okHttp的流程基本上分析完了,接下來就是對Inteceptor的分析了,這裡獻上一張偷來的圖便於理解流程,希望能分析完所有的Inteceptor吧!
OkHttp原始碼