前言
Okhhtp是square公司出品的一款非常優秀的網絡框架。可以說,目前市面上大部分Android開發人員在網絡請求事,都在使用這款優秀的框架。下面,我就結合源碼來分析下這款網絡框架內部的執行原理,做到知其然也之氣所以然的目的。
使用
OkHttp的使用非常的簡單,我下面寫一個大概的使用方法提供給大家查看。
//創建okhttpclient對象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//創建請求,裏面可以設置get、post請求和請求地址和請求頭等一系列操作
Request request = new Request.Builder()
.url("www.baidu.com")
.build();
//獲取到一個call,這其實是一個線程請求任務,由其發起真正的網絡請求
Call call = okHttpClient.newCall(request);
//同步請求
try {
call.execute();
} catch (IOException e) {
e.printStackTrace();
}
//異步請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//TODO 異步請求失敗的操作
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//TODO 同步請求成功的操作
}
});
可以看到,OkHttp的使用並不複雜,按照上面的一套流程下來就可以執行一次請求。由於這篇文章重點在於分析源碼,所以對於OkHttp的使用並不做過多的描述。
下面開始進入分析源碼狀態。
Dispatcher分發器
dispatcher分發器可以說是OkHttp的重要部分之一,用一句話來說明他的用途,就是他是用來管理同步和異步請求的。
我們點擊OkhttpClient的Builder()進去看下源碼
public Builder() {
1 dispatcher = new Dispatcher();
2 protocols = DEFAULT_PROTOCOLS;
3 connectionSpecs = DEFAULT_CONNECTION_SPECS;
4 eventListenerFactory = EventListener.factory(EventListener.NONE);
5 proxySelector = ProxySelector.getDefault();
6 cookieJar = CookieJar.NO_COOKIES;
7 socketFactory = SocketFactory.getDefault();
8 hostnameVerifier = OkHostnameVerifier.INSTANCE;
9 certificatePinner = CertificatePinner.DEFAULT;
10 proxyAuthenticator = Authenticator.NONE;
11 authenticator = Authenticator.NONE;
12 connectionPool = new ConnectionPool();
13 dns = Dns.SYSTEM;
14 followSslRedirects = true;
15 followRedirects = true;
16 retryOnConnectionFailure = true;
17 connectTimeout = 10_000;
18 readTimeout = 10_000;
19 writeTimeout = 10_000;
20 pingInterval = 0;
}
可以看到在標記爲1的地方有一個dispatcher = new Dispatcher(),原來分發器Dispatcher是在OkHttpClient的Builder中創建的。
然後點擊Dispatcher進去看下Dispatcher類的內部代碼
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private Runnable idleCallback;
/** 線程池 */
private ExecutorService executorService;
/** 準備執行任務的異步隊列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** 正在執行任務的異步隊列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在執行任務的同步隊列*/
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
}
刪減了一些代碼,我們只看他的內部的聲明的一些變量。可以看到,在Dispatcher的內部其實聲明瞭四個東西,三個任務隊列,分爲是:
準備執行任務的異步隊列 readyAsyncCalls,
正在執行任務的異步隊列 runningAsyncCalls,
正在執行任務的同步隊列 runningSyncCalls;
和一個線程池 executorService;
那麼Dispatcher是如何在執行同步和異步請求的吶?
同步請求
我們點擊dispatcher.execute()的execute()方法進去查看源碼
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
1.這段代碼首先用一個同步鎖synchronize來修飾代碼塊,代表這段代碼只能是串行狀態並不能支持並行。
2.其次再進行一次if判斷,判斷任務是否在執行,如果正在執行的任務再次進行執行,那麼便會拋出異常"Already Executed",可見OkHttp不能重複進行任務的執行。
3.緊接着調用了dispatcher.execute(this)來執行同步任務
4.接着調用一個連接器鏈getResponseWithInterceptorChain(),將遍歷所有的攔截器,最後將結果result返回。(關於攔截器後面再講,這裏不做論述)
5.最後再次調用dispatcher.finished(this)來結束所有的請求。
那麼這個流程我們重點分析第3步和第5步
我們點擊第3步的execute(this)方法裏面查看裏面做了什麼
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
可以看到,這個方法仍然用synchronize來進行了修飾使得多線程也只能串行。之後將RealCall這個任務添加放入runningSyncCalls這個運行時同步任務隊列中。
可以看出,execute()主要作用就是將任務添加到任務隊列當中去, 那麼添加進去之後在哪裏執行吶?這就要到第5步的dispatcher.finish(this)方法中去看了。
點擊finish(this)進入裏面查看代碼
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
1 int runningCallsCount;
2 Runnable idleCallback;
3 synchronized (this) {
4 if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
5 if (promoteCalls) promoteCalls();
6 runningCallsCount = runningCallsCount();
7 idleCallback = this.idleCallback;
8 }
9 if (runningCallsCount == 0 && idleCallback != null) {
10 idleCallback.run();
}
}
具體我們需要看的是finished(Deque calls, T call, boolean promoteCalls)
前面幾行都只是做一些判斷,我們重點看第5行的代碼,裏面有一個判斷if(promoteCalls) promoteCalls();
如果promoteCalls爲true,那麼就去執行promoteCalls()方法。那麼重點再promoteCalls()方法裏面到底做了什麼。我們再點擊進去看看
private void promoteCalls() {
1 if (runningAsyncCalls.size() >= maxRequests) return;
2 if (readyAsyncCalls.isEmpty()) return;
3 for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
4 AsyncCall call = i.next();
5 if (runningCallsForHost(call) < maxRequestsPerHost) {
6 i.remove();
7 runningAsyncCalls.add(call);
8 executorService().execute(call);
9 }
10 if (runningAsyncCalls.size() >= maxRequests) return;
}
}
可以看到前面1,2兩步都是在判斷,判斷運行時異步隊列的最大值是否超過64,超過則return結束掉。如果異步準備隊列是空(isEmpty()),那麼也return結束掉。
第3步開始進行for循環,從等待隊列中取出任務,添加到運行隊列runningAsyncCalls中去(runningAsyncCalls.add(call);),然後再把call任務交給線程池去執行(executorService().execute(call)),從而完成一次請求。
那麼來總結下同步請求:
通過dispatcher分發器,將一次網絡請求call添加到運行時同步隊列runningSynCall中去,然後再通過一個promoteCall()方法,把任務隊列中的任務交給線程池去執行,從而完成一次請求。
異步請求
我們再來看下異步請求
//異步請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//TODO 異步請求失敗的操作
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//TODO 同步請求成功的操作
}
});
我們點擊enqueue()方法進去看下代碼
void enqueue(Callback responseCallback);
看到只有一行代碼,很簡潔
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
我們再次跳到 client.dispatcher().enqueue(new AsyncCall(responseCallback));的enqueue()方法中去看下
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到,在這裏面我們進行了一次判斷,如果正在運行的異步隊列裏面的任務小於maxRequests並且runningCallsForHost(call) < maxRequestsPerHost則往runningAsyncCalls 中添加任務,然後交給線程池去執行,不然則往異步準備隊列中添加任務,作爲等待。
那麼maxRequests和maxRequestsPerHost是啥?我們在分析Dispatcher的時候看到在這個類初始化了幾個變量其中就有這兩個變量
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
}
看到maxRequests爲64,代表最大異步請求數量爲64,最大的host數爲5個,如果運行任務隊列中超過了這兩個數量,則不能把任務交給線程池去執行,而是直接放到readyAsyncCalls隊列中去等待。
@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);
}
}
}
可以看到這裏仍然通過一次連接器鏈將結果返回;
然後有幾次判斷,判斷是否是需要重定向重連等,如果retryAndFollowUpInterceptor.isCanceled()代表連接取消失敗,則回調responseCallback.onFailure(RealCall.this, new IOException("Canceled"));如果成功則回調responseCallback.onResponse(RealCall.this, response);
最後又回到了finish(this)方法,這個之前說過了,就是把運行隊列中的內容拿出來交給線程池去執行。
總結一下異步請求流程
異步請求
1.一開始會去執行dispatcher.enqueue(),裏面會有兩個判斷,判斷運行任務列隊runningAsyncCalls中的任務總數是否超過64個以及網絡請求的host是否超過5個,如果沒超過這兩個數,那麼往運行任務隊列中添加任務,然後把任務交給線程池去執行。如果超過了,則直接放到準備等待任務隊列中readyAsyncCalls中去等待。
2.之後執行一次攔截器鏈getResponseIntecepterChain()把結果返回返回
3.在進行一次判斷,這個任務是否需要重新連接,是否被取消retryAndFollowUpInterceptor.isCanceled(),如果取消了則證明這個任務執行失敗了,則在線程中回調onFailure()方法,如果成功則回調onResponse()方法。
4.最後調用dispatcher.finish(this)方法,裏面有一個promoteCalls()方法,用來調整隊列中的任務。通過把剛纔準備隊列中(readyAsyncCalls)中的任務再次添加到異步運行任務隊列中去,然後交給線程池去執行。
這就是一次異步請求的流程。
OkHttp的攔截器
如果說OkHttp最精華和亮點的功能是啥的話,我會毫不猶豫的說是攔截器功能。攔截器可以說是整個OkHttp最精華不可不說的功能。
什麼叫做攔截器?
我的個人理解是,在你進行發起一次真正意義上的網絡請求時,需要進行操作或者闖過的一些關卡,只有通過了這個闖關之後,才真正的能夠發起一次真正意義上的網絡請求。
我們來看一次完整意義上的攔截器的請求流程是如何實現的。
@Override public Response execute() throws IOException {
synchronized (this) {
1 if (executed) throw new IllegalStateException("Already Executed");
2 executed = true;
}
captureCallStackTrace();
try {
3 client.dispatcher().executed(this);
4 Response result = getResponseWithInterceptorChain();
5 if (result == null) throw new IOException("Canceled");
6 return result;
} finally {
7 client.dispatcher().finished(this);
}
}
我們把代碼放到RealCall這個類中,從這個類中找到execute()這個方法,當然這裏面有兩個execute()方法,一個是異步請求的,一個是同步請求的。我們以同步請求爲例(其實異步在攔截器這塊是一樣的)。
看到在被標記起來的第三行,分發器dispatcher先執行executed(this)同步方法,然後在第四行會有一個叫做getResponseWithInterceptorChain() 的方法,執行了這個方法之後獲取到結果,然後將result結果返回即完成。而getResponseWithInterceptorChain() 就是執行攔截器鏈的主要核心方法。我們下面重點來看這個方法具體做了哪些操作。
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);
return chain.proceed(originalRequest);
}
點擊進入這個方法,我們一步一步來看。
首先是創建了一個集合interceptors,這個List集合的泛型就是Interceptor,即攔截器類型。也就是說,這個集合是專門用來存儲攔截器的。
之後便是一大堆的add(),即往這個集合中不斷的按照順序添加攔截器。
我們看到,
添加的有自定義的普通攔截器client.interceptors();
重試或重定向攔截器retryAndFollowUpInterceptor;
設置一些請求頭、壓縮數據和設置一些cookie等操作的BridgeInterceptor攔截器;
設置緩存或者使用緩存的緩存攔截器CacheInterceptor;
複用聯接操作裏面有一個連接池的鏈接攔截器ConnectInterceptor
在發起一次真正網路請求之前可以做一些自定義操作的另外一個自定義攔截器networkInterceptors
真正進行一次網絡請求的請求攔截器CallServerInterceptor
在添加完所有攔截器到集合之後,用將這個集合放入到一個類RealInterceptorChain中,最後執行這個RealInterceptorChain對象的一個process()方法,並且把結果返回。
這便是一次完整的攔截器過程。
那麼看完上面這個流程很多人是否會雲裏霧裏的摸着腦袋說,前面的創建集合我看懂了,把攔截器分批放入到集合中我也看懂了,但是最後把集合放入RealInterceptorChain這個類的目的是啥和執行process()是爲了幹嘛我就沒看懂了。也不曉得,爲啥把攔截器放入到集合中就能執行了,他是怎麼一層一層執行攔截器的我也不知道。
說實話,我一開始看這段代碼也是隻看得懂前面定義集合和把攔截器放入集合這操作,後面的操作真的就是小朋友你是不是有好多問號的狀態。
那我們來一個一個回答上面的幾個問題。
第一個問題,爲啥要把裝了攔截器的集合放入到一個叫RealInterceptorChain的類的構造方法中去?
我們點擊進去到這個類裏面,看看源碼。
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 int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
}
由於這個類的代碼比較多,我這裏先貼出部分代碼,我們一步一步拆解來看。
可以看到,這個類一開始定義了好多個變量,比如 List、StreamAllocation、HttpCodec 、RealConnection 等等。而你之所以定義這個變量的目的,肯定是因爲之後需要用到這個量嘛,那具體在哪裏用到吶,我們後面再講。那既然要用到這些變量,那我們肯定要給他賦初始值,不然你咋操作呢?那這些初始值哪裏來吶?那不就是我們通過構造方法傳進去麼。就像我們常常定義一個JavaBean一樣,也會在構造方法中傳入值來進行變量賦值,所以這裏遠離也是一樣,我們後面在這個類RealInterceptorChain的某個方法中需要操作到List中攔截器,所以先在構造方法中傳進來。
這也就解釋了第一個問題,爲什麼要把添加了攔截器的集合List傳入到RealInterceptorChain中,因爲這個類裏面有需要用到操作這些攔截器的方法,所以需要先傳值進來。
第二個問題,定義了那麼多攔截器,是如何一層一層進行調用的?
回答這個問題,我們需要看後面的一個方法proceed(),正是在這個方法裏面執行操作的。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
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");
}
chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
這段代碼,前面都是三個if判斷,並且拋出一些相應的異常信息,這是爲了做一些容錯處理,沒啥看的。我們看主要部分。
1 RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
2 Interceptor interceptor = interceptors.get(index);
3 Response response = interceptor.intercept(next);
這是proceed()方法中最主要的三行代碼。
第一行代碼看到,我們這邊又實例化了一次RealInterceptorChain類,而且傳入的參數幾乎都是一模一樣的,只是在index的時候傳入的是(index + 1),其他都沒改變
第二行則是從攔截器的List集合中通過get()方法拿出index位置的攔截器
第三行則是第二步拿出的攔截器去執行intercept()方法,也就是執行這個攔截器。
不知道大家看着三行代碼有沒有看暈,反正我最早看是暈了,又一次不曉得他到底在幹嘛,爲啥又要實例化一次RealInterceptorChain,傳入(index + 1)又是什麼鬼。
造成這麼暈的原因是因爲我們孤立的看了這三行代碼,如果僅僅只是看着三行代碼而不聯繫之前的RealInterceptorChain類的構造方法的解釋來看確實是看不懂的。
首先,我們之前分析過之所以要把攔截器的List集合傳入到RealInterceptorChain類的構造方法的目的是因爲在RealInterceptorChain類裏面有某個方法使用到了攔截器集合,那麼是哪個方法吶?就是現在的這個proceed()方法。
而我們現在不斷的實例化這個RealInterceptorChain類的目的就是爲了不斷的更新這個構造方法中的變量,那麼這裏面哪個變量是不斷被更新的吶?就是這個index。
我們不斷的傳入(index + 1)的目的就是不斷的讓這個index自增,即index = index + 1。
那麼是無限自增下去麼?顯然不是,他是有條件的自增的,條件就是proceed()方法中的第一個if判斷
if (index >= interceptors.size()) throw new AssertionError();
當index自增到超過了攔截器集合裏面最大數量,即size()的時候他就不能自增而是拋出異常了。
這裏就解釋了爲啥要在prceed()方法中不斷的再次new RealInterceptorChain()這個類,原因就是爲了不斷的更新index這個變量,讓他不斷的自增 +1
再來看第二行代碼
Interceptor interceptor = interceptors.get(index);
這行代碼因爲任何人都能看的懂吧,就是從集合interceptors中獲取下角標爲index的值,也就是說獲取集合中第index位置的攔截器。
那麼結合第一行解釋的,通過第一行不斷的更新了index的值,使得其不斷的自增+1,目的就是爲了能夠給第二行interceptors.get(index)去使用這個值。讓集合能夠不斷的取出下一個攔截器來。
比如,第一行代碼更新的index,讓其自增了+1,即index = index + 1,那麼到了第二行使用的不再是index這個值,而是變成(index + 1)這個值,再配合一開始的if判斷給index的自增添加一個邊際,從而達到遍歷取出List集合中攔截器的目的。
最後是第三行代碼,這行很簡單,從集合中拿出了攔截器直接去執行就OK了。
總結一下proceed()方法
這個方法目的其實說白了就是不斷的遍歷攔截器集合,從集合中取出攔截器來,然後通過intercept()方法,不斷執行下一個攔截器,從而達到鏈式調用的目的。
到此,整體攔截器的調用步驟完成。
後面將分開寫一些主要攔截器和自定義攔截器的內容和源碼分析。
RetryAndFollowUpInterceptor攔截器
這個叫做請求重定向攔截器,主要的作用是在請求失敗之後,自動的進行重新連接的操作。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
1 streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
2 int followUpCount = 0;
3 Response priorResponse = null;
4 while (true) {
5 if (canceled) {
6 streamAllocation.release();
7 throw new IOException("Canceled");
8 }
9 Response response = null;
10 boolean releaseConnection = true;
11 try {
12 response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
13 releaseConnection = false;
14 } catch (RouteException e) {
15 if (!recover(e.getLastConnectException(), false, request)) {
16 throw e.getLastConnectException();
17 }
18 releaseConnection = false;
19 continue;
20 } catch (IOException e) {
21 boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
22 if (!recover(e, requestSendStarted, request)) throw e;
23 releaseConnection = false;
24 continue;
25 } finally {
26 if (releaseConnection) {
27 streamAllocation.streamFailed(null);
28 streamAllocation.release();
29 }
30 }
31 if (priorResponse != null) {
32 response = response.newBuilder()
33 .priorResponse(priorResponse.newBuilder()
34 .body(null)
35 .build())
36 .build();
37 }
38 Request followUp = followUpRequest(response);
39 if (followUp == null) {
40 if (!forWebSocket) {
41 streamAllocation.release();
42 }
43 return response;
44 }
45 closeQuietly(response.body());
46 if (++followUpCount > MAX_FOLLOW_UPS) {
47 streamAllocation.release();
48 throw new ProtocolException("Too many follow-up requests: " + followUpCount);
49 }
50 if (followUp.body() instanceof UnrepeatableRequestBody) {
51 streamAllocation.release();
52 throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
53 }
54 if (!sameConnection(response, followUp.url())) {
55 streamAllocation.release();
56 streamAllocation = new StreamAllocation(
57 client.connectionPool(), createAddress(followUp.url()), callStackTrace);
58 } else if (streamAllocation.codec() != null) {
59 throw new IllegalStateException("Closing the body of " + response
60 + " didn't close its backing stream. Bad interceptor?");
61 }
62 request = followUp;
63 priorResponse = response;
}
}
這些代碼本身不做具體分析,沒有啥太難懂的地方,但是這裏我需要提醒大家注意的一點就是重新連接的次數並非是無限次的,他其實是有一個最大值的。在第46行,我們可以看到
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
這裏有一個if判斷,當followUpCount > MAX_FOLLOW_UPS的時候則會停止從重連,拋出異常。那麼這個MAX_FOLLOW_UPS是多少吶?
private static final int MAX_FOLLOW_UPS = 20;
可以看到,這個最大重連次數是20次,也就是說,RetryAndFollowUpInterceptor攔截器的重連次數並非無限,而是最多20次,超過了這個次數則不再進行重新連接了。
BridgeInterceptor攔截器
這個攔截器可以稱之爲橋接攔截器。代碼如下:
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
這個攔截器的主要功能是在設置一些請求時候的頭部信息功能,例如設置"Content-Type"、"Content-Length"、"Host"、"Cookie"等。
結合CacheInterceptor緩存攔截器來談一談OkHttp的內部緩存原理
OkHttp的緩存過程是一個很值得細講的過程。那麼我們這裏配合着緩存攔截器來談一談OkHttp的緩存內部原理。
如何使用緩存?
要想使用OkHttp的緩存,其實可以在初始化OkHttpClient的時候設置好。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(new Cache(new File("緩存位置"),1024))
.build();
可以看到,在這裏有一個cache()的構造方法,裏面有一個參數,實例化一個Cache的類,這個類就是用於緩存的類。
我們點擊進入這個類裏面查看構造方法
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
從構造方法看,這個構造方法有兩個參數,一個是用來記錄緩存的位置的File,一個是用來記錄緩存大小的maxSize。通過這兩個參數可以將緩存存放在相應的位置並設置好緩存的大小。
之後我們進入到Cache這個類中看到,這個類中最主要的方法有兩個,一個是獲取緩存的get()方法,一個是存儲緩存的put()方法。我們分爲來看看這兩個方法。
緩存存放方法put()
先來看存儲方法put()
1 CacheRequest put(Response response) {
2 String requestMethod = response.request().method();
3 if (HttpMethod.invalidatesCache(response.request().method())) {
4 try {
5 remove(response.request());
6 } catch (IOException ignored) {
7 }
8 return null;
9 }
10 if (!requestMethod.equals("GET")) {
11 return null;
12 }
13 if (HttpHeaders.hasVaryAll(response)) {
14 return null;
15 }
16 Entry entry = new Entry(response);
17 DiskLruCache.Editor editor = null;
18 try {
19 editor = cache.edit(key(response.request().url()));
20 if (editor == null) {
21 return null;
22 }
23 entry.writeTo(editor);
24 return new CacheRequestImpl(editor);
25 } catch (IOException e) {
26 abortQuietly(editor);
27 return null;
28 }
}
從代碼可以看到,這個存儲方法前面都是一些if判斷,主要功能就一個,去除掉不是GET請求的方式,也就是說OkHttp他的緩存只緩存GET請求,對於POST等其他請求他是不進行緩存的。遇到其他請求方式,直接返回null
我們從16行開始看起,這裏會初始化一個Entry數組,這個數組就是用來寫入一些緩存信息。
之後會通過cache.edit()方法,把我們請求的url作爲key來獲取一個DiskLruCache類。也就是說,OkHttp的緩存是基於DiskLruCache算法來進行存儲的,用請求的url作爲key來查詢結果。
最後是通過 entry.writeTo(editor)這個方法,把緩存結果寫入到entry數組當中去,從而完成一次緩存。
緩存獲取方法get()
我們再來看緩存獲取方法
Response get(Request request) {
1 String key = key(request.url());
2 DiskLruCache.Snapshot snapshot;
3 Entry entry;
4 try {
5 snapshot = cache.get(key);
6 if (snapshot == null) {
7 return null;
8 }
9 } catch (IOException e) {
10 return null;
11 }
12 try {
13 entry = new Entry(snapshot.getSource(ENTRY_METADATA));
14 } catch (IOException e) {
15 Util.closeQuietly(snapshot);
16 return null;
17 }
18 Response response = entry.response(snapshot);
19 if (!entry.matches(request, response)) {
20 Util.closeQuietly(response.body());
21 return null;
22 }
23 return response;
}
首先,先通過請求的url獲取key值,之後在DiskLruCache算法中有一個叫做Snapshot的對象,這個對象叫做緩存快照,作用就是用來記錄某一時刻的緩存。
之後通過key獲取到這一時刻的緩存快照snapshot,此處再做一次判斷,如果爲空則代表此時沒有緩存,返回null,否則通過此時獲取的緩存快照從Entry數組中獲取此刻的緩存結果(前面也提到了DiskLruCache緩存最後是寫入到Entry中的)
最後獲取到了緩存之後並沒有結束,還有一層判斷
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
這裏是判斷我們獲取的緩存是否和我們請求時的緩存是匹配的。因爲要知道,我們最終獲取的緩存是必須是我們曾經請求時候緩存下來的,如果不匹配,則代表這個獲取的緩存並非是我們曾經發起網絡請求時候的緩存,則沒必要返回。而如果是matches匹配上了,則代表是一個緩存。
到此,緩存的存儲和獲取的方法分析完畢。那麼問題又來了,OkHttp是在哪裏調用這個獲取和存儲緩存的兩個方法的吶?那就是接下來要重點分析的緩存攔截器的功能了。
緩存攔截器——CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
1 Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
2 long now = System.currentTimeMillis();
3 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
4 Request networkRequest = strategy.networkRequest;
5 Response cacheResponse = strategy.cacheResponse;
6 if (cache != null) {
7 cache.trackResponse(strategy);
8 }
9 if (cacheCandidate != null && cacheResponse == null) {
10 closeQuietly(cacheCandidate.body());
}
11 if (networkRequest == null && cacheResponse == null) {
12 return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
13if (networkRequest == null) {
14 return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
15 }
16 Response networkResponse = null;
17 try {
18 networkResponse = chain.proceed(networkRequest);
19 } finally {
20 if (networkResponse == null && cacheCandidate != null) {
21 closeQuietly(cacheCandidate.body());
22 }
23 }
24 if (cacheResponse != null) {
25 if (networkResponse.code() == HTTP_NOT_MODIFIED) {
26 Response response = cacheResponse.newBuilder()
27 .headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
28 networkResponse.body().close();
29 cache.trackConditionalCacheHit();
30 cache.update(cacheResponse, response);
31 return response;
32 } else {
33 closeQuietly(cacheResponse.body());
34 }
35 }
36 Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
37 if (cache != null) {
38 if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
39 CacheRequest cacheRequest = cache.put(response);
40 return cacheWritingResponse(cacheRequest, response);
41 }
42 if (HttpMethod.invalidatesCache(networkRequest.method())) {
43 try {
44 cache.remove(networkRequest);
45 } catch (IOException ignored) {
46 // The cache cannot be written.
47 }
48 }
49 }
50 return response;
51 }
這是緩存攔截器主要執行的代碼。可以看到在第三行,有一個叫做緩存策略的類CacheStrategy,而這個類可以說就是整個緩存攔截器的核心,他決定了到底是使用緩存還是使用網絡連接去獲取數據。
我們點擊進去看下這個類內部。
public final class CacheStrategy {
public final Request networkRequest;
public final Response cacheResponse;
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
return new CacheStrategy(null, null);
}
return candidate;
}
}
這是部分代碼。我們可以看到這裏面定義了兩個類,一個是Request,一個是Response。然後通過get()方法中的getCandiate()來獲取到緩存策略類CacheStrategy,之後就根據不同的條件來操作這個類,看看到底是用緩存還是用網絡請求。
我們來看下到底哪些條件下用緩存,哪些條件下又用網絡請求。
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
當判斷網絡請求爲空(也就是沒有網絡)並且緩存也爲空的時候,那麼就直接拋出去一個504的響應碼,代表網關超時
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
當網絡的緩存不爲空,並且請求碼是networkResponse.code() == HTTP_NOT_MODIFIED,也就是304未修改的時候,我們直接從緩存中取出數據來處理。此時用到了緩存。
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
而這裏可以看到,在使用網路請求獲取到數據之後,會調用cache.put(response)將數據直接存入到緩存中,這裏我們之前的代碼分析過內部是如何存儲的,這裏不在多說。
到此,整個緩存攔截器就算全部寫完了。
總結一下緩存攔截器
緩存攔截器內部其實是依靠一個緩存策略的類來控制到底是否使用緩存。如果在既沒有網絡也沒有緩存的情況下,他會直接拋出504的網關超時的警告,之後判斷如果請求未修改,也就是304的時候則直接從緩存中獲取緩存信息返回。如果沒有緩存,則先通過網絡端獲取緩存,之後將緩存存儲,方便下次使用。