本文僅為學(xué)習(xí)筆記辟宗;不是原創(chuàng)文章碗硬;
一:OkHttp連接池復(fù)用
1.1 持久連接
持久連接(HTTP keep-alive):允許HTTP設(shè)備在事務(wù)處理結(jié)束之后將TCP連接保持在打開狀態(tài),以便未來的HTTP請求重用現(xiàn)在的連接绵咱;在事務(wù)處理結(jié)束之后仍然保持在打開狀態(tài)的TCP連接叫做持久連接;非持久連接會在事件處理結(jié)束之后關(guān)閉碘饼,持久連接會在不同的事務(wù)之間保持打開狀態(tài);
持久連接的優(yōu)點:降低時延和連接建立的開銷;將連接保持在已經(jīng)調(diào)諧的狀態(tài)艾恼;減少了打開連接的潛在數(shù)量住涉;
持久連接的缺點:如果存在大量空閑的keepalive connections(我們可以稱作僵尸連接或者泄漏連接),其它客戶端們的正常連接速度也會受到影響
1.2 連接池的使用與分析
Call: 對HTTP的Request的封裝
Connection: 對socket連接的包裝钠绍;
**StreamAllocation: **表示Connection被引用的次數(shù)
**ConnectionPool: ** Socket連接池舆声,對連接緩存進行回收與管理
**Deque: ** Deque也就是雙端隊列,雙端隊列同時具有隊列和棧性質(zhì)柳爽;
1.3 Connection自動回收機制
ConnectionPool.java
ConnectionPool內(nèi)部用一個線程池來執(zhí)行連接池的自動回收和管理任務(wù)媳握,
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
private final int maxIdleConnections;//每個connection的空閑socket連接數(shù)目;
private final long keepAliveDurationNs;//每個空閑socket連接的keep-alive時長泻拦;
ConnectionPool()
keepAliveDurationNs默認為5分鐘毙芜;maxIdleConnections默認為5個空閑連接忽媒;
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
當用戶socket連接成功争拐,向連接池中put新的socket時,回收函數(shù)會被主動調(diào)用晦雨,線程池就會執(zhí)行cleanupRunnable;
ConnectionPool.put()
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
cleanupRunnable.java
通過一個無限循環(huán)來執(zhí)行cleanup()方法來執(zhí)行connection的連接自動回收架曹,并返回下一次回收的時間;
//Socket清理的Runnable闹瞧,每當put操作時绑雄,就會被調(diào)用
//put操作是在網(wǎng)絡(luò)線程
//Socket清理是在 ConnectionPool線程池中調(diào)用
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) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
ConnectionPool.Cleanup()
/**
* Performs maintenance on this pool, evicting the connection that has been idle the longest if
* either it has exceeded the keep alive limit or the idle connections limit.
*
* <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
* -1 if no further cleanups are required.
*/
//清理超過空閑連接次數(shù)和空閑時間限制的連接,返回下次執(zhí)行清理需要等待時長奥邮;如果不需要清理万牺,返回-1;
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
//遍歷Deque中所有的RealConnection洽腺,標記泄漏的連接
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
//如果連接在使用脚粟,繼續(xù)搜索需要清理的connection;
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
//空閑連接數(shù)目+1;
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
//連接已經(jīng)空閑的時間
//找到空閑時間最長的連接蘸朋;
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//如果最長空閑時間的連接所包含的socket空閑連接超過最大空閑連接限制或者超過最長空閑時間核无;那么此連接為待清理的連接
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
//清理連接
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
//返回剩余可空閑的時間
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
//返回 keepAlive時長;
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
//無連接藕坯;
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
ConnectionPool.pruneAndGetAllocationCount()
主要用來判斷一個連接是不是活躍的連接
/**
* Prunes any leaked allocations and then returns the number of remaining live allocations on
* {@code connection}. Allocations are leaked if the connection is tracking them but the
* application code has abandoned them. Leak detection is imprecise and relies on garbage
* collection.
*/
返回一個connection剩余的活躍的 allocation數(shù)量团南;
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i);
if (reference.get() != null) {
i++;
continue;
}
// We've discovered a leaked allocation. This is an application bug.
Internal.logger.warning("A connection to " + connection.route().address().url()
+ " was leaked. Did you forget to close a response body?");
//移除一個Allocation;
references.remove(i);
connection.noNewStreams = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
//如果這個 connection所有的allocation都沒有被引用,那么這個連接應(yīng)該馬上被清理炼彪,設(shè)置該connection的已經(jīng)空閑 了keepAliveDurationNs時間吐根;
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
return references.size();
}
}
1:遍歷RealConnection連接中的StreamAllocationList,它維護著一個弱應(yīng)用列表
2 :查看此StreamAllocation是否為空(它是在線程池的put/remove手動控制的)辐马,如果為空拷橘,說明已經(jīng)沒有代碼引用這個對象了,需要在List中刪除
3 :遍歷結(jié)束,如果List中維護的StreamAllocation刪空了膜楷,就返回0旭咽,表示這個連接已經(jīng)沒有代碼引用了,是泄漏的連接;否則返回非0的值赌厅,表示這個仍然被引用穷绵,是活躍的連接。
二:OkHttp緩存策略
OkHttp用CacheStrategy很好的實現(xiàn)了符合HTTP規(guī)范的HTTP緩存策略特愿;
CacheStrategy()構(gòu)造
networkRequest:網(wǎng)絡(luò)請求仲墨;
cacheResponse:緩存響應(yīng);
/** The request to send on the network, or null if this call doesn't use the network. */
public final Request networkRequest;
/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final Response cacheResponse;
private CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
isCacheable():根據(jù)返回的狀態(tài)碼揍障,主要用來判斷一個Reponse是否可以緩存目养;如果不能緩存,那么Request就需要走網(wǎng)絡(luò)請求毒嫡;不支持緩存部分內(nèi)容癌蚁;如果是302響應(yīng)(暫時性重定向,需要進一步判斷兜畸?)努释;如果有Reponse和Request都有noStore(),那么代表不能緩存咬摇;
/** Returns true if {@code response} can be stored to later serve another request. */
public static boolean isCacheable(Response response, Request request) {
// Always go to network for uncacheable response codes (RFC 7231 section 6.1),
// This implementation doesn't support caching partial content.
switch (response.code()) {
case HTTP_OK:
case HTTP_NOT_AUTHORITATIVE:
case HTTP_NO_CONTENT:
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_NOT_FOUND:
case HTTP_BAD_METHOD:
case HTTP_GONE:
case HTTP_REQ_TOO_LONG:
case HTTP_NOT_IMPLEMENTED:
case StatusLine.HTTP_PERM_REDIRECT:
// These codes can be cached unless headers forbid it.
break;
case HTTP_MOVED_TEMP:
case StatusLine.HTTP_TEMP_REDIRECT:
// These codes can only be cached with the right response headers.
// http://tools.ietf.org/html/rfc7234#section-3
// s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
if (response.header("Expires") != null
|| response.cacheControl().maxAgeSeconds() != -1
|| response.cacheControl().isPublic()
|| response.cacheControl().isPrivate()) {
break;
}
// Fall-through.
default:
// All other codes cannot be cached.
return false;
}
// A 'no-store' directive on request or response prevents the response from being cached.
return !response.cacheControl().noStore() && !request.cacheControl().noStore();
Factory.java:是CacheStrage的一個內(nèi)部類伐蒂;
final long nowMillis;
final Request request;
final Response cacheResponse;
/** The server's time when the cached response was served, if known. **
//服務(wù)器創(chuàng)建響應(yīng)的時間
private Date servedDate;
private String servedDateString;
//緩存文檔最后修改時間
/** The last modified date of the cached response, if known. */
private Date lastModified;
private String lastModifiedString;
/**
* The expiration date of the cached response, if known. If both this field and the max age are
* set, the max age is preferred.
*/
//緩存文檔過期時間
private Date expires;
/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
* first initiated.
*/
// 第一次發(fā)送請求的時間戳
private long sentRequestMillis;
//第一次接收到緩存響應(yīng)的時間戳
/**
* Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
* first received.
*/
private long receivedResponseMillis;
//實體標簽
/** Etag of the cached response. */
private String etag;
//緩存響應(yīng)的年齡
/** Age of the cached response. */
private int ageSeconds = -1;
Factory構(gòu)造函數(shù):根據(jù)緩存響應(yīng)來初始化各個參數(shù)值瓦堵;
public Factory(long nowMillis, Request request, Response cacheResponse) {
//當前時間
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
//請求發(fā)送時間存和;初始發(fā)送
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
//響應(yīng)產(chǎn)生時間
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)) {
//緩存響應(yīng)的年齡
ageSeconds = HeaderParser.parseSeconds(value, -1);
}
}
}
}
Factory.getCandidate():返回一個CacheStragey();
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// No cached response.
//響應(yīng)為空, 走網(wǎng)絡(luò)枣耀;
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
//是HTTPS請求且TLS握手失敗在扰,走網(wǎng)絡(luò)缕减;
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
//如果不能緩存,走網(wǎng)絡(luò)健田;
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//請求不允許緩存烛卧,或者是條件請求,走網(wǎng)絡(luò)
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//初始化緩存響應(yīng)的年齡和緩存新鮮時間
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;
CacheControl responseCaching = cacheResponse.cacheControl();
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
//緩存不新鮮妓局;添加相關(guān)響應(yīng)首部总放;
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
//緩存過期;添加相關(guān)響應(yīng)首部好爬;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
//分別構(gòu)造If-None-Match局雄;If-Modified-Since請求首部;
Request.Builder conditionalRequestBuilder = request.newBuilder();
if (etag != null) {
conditionalRequestBuilder.header("If-None-Match", etag);
} else if (lastModified != null) {
conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
} else if (servedDate != null) {
conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
}
Request conditionalRequest = conditionalRequestBuilder.build();
//如果允許條件請求存炮,則進行條件請求炬搭,驗證新鮮度蜈漓;不允許就發(fā)起新的網(wǎng)絡(luò)請求;
return hasConditions(conditionalRequest)
? new CacheStrategy(conditionalRequest, cacheResponse)
: new CacheStrategy(conditionalRequest, null);
}
Factory.computeFreshnessLifetime():計算緩存維持在新鮮(不過期)狀態(tài)還有多長
/** Returns the number of milliseconds that the response was
fresh for, starting from the served date. **/
private long computeFreshnessLifetime() {
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.maxAgeSeconds() != -1) {
//返回reponse的 max-age
return SECONDS.toMillis(responseCaching.maxAgeSeconds());
} else if (expires != null) {
//返回expires-servedDate;取差值宫盔;消除服務(wù)器時鐘偏差融虽;
long servedMillis = servedDate != null
? servedDate.getTime()
: receivedResponseMillis;
long delta = expires.getTime() - servedMillis;
return delta > 0 ? delta : 0;
} else if (lastModified != null
&& cacheResponse.request().url().query() == null) {
// As recommended by the HTTP RFC and implemented in Firefox, the
// max age of a document should be defaulted to 10% of the
// document's age at the time it was served. Default expiration
// dates aren't used for URIs containing a query.
long servedMillis = servedDate != null
? servedDate.getTime()
: sentRequestMillis;
//返回expires-lastModified;作為緩存能維持在新鮮狀態(tài)的時長灼芭;取差值有额;消除服務(wù)器時鐘偏差;
long delta = servedMillis - lastModified.getTime();
return delta > 0 ? (delta / 10) : 0;
}
return 0;
}
Factory:cacheResponseAge():返回 response的年齡彼绷;
/** Returns the current age of the response, in milliseconds.
The calculation is specified by RFC 2616, 13.2.3 Age Calculations. **/
private long cacheResponseAge() {
//客戶端初始接收某響應(yīng)時間-服務(wù)器響應(yīng)產(chǎn)生時間巍佑;
long apparentReceivedAge = servedDate != null
? Math.max(0, receivedResponseMillis - servedDate.getTime())
: 0;
long receivedAge = ageSeconds != -1
? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
: apparentReceivedAge;
//客戶端初始接收某響應(yīng)時間-客戶端初始發(fā)送某請求時間
long responseDuration = receivedResponseMillis - sentRequestMillis;
//當前時間-客戶端初始接收某響應(yīng)時間
long residentDuration = nowMillis - receivedResponseMillis;
當前時間-第一次發(fā)送請求時間+
return receivedAge + responseDuration + residentDuration;
}
okhttp緩存實現(xiàn)
LinkedHashMap;
文件
OkHttp通過對文件進行了多次封裝,實現(xiàn)了非常簡單的I/O操作
OkHttp通過對請求url進行md5實現(xiàn)了與文件的映射寄悯,實現(xiàn)寫入萤衰,刪除等操作
OkHttp內(nèi)部維護著清理線程池,實現(xiàn)對緩存文件的自動清理
okhttp任務(wù)調(diào)度
Dispacher
// 最大并發(fā)請求數(shù)為64
private int maxRequests = 64;
// 每個主機最大請求數(shù)為5
private int maxRequestsPerHost = 5;
//線程池
/** Executes calls. Created lazily. */
private ExecutorService executorService;
//緩存隊列
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在運行的任務(wù);包括已經(jīng)取消但是還沒結(jié)束的任務(wù)猜旬;
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
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;
}
當執(zhí)行任務(wù)脆栋,將任務(wù)加入任務(wù)隊列
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url("http://qq.com").get().build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
}
@Override public void onResponse(Call call, Response response) throws IOException {
}
});
enqueue: 添加任務(wù)實際上是入隊
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//如果滿足最大并發(fā)請求數(shù)為64, 每個主機最大請求數(shù)為5昔馋;
//添加正在運行的請求
runningAsyncCalls.add(call);
//線程池執(zhí)行請求
executorService().execute(call);
} else {
//添加到緩存隊列
readyAsyncCalls.add(call);
}