來吧,今天說說經常使用的網絡框架OKHttp,也是如今Android所用的原生網絡框架(Android 4.4
開始,HttpURLConnection
的底層實現被Google
改爲了OkHttp
),GOGOGO!java
OKHTTP
的攔截器是把全部的攔截器放到一個list裏,而後每次依次執行攔截器,而且在每一個攔截器分紅三部分:web
proceed
方法把請求交給下一個攔截器這樣依次下去就造成了一個鏈式調用,看看源碼,具體有哪些攔截器:設計模式
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); }
根據源碼可知,一共七個攔截器:緩存
addInterceptor(Interceptor)
,這是由開發者設置的,會按照開發者的要求,在全部的攔截器處理以前進行最先的攔截處理,好比一些公共參數,Header均可以在這裏添加。RetryAndFollowUpInterceptor
,這裏會對鏈接作一些初始化工做,以及請求失敗的充實工做,重定向的後續請求工做。跟他的名字同樣,就是作重試工做還有一些鏈接跟蹤工做。BridgeInterceptor
,這裏會爲用戶構建一個可以進行網絡訪問的請求,同時後續工做將網絡請求回來的響應Response轉化爲用戶可用的Response,好比添加文件類型,content-length計算添加,gzip解包。CacheInterceptor
,這裏主要是處理cache相關處理,會根據OkHttpClient對象的配置以及緩存策略對請求值進行緩存,並且若是本地有了可⽤的Cache,就能夠在沒有網絡交互的狀況下就返回緩存結果。ConnectInterceptor
,這裏主要就是負責創建鏈接了,會創建TCP鏈接或者TLS鏈接,以及負責編碼解碼的HttpCodecnetworkInterceptors
,這裏也是開發者本身設置的,因此本質上和第一個攔截器差很少,可是因爲位置不一樣,因此用處也不一樣。這個位置添加的攔截器能夠看到請求和響應的數據了,因此能夠作一些網絡調試。CallServerInterceptor
,這裏就是進行網絡數據的請求和響應了,也就是實際的網絡I/O操做,經過socket讀寫數據。頻繁的進行創建Sokcet
鏈接和斷開Socket
是很是消耗網絡資源和浪費時間的,因此HTTP中的keepalive
鏈接對於下降延遲和提高速度有很是重要的做用。keepalive機制
是什麼呢?也就是能夠在一次TCP鏈接中能夠持續發送多份數據而不會斷開鏈接。因此鏈接的屢次使用,也就是複用就變得格外重要了,而複用鏈接就須要對鏈接進行管理,因而就有了鏈接池的概念。websocket
OkHttp中使用ConectionPool
實現鏈接池,默認支持5個併發KeepAlive
,默認鏈路生命爲5分鐘。cookie
1)首先,ConectionPool
中維護了一個雙端隊列Deque
,也就是兩端均可以進出的隊列,用來存儲鏈接。
2)而後在ConnectInterceptor
,也就是負責創建鏈接的攔截器中,首先會找可用鏈接,也就是從鏈接池中去獲取鏈接,具體的就是會調用到ConectionPool
的get方法。網絡
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; }
也就是遍歷了雙端隊列,若是鏈接有效,就會調用acquire方法計數並返回這個鏈接。併發
3)若是沒找到可用鏈接,就會建立新鏈接,並會把這個創建的鏈接加入到雙端隊列中,同時開始運行線程池中的線程,其實就是調用了ConectionPool
的put方法。框架
public final class ConnectionPool { void put(RealConnection connection) { if (!cleanupRunning) { //沒有鏈接的時候調用 cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection); } }
3)其實這個線程池中只有一個線程,是用來清理鏈接的,也就是上述的cleanupRunnable
socket
private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { while (true) { //執行清理,並返回下次須要清理的時間。 long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { //在timeout時間內釋放鎖 try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } } };
這個runnable
會不停的調用cleanup方法清理線程池,並返回下一次清理的時間間隔,而後進入wait等待。
怎麼清理的呢?看看源碼:
long cleanup(long now) { synchronized (this) { //遍歷鏈接 for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); //檢查鏈接是不是空閒狀態, //不是,則inUseConnectionCount + 1 //是 ,則idleConnectionCount + 1 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++; continue; } idleConnectionCount++; // If the connection is ready to be evicted, we're done. long idleDurationNs = now - connection.idleAtNanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; } } //若是超過keepAliveDurationNs或maxIdleConnections, //從雙端隊列connections中移除 if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { connections.remove(longestIdleConnection); } else if (idleConnectionCount > 0) { //若是空閒鏈接次數>0,返回將要到期的時間 // A connection will be ready to evict soon. return keepAliveDurationNs - longestIdleDurationNs; } else if (inUseConnectionCount > 0) { // 鏈接依然在使用中,返回保持鏈接的週期5分鐘 return keepAliveDurationNs; } else { // No connections, idle or in use. cleanupRunning = false; return -1; } } closeQuietly(longestIdleConnection.socket()); // Cleanup again immediately. return 0; }
也就是當若是空閒鏈接maxIdleConnections
超過5個或者keepalive時間大於5分鐘,則將該鏈接清理掉。
4)這裏有個問題,怎樣屬於空閒鏈接?
其實就是有關剛纔說到的一個方法acquire
計數方法:
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)); }
在RealConnection
中,有一個StreamAllocation
虛引用列表allocations
。每建立一個鏈接,就會把鏈接對應的StreamAllocationReference
添加進該列表中,若是鏈接關閉之後就將該對象移除。
5)鏈接池的工做就這麼多,並不負責,主要就是管理雙端隊列Deque<RealConnection>
,能夠用的鏈接就直接用,而後按期清理鏈接,同時經過對StreamAllocation
的引用計數實現自動回收。
這個不要太明顯,能夠說是okhttp的精髓所在了,主要體現就是攔截器的使用,具體代碼能夠看看上述的攔截器介紹。
在Okhttp中,建造者模式也是用的挺多的,主要用處是將對象的建立與表示相分離,用Builder組裝各項配置。
好比Request:
public class Request { public static class Builder { @Nullable HttpUrl url; String method; Headers.Builder headers; @Nullable RequestBody body; public Request build() { return new Request(this); } } }
工廠模式和建造者模式相似,區別就在於工廠模式側重點在於對象的生成過程,而建造者模式主要是側重對象的各個參數配置。
例子有CacheInterceptor攔截器中又個CacheStrategy對象:
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); 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); } } } }
以前我寫過一篇文章,是關於Okhttp中websocket的使用,因爲webSocket屬於長鏈接,因此須要進行監聽,這裏是用到了觀察者模式:
final WebSocketListener listener; @Override public void onReadMessage(String text) throws IOException { listener.onMessage(this, text); }
這個就不舉例了,每一個項目都會有
有一塊兒學習的小夥伴能夠關注下❤️個人公衆號——碼上積木,天天剖析一個知識點,咱們一塊兒積累知識。