來吧肴颊,今天說說常用的網絡框架OKHttp对室,也是現在Android所用的原生網絡框架(Android 4.4
開始结窘,HttpURLConnection
的底層實現被Google
改成了OkHttp
)各薇,GOGOGO坟瓢!
- OKHttp有哪些攔截器操禀,分別起什么作用
- OkHttp怎么實現連接池
- OkHttp里面用到了什么設計模式
OKHttp有哪些攔截器褂策,分別起什么作用
OKHTTP
的攔截器是把所有的攔截器放到一個list里,然后每次依次執(zhí)行攔截器,并且在每個攔截器分成三部分:
- 預處理攔截器內容
- 通過
proceed
方法把請求交給下一個攔截器 - 下一個攔截器處理完成并返回斤寂,后續(xù)處理工作耿焊。
這樣依次下去就形成了一個鏈式調用,看看源碼遍搞,具體有哪些攔截器:
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)
,這是由開發(fā)者設置的溪猿,會按照開發(fā)者的要求钩杰,在所有的攔截器處理之前進行最早的攔截處理,比如一些公共參數诊县,Header都可以在這里添加讲弄。 -
RetryAndFollowUpInterceptor
,這里會對連接做一些初始化工作翎冲,以及請求失敗的充實工作垂睬,重定向的后續(xù)請求工作。跟他的名字一樣抗悍,就是做重試工作還有一些連接跟蹤工作驹饺。 -
BridgeInterceptor
,這里會為用戶構建一個能夠進行網絡訪問的請求缴渊,同時后續(xù)工作將網絡請求回來的響應Response轉化為用戶可用的Response赏壹,比如添加文件類型,content-length計算添加衔沼,gzip解包蝌借。 -
CacheInterceptor
,這里主要是處理cache相關處理指蚁,會根據OkHttpClient對象的配置以及緩存策略對請求值進行緩存菩佑,而且如果本地有了可?的Cache,就可以在沒有網絡交互的情況下就返回緩存結果凝化。 -
ConnectInterceptor
稍坯,這里主要就是負責建立連接了,會建立TCP連接或者TLS連接搓劫,以及負責編碼解碼的HttpCodec -
networkInterceptors
瞧哟,這里也是開發(fā)者自己設置的,所以本質上和第一個攔截器差不多枪向,但是由于位置不同勤揩,所以用處也不同。這個位置添加的攔截器可以看到請求和響應的數據了秘蛔,所以可以做一些網絡調試陨亡。 -
CallServerInterceptor
傍衡,這里就是進行網絡數據的請求和響應了,也就是實際的網絡I/O操作数苫,通過socket讀寫數據聪舒。
OkHttp怎么實現連接池
- 為什么需要連接池?
頻繁的進行建立Sokcet
連接和斷開Socket
是非常消耗網絡資源和浪費時間的虐急,所以HTTP中的keepalive
連接對于降低延遲和提升速度有非常重要的作用箱残。keepalive機制
是什么呢?也就是可以在一次TCP連接中可以持續(xù)發(fā)送多份數據而不會斷開連接止吁。所以連接的多次使用被辑,也就是復用就變得格外重要了,而復用連接就需要對連接進行管理敬惦,于是就有了連接池的概念盼理。
OkHttp中使用ConectionPool
實現連接池,默認支持5個并發(fā)KeepAlive
俄删,默認鏈路生命為5分鐘宏怔。
- 怎么實現的?
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)如果沒找到可用連接对供,就會創(chuàng)建新連接位他,并會把這個建立的連接加入到雙端隊列中,同時開始運行線程池中的線程犁钟,其實就是調用了ConectionPool
的put方法棱诱。
public final class ConnectionPool {
void put(RealConnection connection) {
if (!cleanupRunning) {
//沒有連接的時候調用
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
}
3)其實這個線程池中只有一個線程泼橘,是用來清理連接的涝动,也就是上述的cleanupRunnable
private final Runnable cleanupRunnable = new Runnable() {
@Override
public void run() {
while (true) {
//執(zhí)行清理,并返回下次需要清理的時間炬灭。
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();
//檢查連接是否是空閑狀態(tài)厦凤,
//不是,則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
。每創(chuàng)建一個連接馍惹,就會把連接對應的StreamAllocationReference
添加進該列表中躺率,如果連接關閉以后就將該對象移除。
5)連接池的工作就這么多万矾,并不復雜悼吱,主要就是管理雙端隊列Deque<RealConnection>
,可以用的連接就直接用勤众,然后定期清理連接舆绎,同時通過對StreamAllocation
的引用計數實現自動回收。
OkHttp里面用到了什么設計模式
- 責任鏈模式
這個不要太明顯们颜,可以說是okhttp的精髓所在了吕朵,主要體現就是攔截器的使用,具體代碼可以看看上述的攔截器介紹窥突。
- 建造者模式
在Okhttp中努溃,建造者模式也是用的挺多的,主要用處是將對象的創(chuàng)建與表示相分離阻问,用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);
}
}
}
- 工廠模式
工廠模式和建造者模式類似,區(qū)別就在于工廠模式側重點在于對象的生成過程称近,而建造者模式主要是側重對象的各個參數配置第队。
例子有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屬于長連接凳谦,所以需要進行監(jiān)聽,這里是用到了觀察者模式:
final WebSocketListener listener;
@Override public void onReadMessage(String text) throws IOException {
listener.onMessage(this, text);
}
- 單例模式
這個就不舉例了衡未,每個項目都會有
- 另外有的博客還說到了策略模式尸执,門面模式等家凯,這些大家可以網上搜搜,畢竟每個人的想法看法都會不同如失,細心找找可能就會發(fā)現绊诲。
拜拜
有一起學習的小伙伴可以關注下??我的公眾號——碼上積木,每天剖析一個知識點褪贵,我們一起積累知識掂之。