做爲一個Android
開發者對OkHttp
這個網絡請求框架必定不會陌生,它早已成爲Android
在網絡請求方面的統治者,不關你是直接使用OkHttp
仍是使用Retrofit
又或者其它對OkHttp
的封裝,說到底都是基於OkHttp
。因此學會使用而且深刻了解OkHttp
的原理就變得頗有必要。java
瞭解原理的前提是要先會運用,根據其使用步驟才能進一步分析學習其運行的原理。那麼OkHttp
最基礎使用步驟以下:android
// 一、建立OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
// 二、建立Request對象
Request request = new Request.Builder().url(url).build();
// 三、經過okHttpClient的newCall方法得到一個Call對象
Call call = okHttpClient.newCall(request);
// 四、請求
// 同步請求
Response response = call.execute();
// 異步請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 子線程
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 子線程
}
});
複製代碼
固然OkHttp
支持各類網絡請求相關配置,其具備的攔截器機制方便使用者能夠對全部請求作統一的配置處理。這裏只是展現最基礎使用方法,沒什麼好說的很是簡單,而且OkHttp
也支持同步和異步請求。git
本篇文章中全部源碼基於OkHttp3.11.0
版本,下面就開始看源碼。github
和以前說的同樣,按照OkHttp
的使用方法流程來讀源碼搞清運行流程原理,使用時咱們首先是構建一個OkHttpClient
對象。來看源碼是怎樣構建的。web
public OkHttpClient() {
this(new Builder());
}
複製代碼
OkHttpClient
的實現是一個建造者模式。構造方法裏建立了一個Builder
對象,來看這個Builder
類。設計模式
public static final class Builder {
Dispatcher dispatcher;
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache;
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
......
public Builder() {
//請求分發器
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
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;
}
......
}
複製代碼
Builder
這裏是OkHttpClient
的內部類,構造函數裏主要是初始化了默認的一些成員屬性對象,能夠看出來有超時時間、cookie
、DNS
等等,主要留意Dispatcher
請求分發器和ConnectionPool
鏈接池這兩個對象比較重要,在以後的流程中有很大的做用。由於建造者模式能夠經過OkHttpClient.Builder.build
方法來得到,因此來看它的build
方法。緩存
public OkHttpClient build() {
return new OkHttpClient(this);
}
複製代碼
build
方法中很是簡單,就是new
了一個OkHttpClient
對象。bash
public final class Request {
private final HttpUrl url;
private final String method;
private final Headers headers;
private final RequestBody body;
private final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
private 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 Builder newBuilder() {
return new Builder(this);
}
......
public static class Builder {
private HttpUrl url;
private String method;
private Headers.Builder headers;
private RequestBody body;
private Object tag;
public Builder() {
// 默認Get請求
this.method = "GET";
this.headers = new Headers.Builder();
}
private 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
源碼能夠看出它也是基於建造者模式,它的Builder
默認構造方法裏就兩行設置默認請求方式是GET
,另外初始化請求頭。一般咱們會使用new Request.Builder().url().build()
方法構建Request
來傳入url
等一些參數。服務器
接下來第三步就是經過OkHttpClient
對象的newCall
方法建立一個Call
對象。來看OkHttpClient
的newCall
方法。cookie
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼
在這個方法中實際上調用的RealCall
這個類的newRealCall
方法,並把request
傳進去。因而再進入RealCall
類查看newRealCall
方法。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
複製代碼
能夠看到newRealCall
方法裏只是建立了一個RealCall
對象和一個eventListener
返回。接下來看RealCall
的構造函數。
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
//初始化重試重定向攔截器
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
複製代碼
RealCall
的構造函數中能夠看到除了傳入的三個參數外,還新初始化一個RetryAndFollowUpInterceptor
這麼一個重試重定向攔截器,這裏涉及到OkHttp
裏的攔截器機制,先無論仍是先記下有這麼一個攔截器是在這裏初始化的。至此爲止,第三步結束,進入最後一個步驟經過call.execute()
或者call.enqueue
方法發送同步或者異步請求。
先來看同步的execute
方法。
@Override public Response execute() throws IOException {
synchronized (this) {
// 判斷是否執行過這個call
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
// 經過dispatcher分發請求call
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 {
// 調用dispatcher的finish方法
client.dispatcher().finished(this);
}
}
複製代碼
首先看到在同步代碼塊中判斷了executed
是否爲true
,executed
是用來標識這個Call
對象是否執行過的,因此每一個Call
只能執行一次,不然就會拋出異常。接着調用client.dispatcher().executed(this)
這個方法,這個dispatcher
就是以前在OkHttpClient
構造函數裏初始化的那個請求分發器,來看這個dispatcher
。
public final class Dispatcher {
//最大請求數
private int maxRequests = 64;
//主機最大請求數
private int maxRequestsPerHost = 5;
//請求執行線程池,懶加載
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<>();
....
}
複製代碼
首先看這個dispatcher
類中的成員變量。默認規定了最大請求數、每一個主機最大請求數,一個線程池用來執行Call
,一個準備運行的異步請求隊列,一個正在運行的異步請求隊列,一個正在運行的同步請求隊列。緊接着回到以前調用的executed
方法代碼:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
複製代碼
executed
方法很簡單,就是把Call
添加到正在運行的同步請求隊列中。
@Override public Response execute() throws IOException {
synchronized (this) {
//判斷是否執行過這個call
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//經過dispatcher分發請求call
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 {
//調用dispatcher的finish方法
client.dispatcher().finished(this);
}
}
複製代碼
再回到call.execute
方法中,client.dispatcher().executed(this)
結束後執行了getResponseWithInterceptorChain()
這個方法,返回的是請求結果Response
,這個方法裏面是調用執行OkHttp
的攔截器鏈,經過一個個攔截器執行完成組裝請求所需參數、設置緩存策略等等最終完成請求返回響應結果數據,涉及到OkHttp
的攔截器機制,先暫時無論,簡單地理解經過這個方法獲得了返回的Response
。最後在finally
代碼塊中執行了client.dispatcher().finished(this)
方法。
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//同步這裏爲false
if (promoteCalls) promoteCalls();
//計算正在運行請求總數
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
複製代碼
注意同步請求finished
方法這裏傳入參數類型是RealCall
。能夠看到finished
又調用重載方法,首先從同步運行隊列中remove
了這個call
對象,而後由於重載傳入的promoteCalls
爲false
,沒有執行promoteCalls
這個從新整理排序隊列的方法,直接執行了runngCallsCount
方法,這個方法用來計算正在運行的請求總數。
public synchronized int runningCallsCount() {
//正在運行的請求總數 = 正在運行的異步請求 + 正在運行的同步請求
return runningAsyncCalls.size() + runningSyncCalls.size();
}
複製代碼
至此同步請求流程執行結束。
接下來看異步請求,調用的是call.enqueue
方法:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
// 判斷是否運行過
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);、
//調用dispatcher的enqueue方法,建立了一個AsyncCall並將獲取結果的回調傳入
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製代碼
這裏能夠看到和同步方法同樣先判斷是否執行過,而後調用client.dispatcher().enqueue(new AsyncCall(responseCallback))
方法,把傳進來的callback
封裝成一個AsyncCall
對象。進入dispatcher
的enqueue
方法中:
synchronized void enqueue(AsyncCall call) {
// 判斷正在運行的異步請求數是否小於最大請求數和主機請求數是否小於主機最大請求數
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
複製代碼
方法中首先判斷正在運行的異步請求隊列是否達到最大請求數和每一個主機的最大請求數,達到了就把call
加入到準備隊列中,不然加入運行隊列而且交給消費者線程池executorService
處理。因此很容易想到AsyncCall
其實是個Runnable
。先進入executorService
方法來看這個線程池是怎樣建立的。
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
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;
}
複製代碼
在初始化Dispatcher
時候能夠傳入一個線程池,在執行請求時默認也會調用executorService
方法,若是線程池爲空,則建立一個核心線程數爲0,最大線程數爲 Integer.MAX_VALUE
,線程超時時間爲60秒的一個線程池,雖然最大線程數雖然爲Integer.MAX_VALUE
可是因爲運行隊列限制了最大請求數默認爲64個,因此也就不會由於一直建立新線程而致使內存問題。
再來看AsyncCall
的代碼:
final class AsyncCall extends NamedRunnable {
......
}
複製代碼
AsyncCall
是RealCall
的一個內部類,它果真繼承了一個叫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
方法,而NamedRunnable
中的execute
方法又是抽象方法,它的實如今其子類AsyncCall
中。
@Override protected void execute() {
boolean signalledCallback = false;
try {
// 調用getResponseWithInterceptorChain方法獲取響應
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) {
......
} finally {
client.dispatcher().finished(this);
}
}
複製代碼
AsyncCall
中的execute
方法裏一樣執行了getResponseWithInterceptorChain
這個方法得到到Response
,而後判斷請求是否取消,取消回調onFailure
拋出異常,沒取消回調onResponse
方法將請求響應傳遞出去。最後在finally
代碼塊中依舊調用了dispatcher().finished(this)
方法。
void finished(AsyncCall call) {
//promoteCalls爲true
finished(runningAsyncCalls, call, true);
}
private <T> void finished(Deque<T> 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();
}
}
複製代碼
看清楚此次傳入的是AysncCall
類型,因此調用的是這個重載的finished
方法,一樣的從運行隊列中移除當前call
對象,可是此時promoteCalls
是true
就會執行promoteCalls
這個方法了。
private void promoteCalls() {
//正在運行的異步請求隊列長度大於等於最大請求數直接return
if (runningAsyncCalls.size() >= maxRequests) return;
//準備運行的異步請求隊列爲空也就直接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);
}
//若是正在運行的請求隊列長度超過最大請求數return跳出循環
if (runningAsyncCalls.size() >= maxRequests) return;
}
}
複製代碼
promoteCalls
方法做用是將準備隊列裏的請求放入到正在運行隊列並將請求加入運行線程池。首先判斷正在運行的異步請求隊列請求是否已滿和準備運行的異步請求隊列是否爲空的狀況,若是都不知足,說明此時運行隊列未滿且還有請求在準備隊列中,就從準備隊列中取出請求放入運行隊列中交給線程池處理並從準備隊列中移除。promoteCalls
執行完後又從新計正在運行的請求總數。至此異步請求流程結束。如下是OkHttp
的運行邏輯流程圖。
簡單的總結下建立OkHttpClient
和Request
從而得到請求Call
,Call
經過Dispatcher
調度加入對應隊列,異步請求會由線程池從隊列中取出執行,調用攔截器鏈得到響應結果返回這麼一個過程。
接下來來看以前一直忽略的getResponseWithInterceptorChain
方法,來看看攔截器機制是怎麼實現的,這個攔截器鏈究竟是什麼。
Response getResponseWithInterceptorChain() throws IOException {
// 建立一個存放攔截器的List集合
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());
//調用攔截器鏈的proceed方法返回
return chain.proceed(originalRequest);
}
複製代碼
getResponseWithInterceptorChain
方法裏的代碼也不是不少,首先建立了一個存放interceptor
攔截器的List
集 合,並往裏添加了許多攔截器,包括在RealCall
構造函數中建立retryAndFollowUpInterceptor
攔截器,以後建立了一個RealInterceptorChain
真正的攔截器鏈對象,把剛纔的List
傳入,最後調用了chain.proceed
方法得到響應Respone
返回。下面直接來看這個proceed
方法。
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
......
// 建立下一個攔截器鏈
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 得到list中第index個攔截器
Interceptor interceptor = interceptors.get(index);
// 調用它的intercept方法將next攔截器鏈傳入
Response response = interceptor.intercept(next);
......
return response;
}
複製代碼
能夠看到proceed(request)
方法又調用了一個四個參數的重載方法,撇開拋異常的判斷看主要的實現,這裏一樣再次建立了一個叫next
攔截器鏈對象。接着獲取到攔截器集合中的第index
個攔截器,調用了攔截器的intercept
方法並將新的這個next
攔截器鏈傳入,進而獲取道返回的Response
。這裏就要仔細看一下了,先看看這個index
,它是攔截器鏈建立時傳入的。
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;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
//傳入的index
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
複製代碼
構造函數中傳入了index
和全部攔截器的List
,來看下在getResponseWithInterceptorChain
方法裏第一次傳入的值。
// getResponseWithInterceptorChain方法中建立攔截器鏈
// 第一次傳入的index爲0
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
複製代碼
// chain.proceed`方法中建立下一個攔截器鏈,index爲index+1
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 這裏第一次拿到的是第0個攔截器
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
複製代碼
這裏第一次傳入的Index
是0,而後在攔截器鏈chain.proceed
方法中建立的next
攔截器鏈傳入的是index+1
即爲1。而後得到到的interceptor
就是攔截器集合中下標爲0的攔截器,調用它的intercept
方法,從上面攔截器集合添加能夠看出,默認第一個是RetryAndFollowUpInterceptor
攔截器,因而再進入去看它的intercept
方法。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
......
response = realChain.proceed(request, streamAllocation, null, null);
......
}
複製代碼
這裏對intercept
方法中攔截器自己功能相關實現代碼先行省略,只關心對攔截器機制實現的代碼。看到其中有這樣一行realChain.proceed
,這個realChain
就是傳入的以前新建立的next
攔截器鏈,這樣就又調用回到RealInterceptorChain
的proceed
方法中,從而又會建立一個新的攔截器鏈,將index
再加一傳入,以後又會調用攔截器集合中下標爲1的攔截器的intercept
方法,而後又會調用到傳入index
爲2的攔截器鏈的proceed
方法,如此循環往復,直到攔截器集合中的全部攔截器都執行完畢,最終會執行到最後一個攔截器CallServerInterceptor
,在其中會得到請求返回結果封裝成Response
返回。以上就是OkHttp
中的攔截器機制的核心邏輯,經過代碼邏輯看出來這裏實際是使用了責任鏈設計模式。
在存放攔截器的List
中除了添加client
裏的應用攔截器和網絡攔截器以外,還有五個默認要加的攔截器,這五個攔截器很重要,也是主要實現功能的攔截器,接下來分別來看看這五個攔截器的做用。
看名字就能猜到這個攔截器的功能,就是用來重試和重定向的,來看它的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 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;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
//判斷是否要重定向,獲取重定向後的request
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
複製代碼
來梳理下這個攔截器的工做流程,intercept
方法裏主要作了這樣幾件事:
1. 建立StreamAllocation
對象。
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//建立StreamAllocation
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
複製代碼
方法開始先從攔截器鏈中得到請求的Request
、eventListener
和call
對象,接着就建立了一個StreamAllocation
對象,構造函數中傳遞了OkhttpClient
中的connectionPool
、Address
、call
、eventListener
和callStackTrace
。其中Address
又由createAddress
方法建立,createAddress
方法中判斷了是否爲HTTPS
類型請求,並根據傳入的url
和client
中參數建立了一個Address
對象返回。
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
複製代碼
2. 開啓循環判斷請求是否被取消。
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
複製代碼
開啓了一個while
循環,首先判斷若是被取消了就將streamAllocation
釋放並拋出異常。
3. 調用RealInterceptorChain
的proceed
方法進入下一個攔截器處理請求。
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
複製代碼
在調用realChain.proceed
中若是發生異常會被捕獲,RouteException
表示路由鏈接失敗,IOException
說明與服務器鏈接失敗,無論哪一個異常被捕獲後會調用recover
方法判斷是否可重試,不可重試會直接拋出異常。
4. 判斷進行重定向操做
Request followUp;
try {
// followUpRequest方法判斷是否要重定向,返回重定向後的request
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
// 重定向的request爲空說明不須要重定向直接返回response
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
// 判斷重定向次數,大於最大次數釋放streamAllocation拋出異常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 重定向的body是不可重複的一樣也釋放streamAllocation拋出異常
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 判斷是不是同一個鏈接,不是同一個就釋放原來的streamAllocation,從新建立streamAllocation
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
複製代碼
重定向時候會先經過followUpRequest
方法判斷是否要重定向。
private Request followUpRequest(Response userResponse, Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET // or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols. if (url == null) return null; // If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
// 408 s are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
return null;
}
}
複製代碼
followUpRequest
方法中經過響應Response
中的響應碼判斷,HTTP
中響應碼爲3XX
的表示須要重定向,方法中的switch-case
就是根據不一樣響應碼作的處理,須要重定向會從Header
中獲取Location
重定向的位置,建立新的Request
返回,不須要重定向直接返回null
。
獲取到重定向的Request
後接着判斷其是否爲空,若爲空說明不須要重定向,則直接返回Response
並釋放streamAllocation
。接着繼續判斷重定向次數是否超過最大次數(默認20次)和重定向的body
是不是不可重複的,超過最大次數或者是不可重複的,就一樣釋放streamAllocation
且拋出異常。最後再調用sameConnection
方法判斷是否爲同一個鏈接,不是同一個就釋放原來的streamAllocation·
從新建立新的streamAllocation
對象。
仍是進入intercept
方法。
@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();
//設置contentType
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
//設置contentLength或Transfer-Encoding
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
//設置host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//設置Connection頭
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
boolean transparentGzip = false;
//設置Accept-Encoding爲gzip
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
//設置cookies
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//設置User-Agent
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//調用chain.proceed進入下一個攔截器
Response networkResponse = chain.proceed(requestBuilder.build());
//響應頭
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//若是支持gzip壓縮則進行gzip解壓
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
//解壓後移除Content-Encoding、Content-Length這兩個響應頭
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
//構建Response返回
return responseBuilder.build();
}
複製代碼
BridgeInterceptor
的intercept
方法邏輯比較簡單,正如它的名字同樣,這個攔截器的主要功能是給請求添加請求頭,若是支持gzip
則對響應體進行gizp
解壓,最後返回body
解壓後的Response
。這裏註釋應該還算清楚就很少講了。
@Override public Response intercept(Chain chain) throws IOException {
// cache不爲空,將request做爲key傳入獲取緩存的Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 建立緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
}
// 根據緩存策略判斷,若是不使用網絡且緩存爲空,則返回一個504的Response錯誤
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();
}
// 若是不使用網絡,就直接返回緩存的Response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 走到這說明要使用網絡,就繼續進入下一個攔截器
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 得到網絡響應,同時又有緩存
if (cacheResponse != null) {
// 根據網絡響應的響應碼若是是304就使用緩存
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());
}
}
// 走到這裏就說明要使用網絡返回的響應結果了
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 將網絡請求加入緩存
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.
}
}
}
return response;
}
複製代碼
緩存攔截器顧名思義必定是設置緩存相關的操做,它具體作了如下幾個操做。
1. 讀取緩存
// 緩存不爲空,將request做爲key傳入獲取緩存的Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
複製代碼
2.建立緩存策略
// 建立緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
複製代碼
3. 根據緩存策略判斷緩存使用
// 根據緩存策略判斷,若是不使用網絡且緩存爲空,則返回一個504的Response錯誤
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();
}
// 若是不使用網絡,就直接返回緩存的Response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
複製代碼
這裏根據緩存策略判斷不使用網絡且沒有緩存,就直接返回一個錯誤的Response
,不使用網絡有緩存則返回緩存的Response
。
4. 使用網絡響應則繼續進入下一個攔截器處理
Response networkResponse = null;
try {
// 走到這說明要使用網絡,就繼續進入下一個攔截器
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
複製代碼
5.獲取到網絡響應同時又有緩存
// 得到網絡響應,同時又有緩存
if (cacheResponse != null) {
// 根據網絡響應的響應碼若是是304就使用緩存
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());
}
}
複製代碼
得到到網絡響應結果,同時又有緩存,再根據網絡響應碼判斷,若是響應嗎爲304則使用緩存,返回緩存的Response
。
6.使用網絡響應結果,並加入緩存
// 走到這裏就說明要使用網絡返回的響應結果了
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 將網絡請求加入緩存
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
不爲空且有響應體緩存策略也容許緩存,就將網絡響應結果存入緩存。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
ConnectInterceptor
的intercept
方法中代碼意外的少。主要作了三件事,一是經過streamAllocation
的newStream
方法獲取到了一個HttpCodec
對象。二是再調用了streamAllocation
的connection
方法獲取到一個RealConnection
鏈接對象。最後是照例調用realChain.proceed
進入下一個攔截器。還記得StreamAllocation
對象嗎?是在RetryAndFollowUpInterceptor
的intercept
方法中的建立的實例,並在realChain.proceed
方法中傳入。
@Override public Response intercept(Chain chain) throws IOException {
......
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
......
response = realChain.proceed(request, streamAllocation, null, null);
......
}
複製代碼
如今再來仔細看一下攔截器鏈的proceed
方法的四個參數。
//只有Request一個傳參的proceed方法
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
//四個傳參的proceed方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
.....
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;
}
//RealInterceptorChain構造函數
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;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
Response getResponseWithInterceptorChain() throws IOException {
......
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
//RetryAndFollowUpInterceptor中intercept方法
@Override public Response intercept(Chain chain) throws IOException {
......
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
......
response = realChain.proceed(request, streamAllocation, null, null);
......
}
//BridgeInterceptor中intercept方法
@Override public Response intercept(Chain chain) throws IOException {
......
Response networkResponse = chain.proceed(requestBuilder.build());
......
return responseBuilder.build();
}
//CacheInterceptor中intercept方法
@Override public Response intercept(Chain chain) throws IOException {
......
networkResponse = chain.proceed(networkRequest);
......
return response;
}
//ConnectInterceptor中intercept方法
@Override public Response intercept(Chain chain) throws IOException {
.......
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
RealInterceptorChain
中有兩個重載的proceed
方法,四個參數的proceed
方法參數分別爲Request
、StreamAllocation
、httpCodec
和RealConnection
這四個類型,而單個參數的方法只傳了一個Request
,剩下的,其他三個參數傳遞全是取的成員變量中的對應值。proceed
方法裏會建立新的next
攔截器鏈,將接收到的這四個類型對象傳入到新建的攔截器鏈中。又由於成員變量的值是在初始化對象時傳入的,能夠看到在getResponseWithInterceptorChain
方法中初始化第一個攔截器鏈時除了Request
其他三個類型所有傳的null
,在RetryAndFollowUpInterceptor
的intercept
方法中建立了StreamAllocation
對象,繼而在它調用四個傳參的chain.proceed
方法時將Request
和streamAllocation
傳入,其他兩個傳參依舊爲空。在 BridgeInterceptor
和CacheInterceptor
的intercept
方法中沒有建立新的類型對象只是對Request
作了修改包裝,因此都是調用的一個傳參的方法,到了剛纔看到的ConnectInterceptor
的intercept
方法裏纔得到了httpCodec
和RealConnection
兩個對象,因此這四個類型對象到此才所有建立完成實例,最終傳到最後一個CallServerInterceptor
攔截器中進行向服務器發送網絡請求。
繼續回到ConnectInterceptor
裏來,先來看StreamAllocation
這個類。
/**
* This class coordinates the relationship between three entities:
*
* <ul>
* <li><strong>Connections:</strong> physical socket connections to remote servers. These are
* potentially slow to establish so it is necessary to be able to cancel a connection
* currently being connected.
* <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
* connections. Each connection has its own allocation limit, which defines how many
* concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
* at a time, HTTP/2 typically carry multiple.
* <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
* its follow up requests. We prefer to keep all streams of a single call on the same
* connection for better behavior and locality.
* </ul>
**/
複製代碼
從類的註釋能夠看出這個類是用來協調Connections
、Streams
、Calls
這三個實體間的關係的。來看newStream
方法。
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
//獲取設置的鏈接超時時間、讀取超時時間和寫入超時時間
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
//ping的耗時
int pingIntervalMillis = client.pingIntervalMillis();
//鏈接失敗是否重連
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//找到一個健康的鏈接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//從鏈接中獲取一個HttpCodec流
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
複製代碼
從方法名就能看出這個方法是用來建立一個流,返回的是一個HttpCodec
類型。方法中第一步先調用findHealthyConnection
得到一個鏈接,來看這個findHealthyConnection
方法。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
// 開啓循環
while (true) {
// 經過findConnection方法找到一個鏈接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// 若是鏈接的successCount等於0,說明是一個新的鏈接,就直接返回
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// 走到這說明不是新的鏈接要先判斷是不是一個健康的鏈接,若是不是就跳過此次繼續尋找
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
// 到這就是健康的鏈接因而返回
return candidate;
}
}
複製代碼
findHealthyConnection
方法中開啓了一個循環不停地調用findConnection
方法尋找鏈接,找到以後進行判斷,若是是一個新的鏈接直接返回,不然須要判斷鏈接是否「健康」,知足「健康」條件就會做爲結果返回,不知足則跳過此次循環繼續尋找。接着進入findConnection
查看一下鏈接是怎麼找到的。
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
//異常判斷
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 先得到當前的鏈接
releasedConnection = this.connection;
//releaseIfNoNewStreams方法裏會判斷當前鏈接是否爲空和是否能建立新流,不知足就會釋放鏈接關閉socket
toClose = releaseIfNoNewStreams();
// 若是當前鏈接不爲空就將鏈接賦給result
if (this.connection != null) {
// We had an already-allocated connection and its good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, dont report it as released!
releasedConnection = null;
}
if (result == null) {
//這裏若是result爲空就從鏈接池中獲取一個鏈接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {//從鏈接池中找到鏈接將它賦給result
foundPooledConnection = true;
result = connection;
} else {
//沒找到就將路由route賦給selectedRoute
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
//若是鏈接池裏找到就直接返回
return result;
}
// 若是須要一個路由選擇就建立一個
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// 根據以前路由選擇獲取全部的路由
List<Route> routes = routeSelection.getAll();
//循環路由,再根據這些路由去鏈接池中獲取鏈接
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
//找到了就將鏈接賦給result
result = connection;
this.route = route;
break;
}
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
route = selectedRoute;
refusedStreamCount = 0;
//到這還沒找到就建立一個鏈接
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, were done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
//經過connect方法進行鏈接,進行握手
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// 將鏈接放入到鏈接池
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
複製代碼
findConnection
方法比較長,大致邏輯是先從鏈接池尋找,找不到纔會建立鏈接。仍是一點一點來看。
// 先得到當前的鏈接
releasedConnection = this.connection;
//releaseIfNoNewStreams方法裏會判斷當前鏈接是否爲空和是否能建立新流,不知足就會釋放鏈接關閉socket
toClose = releaseIfNoNewStreams();
// 若是當前鏈接不爲空就將鏈接賦給result
if (this.connection != null) {
// We had an already-allocated connection and its good.
result = this.connection;
releasedConnection = null;
}
複製代碼
首先使用當前的鏈接,當前鏈接不爲空且能建立新流就把它賦給結果result
。
if (result == null) {
//這裏若是result爲空就從鏈接池中獲取一個鏈接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {//從鏈接池中找到鏈接將它賦給result
foundPooledConnection = true;
result = connection;
} else {
//沒找到就將路由route賦給selectedRoute
selectedRoute = route;
}
}
複製代碼
第二步是當前鏈接不可用就從鏈接池中獲取一個鏈接。這裏Internal.instance.get
方法會從鏈接池中尋找鏈接,深刻進去看一下。
/**
* Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation * packages. The only implementation of this interface is in {@link OkHttpClient}. */ public abstract class Internal { } 複製代碼
Internal
是個抽象類根據註釋它的惟一實如今OkHttpClient
中。
Internal.instance = new Internal() {
......
@Override public RealConnection get(ConnectionPool pool, Address address,
StreamAllocation streamAllocation, Route route) {
return pool.get(address, streamAllocation, route);
}
......
}
複製代碼
OkHttpClient
中的Internal.instance
的get
方法會調用鏈接池的get
方法獲取一個鏈接。
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
複製代碼
在ConnetionPool
的get
方法中獲取鏈接後會調用streamAllocation.acquire
方法,因而又回到StreamAllocation
類中。
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
this.reportedAcquired = reportedAcquired;
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
複製代碼
acquire
方法又會把這個鏈接賦給成員變量,這樣StreamAllocation
中就獲取到了鏈接池中的這個鏈接。再回到findConnection
方法,繼續第三步。
if (result != null) {
//若是鏈接池裏找到就直接返回
return result;
}
// 若是須要一個路由選擇就建立一個
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// 根據以前路由選擇獲取全部的路由
List<Route> routes = routeSelection.getAll();
// 循環路由,再根據這些路由去鏈接池中獲取鏈接
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
//找到了就將鏈接賦給result
result = connection;
this.route = route;
break;
}
}
}
複製代碼
此時若是從鏈接池找到鏈接就直接返回結果,不然繼續向下建立一個路由選擇,而後再循環其中全部路由再次在鏈接池中獲取一次鏈接。
//到這還沒找到就建立一個鏈接
result = new RealConnection(connectionPool, selectedRoute);
......
//經過connect方法進行鏈接,進行握手
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// 將鏈接放入到鏈接池
Internal.instance.put(connectionPool, result);
複製代碼
若是尚未找到就建立一個新鏈接,接着調用connect
方法進行鏈接,最後將新鏈接放入鏈接池。至此尋找獲取鏈接這個步驟就結束了這裏再繼續看一下RealConnection
的connect
這個鏈接方法。
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
......
while (true) {
try {
// 這裏從路由判斷是否進行隧道傳輸
if (route.requiresTunnel()) {
// 是則進行隧道鏈接
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
// 不然進行socket鏈接
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
......
}
複製代碼
connect
方法中省略各類判斷只看鏈接相關代碼,這裏從路由中先判斷是否須要進行隧道鏈接,根據結果調用鏈接隧道或者鏈接套接字socket
。這裏只看socket
鏈接,進入connectSocket
方法。
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
// 獲取代理
Proxy proxy = route.proxy();
// 獲取地址
Address address = route.address();
// 根據代理類型建立socket
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
//進行socket
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
//經過Okio獲取socket輸入輸出
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
複製代碼
connectSocket
方法中其實最核心的就是Platform.get().connectSocket
方法,這裏Platform
類是爲了多平臺適配,最終會調用到AndroidPlatform
中的connectSocket
方法,其中就會調用socket
的connect
方法。
@Override public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
......
socket.connect(address, connectTimeout);
......
}
複製代碼
瞭解完鏈接創建的過程後,再回到StreamAllocation
的newStream
方法中來。
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
......
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
.......
}
複製代碼
接着調用resultConnection
的newCodec
方法。
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
複製代碼
newCodec
方法裏判斷若是是用HTTP2.0
就建立Http2Codec
返回不然建立Http1Codec
返回,這兩個類都是HttpCodec
的實現類。再回到一開始的ConnectInterceptor
的intercept
方法中。
@Override public Response intercept(Chain chain) throws IOException {
......
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
newStream
後調用streamAllocation.connection
方法獲取到了須要的鏈接。
public synchronized RealConnection connection() {
return connection;
}
複製代碼
最後一個攔截器就要向服務器發送請求讀取響應了。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
// 寫入請求頭
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
// 判斷是否容許發送請求體和是否有請求體
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 判斷請求頭中有Expect:100-continue
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
// 發送請求頭
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
// 接收響應頭
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// 若是服務器容許發送請求體
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
// 寫入請求體
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
// 結束請求發送
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
// 讀取響應頭
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 構建響應結果Response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
// 獲取響應碼
int code = response.code();
// 響應碼爲100,再次讀取響應頭構建響應結果
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
//WebSocket或者響應碼爲101,構建一個響應體爲空的response
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
// 不然將響應體添加到response對象中
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
......
return response;
}
複製代碼
仍是梳理一下主要流程,看intercept
方法作了哪些步驟。
1. 寫入請求頭
//寫入請求頭
httpCodec.writeRequestHeaders(request);
複製代碼
首先是經過httpCodec
對象寫入請求頭。
2. 發送請求體
Response.Builder responseBuilder = null;
// 判斷是否容許發送請求體和是否有請求體
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 判斷請求頭中有Expect:100-continue
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
// 發送請求頭
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
// 接收響應頭
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// 若是服務器能夠接收處理請求體
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
// 寫入請求體
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
// 結束請求發送
httpCodec.finishRequest();
複製代碼
發送請求體前先判斷請求方法是否運行發送請求體和是否有請求體,若是有請求體且支持發送,再判斷請求頭中是否有Expect:100-continue
這個頭,若是含有這個請求頭就先讀取服務器的響應頭,這個請求頭的做用是詢問服務器是否能夠接收處理請求體的數據,具體解釋能夠看這篇文章。服務器能夠接受就將請求體寫入,最後調用httpCodec.finishRequest
結束請求發送。
3. 獲取響應
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
// 讀取響應頭
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 構建響應結果Response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
// 獲取響應碼
int code = response.code();
// 響應碼爲100,再次讀取響應頭構建響應結果
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
//forWebSocket爲true且響應碼爲101,構建一個響應體爲空的response
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
// 不然將響應體添加到response對象中
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
......
return response;
複製代碼
發送完請求最後一步就是接收響應了,首先仍是經過httpCodec
讀取響應頭,構建一個供返回的響應結果對象Response
,接着從Response
中獲取到響應碼判斷若是響應碼爲100,則再次讀取響應頭,從新構建一個響應結果對象Response
,獲取響應碼。最後再判斷forWebSocket
和響應碼是否爲101,若是forWebS
爲true
且響應碼爲101,就將一個空的響應體添加到以前構建的Response
對象中,不然將解碼後的響應體添加到Response
對象中。方法的最後返回響應Response
對象。
在以前的5.3緩存攔截器CacheInterceptor
中,描述了緩存欄解器中的工做過程對緩存自己的沒有深究,接下來就來看看這一部份內容。OkHttp
的緩存是創建在HTTP
協議的緩存機制上的。關於HTTP
協議的緩存不清楚的建議看下這篇文章,這裏就直接去研究OkHttp
中相關的實現類了。
CacheControl
這個類是OkHttp
中對應HTTP
協議的Cache-Control
頭的一個描述,對Cache-Control
頭的各類取值作了封裝。
private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds,
int minFreshSeconds, boolean onlyIfCached, boolean noTransform, boolean immutable,
@Nullable String headerValue) {
this.noCache = noCache;
this.noStore = noStore;
this.maxAgeSeconds = maxAgeSeconds;
this.sMaxAgeSeconds = sMaxAgeSeconds;
this.isPrivate = isPrivate;
this.isPublic = isPublic;
this.mustRevalidate = mustRevalidate;
this.maxStaleSeconds = maxStaleSeconds;
this.minFreshSeconds = minFreshSeconds;
this.onlyIfCached = onlyIfCached;
this.noTransform = noTransform;
this.immutable = immutable;
this.headerValue = headerValue;
}
CacheControl(Builder builder) {
this.noCache = builder.noCache;
this.noStore = builder.noStore;
this.maxAgeSeconds = builder.maxAgeSeconds;
this.sMaxAgeSeconds = -1;
this.isPrivate = false;
this.isPublic = false;
this.mustRevalidate = false;
this.maxStaleSeconds = builder.maxStaleSeconds;
this.minFreshSeconds = builder.minFreshSeconds;
this.onlyIfCached = builder.onlyIfCached;
this.noTransform = builder.noTransform;
this.immutable = builder.immutable;
}
複製代碼
CacheControl
類中提供了兩個默認的實現FORCE_NETWORK
和FORCE_CACHE
,分表表示強制只使用網絡響應和強制只使用緩存中的響應。
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
複製代碼
在建立請求的時候能夠爲每一個請求設置CacheControl
。
Request request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
複製代碼
CacheStrategy
表示緩存策略,在CacheInterceptor
的intercept
方法中建立了這個對象實例,以後也根據這個對象中的networkRequest
和cacheResponse
判斷緩存的策略。
// CacheInterceptor中建立CacheStrategy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
複製代碼
下面就來看它的建立方法。
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
複製代碼
建立CacheStrategy.Factory
只是將傳入的參數賦值,而且從傳入的緩存響應中獲取有關的響應頭的值,賦值到成員變量。接着調用了get
方法獲取到CacheStrategy
實例。
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient. return new CacheStrategy(null, null); } return candidate; } 複製代碼
get
方法中先調用getCandidate
得到一個當前的緩存策略candidate
,而後判斷candidate.networkRequest
不爲空且當前請求的CacheControl
設置只用緩存,就返回一個networkRequest
和cacheResponse
皆爲空的緩存策略,不然返回getCandidate
方法得到的策略。下面先看networkRequest
和cacheResponse
這倆到底表示個啥?
/** The request to send on the network, or null if this call doesn't use the network. */ public final @Nullable Request networkRequest; /** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
複製代碼
根據註釋看來就是說若是請求調用不使用網絡networkRequest
就爲null
,不使用緩存cacheResponse
就爲null
。因此以前的判斷表示的是若是緩存策略要使用網絡但這個請求的cacheControl
設置只用緩存,就返回一個networkRequest
和cacheResponse
皆爲空的策略,從而在CacheInterceptor
中會判斷到緩存策略既不用網絡也不用緩存進而返回一個504錯誤的響應結果。
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();
}
複製代碼
接着進入getCandidate
方法。
private CacheStrategy getCandidate() {
// 自己傳入的cacheResponse爲空
if (cacheResponse == null) {
// 返回一個cacheResponse爲空的CacheStrategy
return new CacheStrategy(request, null);
}
// 若是請求是https而且缺乏必要的握手
if (request.isHttps() && cacheResponse.handshake() == null) {
// 返回一個cacheResponse爲空的CacheStrategy
return new CacheStrategy(request, null);
}
// 若是不容許緩存
if (!isCacheable(cacheResponse, request)) {
// 返回一個cacheResponse爲空的CacheStrategy
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
// 若是請求中CacheControl設置不用緩存
if (requestCaching.noCache() || hasConditions(request)) {
// 返回一個cacheResponse爲空的CacheStrategy
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
// 若是響應中CacheControl設置是不變的
if (responseCaching.immutable()) {
// 返回一個networkRequest爲空的CacheStrategy
return new CacheStrategy(null, cacheResponse);
}
// 獲取緩存響應年齡 毫秒值
long ageMillis = cacheResponseAge();
// 獲取返回響應新鮮毫秒數
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
// 獲取最小新鮮毫秒數
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
// 獲取最大已過時時間
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
// 若是知足沒有noCache即強制不緩存而且知足ageMillis + minFreshMillis < freshMillis + maxStaleMillis
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
// 返回一個networkRequest爲空的CacheStrategy
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
複製代碼
方法中是根據傳入的networkRequest
和cacheResponse
頭中信息判斷建立不一樣要求緩存策略此處的邏輯都是基於HTTP
協議文檔要求的。
Cache
是用來緩存的類。在初始化OkHttpClient
時,能夠爲其設置一個Cache
並設置緩存位置和緩存大小。
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(new File(getExternalCacheDir(), "cache"), 10 * 1024 * 1024))
.build();
複製代碼
仍是先來看Cache
構造函數。
final DiskLruCache cache;
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
複製代碼
看到構造函數最終會調用DiskLruCache.create
方法建立一個cache
,從這就能夠看出來這個Cache
緩存類是基於DiskLruCache
實現的。那麼接下來先看它的添加方法。
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
// 判斷是不是無效的緩存
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
//無效的則remove
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
// 不是get請求不緩存直接返回null
if (!requestMethod.equals("GET")) {
return null;
}
// 請求頭中包含*號不緩存直接返回null
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
// 建立Entry傳入response
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
// 寫入緩存
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
複製代碼
put
方法中先判斷無效的緩存將其remove
,invalidatesCache
方法中判斷請求的請求類型,若請求類型爲POST
、PATCH
、PUT
、DELETE
、MOVE
中一個就將該響應remove
。
public static boolean invalidatesCache(String method) {
return method.equals("POST")
|| method.equals("PATCH")
|| method.equals("PUT")
|| method.equals("DELETE")
|| method.equals("MOVE"); // WebDAV
}
複製代碼
以後判斷請求不爲GET
就不緩存,請求頭重帶有*
號也不緩存,以後就建立Entry
實體,調用key
方法生成key
而後寫入緩存。
添加緩存結束了接着看取緩存get
方法。
@Nullable Response get(Request request) {
// 調用key方法發生成key
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
// cache中根據key獲取快照snapshot
snapshot = cache.get(key);
// 快照爲空直接返回空
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
// 從快照中獲取到緩存內容建立Entry對象
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
// 調用entry.response方法獲取到緩存的response
Response response = entry.response(snapshot);
// request與response匹配校驗
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
// 返回緩存結果
return response;
}
複製代碼
get
方法中一樣仍是先根據請求的url
生成key
,而後從緩存中根據key
拿到一個snapshot
快照,從快照中取出緩存內容建立一個Entry
,接着調用entry.response
方法將得到的快照傳入獲得緩存的Response
,對request
與response
進行匹配校驗,最後返回得到的緩存的結果。
經過上面對源碼流程的閱讀,關於OkHttp
的使用運行流程和工做原理應該已經基本瞭解。從中能夠發現OkHttp
底層是基於Socket
鏈接,依據HTTP
協議規範封裝的一套網絡請求框架。相較於基於HttpURLonnection
和httpclient
封裝的Volley
又或者基於OkHttp
封裝的Retrofit
來講OkHttp
更加「底層」一些,而且OkHttp
底層使用了Okio
進行讀寫會更加的高效迅速。