因爲以前項目搭建的是 MVP 架構,由RxJava + Glide + OKHttp + Retrofit
等開源框架組合而成,以前也都是停留在使用層面上,沒有深刻的研究,最近打算把它們所有攻下,尚未關注的同窗能夠先關注一波,看完這個系列文章,(無論是面試仍是工做中處理問題)相信你都在知道原理的狀況下,處理問題更加駕輕就熟。html
Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析 Glide 執行流程java
Android 圖片加載框架 Glide 4.9.0 (二) 從源碼的角度分析 Glide 緩存策略git
從源碼的角度分析 Rxjava2 的基本執行流程、線程切換原理github
從源碼的角度分析 OKHttp3 (一) 同步、異步執行流程web
從源碼的角度分析 Retrofit 網絡請求,包含 RxJava + Retrofit + OKhttp 網絡請求執行流程服務器
在上一篇 從源碼的角度分析 OKHttp3 (一) 同步、異步執行流程 文章中,最後咱們知道是在 getResponseWithInterceptorChain()
函數中完成了最後的請求與響應,那麼內部是怎麼完成請求,並把服務端的響應數據回調給調用層,先來看一段代碼:cookie
Response getResponseWithInterceptorChain() throws IOException {
// 構建一個攔截器調用的容器棧
List<Interceptor> interceptors = new ArrayList<>();
//配置 OKHttpClient 的時候,以 addInterceptor 方式添加的全局攔截器
interceptors.addAll(client.interceptors());
//錯誤、重定向攔截器
interceptors.add(new RetryAndFollowUpInterceptor(client));
//橋接攔截器,橋接應用層與網絡層,添加必要的頭
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//緩存處理,Last-Modified、ETag、DiskLruCache等
interceptors.add(new CacheInterceptor(client.internalCache()));
//鏈接攔截器
interceptors.add(new ConnectInterceptor(client));
//是不是 webSocket
if (!forWebSocket) {
//經過okHttpClient.Builder#addNetworkInterceptor()
//傳進來的攔截器只對非網頁的請求生效
interceptors.addAll(client.networkInterceptors());
}
//真正訪問服務器的攔截器
interceptors.add(new CallServerInterceptor(forWebSocket));
//真正執行 攔截器的 調用者
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//開始執行
Response response = chain.proceed(originalRequest);
//是否取消
if (transmitter.isCanceled()) {
//關閉
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
複製代碼
函數中代碼很少,但確實精髓所在。經過上面代碼跟註釋咱們知道網絡
RealInterceptorChain
對象攔截器,並把 攔截器容器、發射器、請求數據等一些配置傳入進去RealInterceptorChain
的 chain.proceed(originalRequest);
函數, 纔是真正使 這些攔截器執行起來。上一篇文章也簡單的介紹了攔截器,提到了 責任鏈模式,可是這個攔截器它是經過 RealInterceptorChain
對象開啓了責任鏈任務的下發,這裏感受是否是有點像一個 CEO 在下發任務並一層一層的傳遞,也有點像 Android 源碼中 觸摸反饋事件傳遞,OKHttp 的核心其實就在於攔截器。下面咱們就開始一步一步分析 OKHttp 攔截器的精妙所在。
經過上一小節對攔截器的介紹,咱們知道最後是在 RealInterceptorChain
的 chain.proceed(originalRequest)
開啓執行的攔截任務,下面直接進入源碼模式
public final class RealInterceptorChain implements Interceptor.Chain {
...//省略成員變量屬性
public RealInterceptorChain( List<Interceptor> interceptors, //全部攔截器 Transmitter transmitter,//發射器 @Nullable Exchange exchange, //封裝對 OKIO 的請求數據的操做 int index, Request request, Call call, int connectTimeout, int readTimeout, int writeTimeout ){
...//省略賦值代碼
}
//外部 getResponseWithInterceptorChain 函數中調用
public Response proceed( Request request, Transmitter transmitter, @Nullable Exchange exchange )throws IOException {
//index 不能超過攔截器容器大小
if (index >= interceptors.size()) throw new AssertionError();
//若是已經存在了一個 request 的請求鏈接就拋一個異常
if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
...//拋異常代碼省略
}
// 保證開啓調用的惟一性,不然拋一個異常,我的認爲這樣判斷只是使得代碼更加健壯,其實這裏的 Calls 只會是 1;
if (this.exchange != null && calls > 1) {
...//拋異常代碼省略
}
//1. 建立下一個攔截器執行的對象
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
//2. 取出當前的攔截器
Interceptor interceptor = interceptors.get(index);
//3. 調用下一個攔截器的 intercept(Chain) 方法,傳入剛纔新建的 RealInterceptorChain, //返回 Response
Response response = interceptor.intercept(next);
//限制作一些判斷,保證程序健壯
if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
...//拋異常代碼省略
}
//若是返回回來的 response 爲空,那麼就拋一個異常
if (response == null) {
...//拋異常代碼省略
}
//若是響應爲空,也拋出一個異常
if (response.body() == null) {
...//拋異常代碼省略
}
//真正返回服務端的響應
return response;
}
}
複製代碼
請看上面代碼註釋 1,2,3 處,這三處代碼就是分發攔截器執行的核心代碼,首先看註釋一 在 RealInterceptorChain
內部又建立一個 RealInterceptorChain
並傳入 index + 1 等參數, 這裏就是開始遞歸執行攔截器了,每次執行 get(index + 1)攔截器。註釋 2 是取出當前攔截器,註釋三是執行攔截器。
這裏咱們能夠先小節總結下RealInterceptorChain
的做用, 能夠把 RealInterceptorChain 這個類看作看一個遞歸函數 interceptor.intercept(next); 就是開始遞歸的入口,固然有入口確定有出口,其實出口沒有在這個類裏面,這裏我先透露下吧,實際上是在 CallServerInterceptor
請求與響應處理的攔截器中,最後直接return response;
至關於出口。因此 RealInterceptorChain
這個類我的理解就是負責 啓動/中止 攔截器的做用,有點像攔截器的調用委託於 RealInterceptorChain 。
那麼這裏確定是 list.get(index = 0) RetryAndFollowUpInterceptor
攔截器第一個執行了,下面就開始分析 錯誤、重定向攔截器。
在上面介紹攔截器的時候講過,它是錯誤重連、重定向的攔截器,下面咱們看它的核心代碼
public final class RetryAndFollowUpInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
//拿到當前的請求
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//拿到 Transmitter 對象
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//準備鏈接工做
transmitter.prepareToConnect(request);
//判斷是否取消
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
//將當前請求傳遞給下一個攔截器
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
//檢查是否能夠繼續使用
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
//檢查是否能夠繼續使用
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
//若是未成功 釋放鏈接
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
//執行到這裏說明沒有出現異常
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
...//省略代碼
//根據響應來處理請求頭
Request followUp = followUpRequest(response, route);
//若是爲空,不須要重定向,直接返回響應
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
//不爲空,須要重定向
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
//重定向的次數不能大於 20
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//根據重定向以後的請求再次重試
request = followUp;
priorResponse = response;
}
}
}
複製代碼
根據上面代碼分析能夠知道,主要作了如下幾點
ConnectInterceptor
攔截器中BridgeInterceptor
將請求交於它在預處理。當前 RetryAndFollowUpInterceptor
中的 realChain.proceed(request, transmitter, null);
調用走到了 BridgeInterceptor 應用與網絡交互的攔截器。
當上一個攔截器調用了 proceed 函數以後就會走到當前 intercept 函數裏面,裏面具體操做咱們看下源碼處理
public final class BridgeInterceptor implements Interceptor {
private final CookieJar cookieJar;
...//省略構造函數
@Override public Response intercept(Chain chain) throws IOException {
//拿到當前請求 Request
Request userRequest = chain.request();
//拿到 Request 配置參數的 Builder
Request.Builder requestBuilder = userRequest.newBuilder();
//獲取到請求體 body
RequestBody body = userRequest.body();
//判斷請求體是否爲空
if (body != null) {//不爲空的狀況下
//獲取請求體類型
MediaType contentType = body.contentType();
if (contentType != null) {
//將請求體類型添加 header
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");
}
}
//添加header HOST 主機
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//添加鏈接狀態
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
//對數據是否開啓 壓縮--默認添加 Gzip
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
//添加 gzip 壓縮
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
//header 中添加 cookie
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//添加 user-agent
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//執行下一個攔截器 CacheInterceptor
Response networkResponse = chain.proceed(requestBuilder.build());
//對 url 和 cookie 保存
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);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
//返回響應
return responseBuilder.build();
}
...//省略部分代碼
}
複製代碼
從上面代碼中,咱們知道 BridgeInterceptor
主要是對請求頭作一些預處理,以後就調用下一個攔截器。
根據上一個攔截器 BridgeInterceptor
調用最後會走到當前的 intercept
, 根據上面的攔截器介紹知道,它是獲取緩存和更新緩存的做用。下面咱們看下它具體實現
public final class CacheInterceptor implements Interceptor {
final @Nullable InternalCache cache;
...//構造函數省略
@Override public Response intercept(Chain chain) throws IOException {
//判斷緩存若是不爲空,就根據請求拿到緩存響應
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;
...//省略部分代碼
//若是請求跟緩存響應爲空的話,就強制使用緩存,返回錯誤碼爲 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();
}
// 若是 networkRequest 爲空的話,也強制獲取緩存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//調用下一個攔截器
networkResponse = chain.proceed(networkRequest);
} finally {
...
}
// 若是緩存不爲空
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();
//更新響應
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) {
}
}
}
return response;
}
複製代碼
能夠看到這裏主要是對緩存作處理,因爲這裏只講攔截器的調用和一些基本處理邏輯,OKHttp 緩存機制後面會單獨用一篇文章來介紹,在這裏只要知道,若是外部 OKHttpClient 配置了緩存的話(看下面代碼塊,否則緩存都是空的,也不會默認添加緩存),纔會執行緩存 put、get、update,因爲這裏咱們沒有配置緩存策略,因此直接調用下一個攔截器,也就是 ConnectInterceptor
File file = new File(Environment.getExternalStorageDirectory() + "/T01");
Cache cache = new Cache(file, 1024 * 1024 * 10);
OkHttpClient okHttpClient = new OkHttpClient.Builder().
addInterceptor(new LoggingInterceptor())
.cache(cache).
build();
複製代碼
(ps: 攔截攔截器主要參考了:juejin.im/post/5d8364…
緩存攔截器執行完成以後, 下一個調用鏈就是鏈接攔截器了,看一下代碼實現:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//拿到請求
Request request = realChain.request();
//拿到 Transmitter
Transmitter transmitter = realChain.transmitter();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//從新建立一個 Exchange
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
//調用proceed方法,裏面調用下一個攔截器CallServerInterceptor的intercept方法
return realChain.proceed(request, transmitter, exchange);
}
}
複製代碼
經過上面代碼能夠看出 ConnectInterceptor
內部代碼很簡潔,首先拿到 Request 請求,獲取 Transmitter
對象,其次是經過 transmitter 從新建立一個 Exchange , Exchange 是負責將數據寫入到建立鏈接的 IO 流中的交互動做,最後在調用 CallServerInterceptor
攔截器。咱們看下 transmitter.newExchange(chain, doExtensiveHealthChecks)
內部代碼實現
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
//若是沒有 Exchanges 拋一個異常
if (noMoreExchanges) {
throw new IllegalStateException("released");
}
if (exchange != null) {
...//省略拋異常代碼
}
//經過ExchangeFinder的find方法找到一個ExchangeCodec
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
//建立Exchange,並把ExchangeCodec實例codec傳進去,因此Exchange內部持有ExchangeCodec實例
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
複製代碼
ExchangeFinder
對象早在RetryAndFollowUpInterceptor
中經過Transmitter
的prepareToConnect
方法建立,它的 find 方法是鏈接真正建立的地方,ExchangeFinder 是什麼?ExchangeFinder
就是負責鏈接的建立,把建立好的鏈接放入鏈接池,若是鏈接池中已經有該鏈接,就直接取出複用,因此 ExchangeFinder
管理着兩個重要的角色:RealConnection
、RealConnectionPool
,下面講解一下 RealConnectionPool
和RealConnection
,有助於鏈接機制的理解。
鏈接的真正實現,實現了 Connection 接口,內部利用 Socket 創建鏈接,以下:
public interface Connection {
//返回這個鏈接使用的Route
Route route();
//返回這個鏈接使用的Socket
Socket socket();
//若是是HTTPS,返回TLS握手信息用於創建鏈接,不然返回null
@Nullable Handshake handshake();
//返回應用層使用的協議,Protocol是一個枚舉,如HTTP1.一、HTTP2
Protocol protocol();
}
public final class RealConnection extends Http2Connection.Listener implements Connection {
public final RealConnectionPool connectionPool;
//路由
private final Route route;
//內部使用這個rawSocket在TCP層創建鏈接
private Socket rawSocket;
//若是沒有使用HTTPS,那麼socket == rawSocket,不然這個socket == SSLSocket
private Socket socket;
//TLS握手
private Handshake handshake;
//應用層協議
private Protocol protocol;
//HTTP2鏈接
private Http2Connection http2Connection;
//okio庫的BufferedSource和BufferedSink,至關於javaIO的輸入輸出流
private BufferedSource source;
private BufferedSink sink;
public RealConnection(RealConnectionPool connectionPool, Route route) {
this.connectionPool = connectionPool;
this.route = route;
}
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
//...
}
//...
}
複製代碼
RealConnection 中有一個 connect 方法,外部能夠調用該方法創建鏈接,connect 方法以下:
//RealConnection.java
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
//路由選擇
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
} else {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
throw new RouteException(new UnknownServiceException(
"H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
}
}
//開始鏈接
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);
}
//創建HTTPS鏈接
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
break;
}
//...省略異常處理
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
複製代碼
咱們關注註釋1,通常會調用 connectSocket 方法創建 Socket 鏈接,connectSocket 方法以下:
//RealConnection.java
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);
}
//...省略異常處理
try {
//得到Socket的輸入輸出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
//...省略異常處理
}
複製代碼
咱們關注註釋1,Platform 是 okhttp 中根據不一樣 Android 版本平臺的差別實現的一個兼容類,這裏就不細究,Platform 的 connectSocket 方法最終會調用 rawSocket 的 connect() 方法創建其Socket 鏈接,創建 Socket 鏈接後,就能夠經過 Socket 鏈接得到輸入輸出流 source 和 sink,okhttp 就能夠從 source 讀取或往 sink 寫入數據,source 和 sink 是 BufferedSource 和BufferedSink 類型,它們是來自於okio庫,它是一個封裝了 java.io 和 java.nio 的庫,okhttp 底層依賴這個庫讀寫數據,想要了解 okio 這個庫能夠看這篇文章拆輪子系列:拆 Okio。
鏈接池,用來管理鏈接對象 RealConnection ,以下:
public final class RealConnectionPool {
//線程池
private static final Executor executor = new ThreadPoolExecutor(
0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */,
60L /* keepAliveTime */,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
Util.threadFactory("OkHttp ConnectionPool", true));
boolean cleanupRunning;
//清理鏈接任務,在executor中執行
private final Runnable cleanupRunnable = () -> {
while (true) {
//調用cleanup方法執行清理邏輯
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
//調用wait方法進入等待
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
//雙端隊列,保存鏈接
private final Deque<RealConnection> connections = new ArrayDeque<>();
void put(RealConnection connection) {
if (!cleanupRunning) {
cleanupRunning = true;
//使用線程池執行清理任務
executor.execute(cleanupRunnable);
}
//將新建鏈接插入隊列
connections.add(connection);
}
long cleanup(long now) {
//...
}
//...
}
複製代碼
RealConnectionPool 在內部維護了一個線程池,用來執行清理鏈接任務 cleanupRunnable ,還維護了一個雙端隊列 connections ,用來緩存已經建立的鏈接。要知道建立一次鏈接要經歷 TCP握手,若是是 HTTPS 還要經歷 TLS 握手,握手的過程都是耗時的,因此爲了提升效率,就須要connections 來對鏈接進行緩存,從而能夠複用;還有若是鏈接使用完畢,長時間不釋放,也會形成資源的浪費,因此就須要 cleanupRunnable 定時清理無用的鏈接,okhttp 支持 5 個併發鏈接,默認每一個鏈接 keepAlive 爲 5 分鐘,keepAlive 就是鏈接空閒後,保持存活的時間。
當咱們第一次調用 RealConnectionPool 的 put 方法緩存新建鏈接時,若是 cleanupRunnable 還沒執行,它首先會使用線程池執行 cleanupRunnable ,而後把新建鏈接放入雙端隊列,cleanupRunnable 中會調用 cleanup 方法進行鏈接的清理,該方法返回如今到下次清理的時間間隔,而後調用 wiat 方法進入等待狀態,等時間到了後,再次調用 cleanup 方法進行清理,就這樣往復循環。咱們來看一下 cleanup 方法的清理邏輯:
//RealConnectionPool.java
long cleanup(long now) {
int inUseConnectionCount = 0;//正在使用鏈接數
int idleConnectionCount = 0;//空閒鏈接數
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
synchronized (this) {
//遍歷全部鏈接,記錄空閒鏈接和正在使用鏈接各自的數量
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//若是該鏈接還在使用,pruneAndGetAllocationCount種經過引用計數的方式判斷一個鏈接是否空閒
if (pruneAndGetAllocationCount(connection, now) > 0) {
//使用鏈接數加1
inUseConnectionCount++;
continue;
}
//該鏈接沒有在使用
//空閒鏈接數加1
idleConnectionCount++;
//記錄keepalive時間最長的那個空閒鏈接
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
//這個鏈接極可能被移除,由於空閒時間太長
longestIdleConnection = connection;
}
}
//跳出循環後
//默認keepalive時間keepAliveDurationNs最長爲5分鐘,空閒鏈接數idleConnectionCount最大爲5個
if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {//若是longestIdleConnection的keepalive時間大於5分鐘 或 空閒鏈接數超過5個
//把longestIdleConnection鏈接從隊列清理掉
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {//若是空閒鏈接數小於5個 而且 longestIdleConnection鏈接還沒到期清理
//返回該鏈接的到期時間,下次再清理
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {//若是沒有空閒鏈接 且 全部鏈接都還在使用
//返回keepAliveDurationNs,5分鐘後再清理
return keepAliveDurationNs;
} else {
// 沒有任何鏈接,把cleanupRunning復位
cleanupRunning = false;
return -1;
}
}
//把longestIdleConnection鏈接從隊列清理掉後,關閉該鏈接的socket,返回0,當即再次進行清理
closeQuietly(longestIdleConnection.socket());
return 0;
}
複製代碼
從 cleanup 方法得知,okhttp 清理鏈接的邏輯以下:
一、首先遍歷全部鏈接,記錄空閒鏈接數 idleConnectionCount 和正在使用鏈接數inUseConnectionCount ,在記錄空閒鏈接數時,還要找出空閒時間最長的空閒鏈接longestIdleConnection,這個鏈接是頗有可能被清理的;
二、遍歷完後,根據最大空閒時長和最大空閒鏈接數來決定是否清理longestIdleConnection,
2.一、若是 longestIdleConnection 的空閒時間大於最大空閒時長 或 空閒鏈接數大於最大空閒鏈接數,那麼該鏈接就會被從隊列中移除,而後關閉該鏈接的 socket,返回 0,當即再次進行清理;
2.二、若是空閒鏈接數小於5個 而且 longestIdleConnection 的空閒時間小於最大空閒時長即還沒到期清理,那麼返回該鏈接的到期時間,下次再清理;
2.三、若是沒有空閒鏈接 且 全部鏈接都還在使用,那麼返回默認的 keepAlive 時間,5分鐘後再清理;
2.四、沒有任何鏈接,idleConnectionCount 和 inUseConnectionCount 都爲0,把cleanupRunning 復位,等待下一次 put 鏈接時,再次使用線程池執行 cleanupRunnable。
瞭解了 RealConnectionPool 和 RealConnection 後,咱們再回到 ExchangeFinder 的 find 方法,這裏是鏈接建立的地方。
ExchangeFinder的fing方法以下:
//ExchangeFinder.java
public ExchangeCodec find( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
try {
//1.內部調用 findHealthyConnection 函數返回 RealConnection 鏈接對象
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//2. 創建一個新的鏈接
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
...//省略異常處理
}
}
複製代碼
根據註釋 1 咱們知道建立一個 RealConnection ,咱們看下 findHealthyConnection
函數
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException {
while (true) {
//找到一個鏈接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled);
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
//主要判斷鏈接的可用性
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
複製代碼
接着看 findConnection
//ExchangeFinder.java
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;//返回結果,可用的鏈接
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; .
//一、嘗試使用已經建立過的鏈接,已經建立過的鏈接可能已經被限制建立新的流
releasedConnection = transmitter.connection;
//1.一、若是已經建立過的鏈接已經被限制建立新的流,就釋放該鏈接(releaseConnectionNoEvents中會把該鏈接置空),並返回該鏈接的Socket以關閉
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
//1.二、已經建立過的鏈接還能使用,就直接使用它看成結果、
if (transmitter.connection != null) {
result = transmitter.connection;
releasedConnection = null;
}
//二、已經建立過的鏈接不能使用
if (result == null) {
//2.一、嘗試從鏈接池中找可用的鏈接,若是找到,這個鏈接會賦值先保存在Transmitter中
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
//2.二、從鏈接池中找到可用的鏈接
foundPooledConnection = true;
result = transmitter.connection;
} else if (nextRouteToTry != null) {
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
selectedRoute = transmitter.connection.route();
}
}
}
closeQuietly(toClose);
//...
if (result != null) {
//三、若是在上面已經找到了可用鏈接,直接返回結果
return result;
}
//走到這裏沒有找到可用鏈接
//看看是否須要路由選擇,多IP操做
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
//若是有下一個路由
if (newRouteSelection) {
routes = routeSelection.getAll();
//四、這裏第二次嘗試從鏈接池中找可用鏈接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {
//4.一、從鏈接池中找到可用的鏈接
foundPooledConnection = true;
result = transmitter.connection;
}
}
//在鏈接池中沒有找到可用鏈接
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
//五、因此這裏新建立一個鏈接,後面會進行Socket鏈接
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 4.二、若是在鏈接池中找到可用的鏈接,直接返回該鏈接
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
//5.一、調用RealConnection的connect方法進行Socket鏈接,這個在RealConnection中講過
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
//若是咱們剛剛建立了同一地址的多路複用鏈接,釋放這個鏈接並獲取那個鏈接
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
//5.二、把剛剛新建的鏈接放入鏈接池
connectionPool.put(result);
//5.三、把剛剛新建的鏈接保存到Transmitter的connection字段
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
//5.四、返回結果
return result;
}
複製代碼
這個findConnection方法就是整個ConnectInterceptor的核心,咱們忽略掉多IP操做和多路複用(HTTP2),假設如今咱們是第一次請求,鏈接池和Transmitter中沒有該鏈接,因此跳過一、二、3,直接來到5,建立一個新的鏈接,而後把它放入鏈接池和Transmitter中;接着咱們用同一個Call進行了第二次請求,這時鏈接池和Transmitter中有該鏈接,因此就會走一、二、3,若是Transmitter中的鏈接還可用就返回,不然從鏈接池獲取一個可用鏈接返回,因此整個鏈接機制的大概過程以下:
Transmitter
中的鏈接和鏈接池中的鏈接有什麼區別?咱們知道每建立一個 Call,就會建立一個對應的 Transmitter ,一個 Call 能夠發起屢次請求(同步、異步),不一樣的 Call 有不一樣的Transmitter ,鏈接池是在建立 OkhttpClient 時建立的,因此鏈接池是全部 Call 共享的,即鏈接池中的鏈接全部 Call 均可以複用,而 Transmitter 中的那個鏈接只是對應它相應的 Call,只能被本次 Call 的全部請求複用。
瞭解了 okhttp3 的鏈接機制後,咱們接着下一個攔截器 networkInterceptors 。
networkInterceptors
它是 OKHttp 攔截器中的第 6 個攔截器,屬於 網絡攔截器,那麼它的做用是什麼請看下面 攔截器實戰 中介紹。
最後執行到了 OKHttp 最後一個攔截器 CallServerInterceptor
根據源碼中的介紹:它是鏈中的最後一個攔截器。它與服務器進行網絡請求、響應操做。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//拿到 Exchange 與 網絡交互
Exchange exchange = realChain.exchange();
//拿到請求數據
Request request = realChain.request();
//獲取當前請求的時間
long sentRequestMillis = System.currentTimeMillis();
//寫入請求頭
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
//若是能夠寫入請求體
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//若是請求頭添加了 100-continue
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest(); //關閉 IO 流資源
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) { //若是爲空
if (request.body().isDuplex()) {
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else { //通常走 else
//寫入請求體的操做
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
//
exchange.noNewExchangesOnConnection();
}
}
} else { //若是沒有請求體 執行 noRequestBody
exchange.noRequestBody();
}
//若是請求體爲空 而且不支持 isDuplex = false IO 流
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
//讀取響應的 head
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
//構建響應數據
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//拿到響應碼
int code = response.code();
if (code == 100) {
// 構建響應
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(response);
if (forWebSocket && code == 101) {
// 構建空響應體
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
// 經過響應的 body 構造 響應體
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
}
...//省略部分代碼
return response;
}
複製代碼
在當前攔截器中咱們把請求 head /body 經過 okio 寫入了服務端,而後根據服務端的響應數據構建響應頭、響應體等一些響應數據。
到這裏咱們完成了攔截器全部操做,下面進入攔截器實戰。
/** * 打印日誌攔截器 */
class LoggingInterceptor implements Interceptor {
private String TAG = "LoggingInterceptor";
public static String requestBodyToString(RequestBody requestBody) throws IOException {
if (requestBody == null)return "";
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
return buffer.readUtf8();
}
@Override
public Response intercept(Chain chain) throws IOException {
//拿到請求數據
Request request = chain.request();
//能夠在請求服務器以前添加請求頭
request = request.newBuilder()
.addHeader("head-1","1")
.addHeader("head-2","2")
.url("https://juejin.im/user/578259398ac2470061f3a3fb")
.build();
HttpUrl url = request.url();
String scheme = url.scheme();// http https
String host = url.host();// 127.0.0.1
String path = url.encodedPath();// /test/upload/img
String query = url.encodedQuery();// userName=DevYk&userPassword=12345
RequestBody requestBody = request.body();
String bodyToString = requestBodyToString(requestBody);
Log.d(TAG,"scheme--》"+scheme);
Log.d(TAG,"Host--->"+host);
Log.d(TAG,"path--->"+path);
Log.d(TAG,"query--->"+query);
Log.d(TAG,"requestBody---->"+bodyToString+"");
Log.d(TAG,"head---->"+request.headers().names());
//調用下一個攔截器
Response response = chain.proceed(request);
//拿到響應
ResponseBody responseBody = response.body();
String body = responseBody.string();
String type = responseBody.contentType().type();
String subtype = responseBody.contentType().subtype();
//打印響應
Log.d(TAG,"contentType--->"+type+" "+subtype);
Log.d(TAG,"responseBody--->"+body);
return chain.proceed(request);
}
}
複製代碼
添加配置
OkHttpClient okHttpClient = new OkHttpClient.Builder().
addInterceptor(new LoggingInterceptor())
build();
複製代碼
output:
LoggingInterceptor: scheme--》https
LoggingInterceptor: Host--->juejin.im
LoggingInterceptor: path--->/user/578259398ac2470061f3a3fb
LoggingInterceptor: query--->null
LoggingInterceptor: requestBody---->
LoggingInterceptor: head---->[head-1, head-2]
LoggingInterceptor: responseHeader--->text html
LoggingInterceptor: responseBody---><!DOCTYPE html><html ....
複製代碼
public class NetworkInterceptor implements Interceptor {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
if (true) {
Response response = new Response.Builder()
.code(404) // 其實code能夠隨便給
.protocol(Protocol.HTTP_1_1)
.message("根據規定,暫時不能進行網絡請求。")
.body(ResponseBody.create(MediaType.get("text/html; charset=utf-8"), "")) // 返回空頁面
.request(chain.request())
.build();
return response;
} else {
return chain.proceed(chain.request());
}
}
}
複製代碼
配置
OkHttpClient okHttpClient = new OkHttpClient.Builder().
addInterceptor(new LoggingInterceptor()).
addInterceptor(new NetworkInterceptor()).
build();
複製代碼
Output:
LoggingInterceptor: responseCode--->404
LoggingInterceptor: responseMessage--->根據規定,暫時不能進行網絡請求。
LoggingInterceptor: responseisSuccessful--->false
複製代碼
小總結:攔截器分爲 應用攔截器、網絡攔截器 根據官網解釋有這幾點:
應用攔截器
If-None-Match
。Chain.proceed()
。Chain.proceed()
。網絡攔截器
Connection
帶有請求的。因此怎麼選擇看本身需求了。
根據上面的攔截器講解和實戰,相信你們對 OKHttp 攔截器有了必定的認識,這裏咱們根據分析來總結下:
其實每個攔截器都對應一個 RealInterceptorChain ,而後每個interceptor 再產生下一個RealInterceptorChain,直到 List 迭代完成。因此上面基本上就是遞歸,找了一些圖片有助於你們理解以下圖