OKHttp原始碼學習——快取篇
阿新 • • 發佈:2018-11-12
OKHttp真實呼叫請求的類是RealCall
Dispatcher該類是作為請求分發
//非同步請求最多的請求個數 private int maxRequests = 64; //同一個host最多非同步請求的個數 private int maxRequestsPerHost = 5; //執行緒池 private ExecutorService executorService; //即將要請求的 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //正在請求的 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //同步請求 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
非同步請求時如果最多非同步請求超過64,並且同一主機請求個數超過了5就,放入到readyAsyncCalls
中
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
當網路請求完畢之後會呼叫finish方法,從readyAsyncCalls
中把準備請求的拿出來
synchronized void finished(AsyncCall call) { //將同步請求移除 if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!"); //將readyAsyncCalls中等待的realCall放入執行緒池中 promoteCalls(); } private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
同步請求時直接將RealCall放入到runningSyncCalls
中
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
攔截器
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
ApplicationInterceptorChain這是一個攔截器,使用者可以自己定義攔截器進行攔截
如果沒有攔截器,那麼就可以進行網路請求了,在getResponse
方法中例項化了HttpEngine類
傳送請求sendRequest
networkRequest
:只是設定了一些請求頭的操作
private Request networkRequest(Request request) throws IOException {
Request.Builder result = request.newBuilder();
if (request.header("Host") == null) {
result.header("Host", hostHeader(request.url(), false));
}
if (request.header("Connection") == null) {
result.header("Connection", "Keep-Alive");
}
if (request.header("Accept-Encoding") == null) {
transparentGzip = true;
result.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = client.cookieJar().loadForRequest(request.url());
if (!cookies.isEmpty()) {
result.header("Cookie", cookieHeader(cookies));
}
if (request.header("User-Agent") == null) {
result.header("User-Agent", Version.userAgent());
}
return result.build();
}
下面是快取的策略,重點啊~
如果你的程式碼設定
File cacheFile = new File(context.getExternalCacheDir().toString(),"cache");
//快取大小為10M
int cacheSize = 10 * 1024 * 1024;
//建立快取物件
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(mConnectTimeout, TimeUnit.SECONDS)
.readTimeout(mReadTimeOut, TimeUnit.SECONDS)
.writeTimeout(mWriteTimeOut, TimeUnit.SECONDS)
.cache(cache);
那麼將會在訪問網路之後
if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
將響應體寫到Cache裡面,採用LRU演算法,Key是一個32位長度的MD5值,Value是檔案封裝的物件
當put存入之後,你下次responseCache.get
就是上一次的響應
InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
但是光拿到上一次的響應頭還不算完,還得進行一些判斷,目的是判斷是否需要重新進行網路請求,因為可能響應頭的資源已經過期,超出了規定的時間,就不能在用了,要重新進行網路請求
public Factory(long nowMillis, Request request, Response cacheResponse) {
//這個是當前系統的時間
this.nowMillis = nowMillis;
//請求
this.request = request;
//快取中的響應
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
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)) {
//首部欄位Age能告知客戶端,源伺服器在多久前建立了響應,欄位值的單位為秒。
//若建立該響應的伺服器是快取伺服器,Age值是指快取後的響應再次發起認證到認證
//完成的時間值
ageSeconds = HeaderParser.parseSeconds(value, -1);
} else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
//傳送請求的本地時間。
sentRequestMillis = Long.parseLong(value);
} else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
//收到響應的本地時間。
receivedResponseMillis = Long.parseLong(value);
}
}
}
}
只是對響應頭進行了一些抽取,接下來
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
方法,快取策略的重點
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
//如果響應頭是null,那麼就得請求網路了
return new CacheStrategy(request, null);
}
//如果缺少必要的握手,則刪除快取的響應,也得去請求網路
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//主要是對各種狀態進行檢查
//這裡面注意下這樣程式碼
//!response.cacheControl().noStore() && !request.cacheControl().noStore();
//no-store這個響應欄位含義:簡單來說就是不能有快取,應用於機密檔案
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//請求物件獲取cacheControl物件,Request可以通過cacheControl方法進行設定
CacheControl requestCaching = request.cacheControl();
//requestCaching.noCache()是否需要快取
//hasConditions(request)
//request.header("If-Modified-Since") != null || request.header("If-None-Match") != null
//If-Modified-Since和If-None-Match你可以理解成資料庫的條件語句
//你想啊,有條件,證明每次請求都會不一樣,那麼當然需要重新請求網路了
//實在好奇就自己百度下吧
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//該方法裡面有這樣一句receivedAge + responseDuration + residentDuration
//就是現在的我距離上次傳送請求的時間
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;
//我在自己的程式碼中加入了攔截器
/* class CacheInterceptor implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Response originResponse = chain.proceed(chain.request());
//設定快取時間為60秒,並移除了pragma訊息頭,移除它的原因是因為pragma也是控制快取的一個訊息頭屬性
return originResponse.newBuilder().removeHeader("pragma")
.header("Cache-Control","max-age=60").build();
}
}
*/
//這句話就是得到我設定快取過期時間
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) {
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\"");
}
return new CacheStrategy(null, builder.build());
}
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();
return hasConditions(conditionalRequest)
? new CacheStrategy(conditionalRequest, cacheResponse)
: new CacheStrategy(conditionalRequest, null);
}
以上就是快取的策略
//假如你現在沒有快取,並且你還設定了CacheControl.FORCE_CACHE這個引數,那麼你就會收
//到504的異常
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
回到上面接著說
if (responseCache != null) {
//記錄當前請求是網路發起還是快取發起
responseCache.trackResponse(cacheStrategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//504錯誤
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
}
//如果networkRequest為null那麼就可以走快取了,這種情況在前面講過
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
}