OkHttp源碼解讀總結(九)—>okhttp的緩存策略
標籤(空格分隔): OkHttp源碼 學習筆記
前言
- 以下的相關知識總結是通過慕課網的相關學習和自己的相關看法,如果有需要的可以去查看一下慕課網的相關教學,感覺還可以。
爲什麼要使用緩存
- 一個優點就是讓客戶端下一次的網絡請求節省更多的時間,更快的展示數據
如何開啓和使用緩存功能的呢?
new OkHttpClient.Builder()
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.readTimeout(10000, TimeUnit.MILLISECONDS)
.writeTimeout(10000, TimeUnit.MICROSECONDS)
//直接這樣使用 配置Cache File對象 緩存大小
.cache(new Cache(new File("cache"),24*1024*1024))
.build();
Cache.put()方法源碼
@Nullable CacheRequest put(Response response) {
//獲取到請求方法
String requestMethod = response.request().method();
//判斷該請求方法是否符合緩存
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
//移除這個請求
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//非get方法不需要緩存
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}
//
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
//當經過上述的邏輯之後 到這一步 證明是可以緩存的 //那麼就通過傳入的response響應 創建Entry對象 就是我們需要寫入緩存的 把需要的一些屬性 方法 地址 頭部 信息 code 等
Entry entry = new Entry(response);
//可以看到這裏採用的是DiskLruCache緩存策略
//http://blog.csdn.net/guolin_blog/article/details/28863651 (Android DiskLruCache完全解析,硬盤緩存的最佳方案)
DiskLruCache.Editor editor = null;
try {
//把這個請求url(經過轉換MD5加密然後轉十六進制)作爲key
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
//真正的開始緩存
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
entry.writeTo()方法
public void writeTo(DiskLruCache.Editor editor) throws IOException {
//使用的是okio
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
//緩存的請求地址
sink.writeUtf8(url)
.writeByte('\n');
//緩存的請求方法
sink.writeUtf8(requestMethod)
.writeByte('\n');
sink.writeDecimalLong(varyHeaders.size())
.writeByte('\n');
//對header頭部進行遍歷
for (int i = 0, size = varyHeaders.size(); i < size; i++) {
//名字
sink.writeUtf8(varyHeaders.name(i))
.writeUtf8(": ")
//value
.writeUtf8(varyHeaders.value(i))
.writeByte('\n');
}
//緩存http的響應行StatusLine
sink.writeUtf8(new StatusLine(protocol, code, message).toString())
.writeByte('\n');
//響應手部
sink.writeDecimalLong(responseHeaders.size() + 2)
.writeByte('\n');
//頭部
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
sink.writeUtf8(responseHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(responseHeaders.value(i))
.writeByte('\n');
}
//發送時間
sink.writeUtf8(SENT_MILLIS)
.writeUtf8(": ")
.writeDecimalLong(sentRequestMillis)
.writeByte('\n');
//響應時間
sink.writeUtf8(RECEIVED_MILLIS)
.writeUtf8(": ")
.writeDecimalLong(receivedResponseMillis)
.writeByte('\n');
//判斷是否是https請求
if (isHttps()) {
//相應的握手 等緩存
sink.writeByte('\n');
sink.writeUtf8(handshake.cipherSuite().javaName())
.writeByte('\n');
writeCertList(sink, handshake.peerCertificates());
writeCertList(sink, handshake.localCertificates());
sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\n');
}
//關閉
sink.close();
}
new CacheRequestImpl(editor)
CacheRequestImpl實現了CacheRequest接口,主要暴漏給後面需要介紹的CacheInterceptor攔截器 緩存攔截器可以直接根據這個CacheRequest實現類 CacheRequestImpl直接寫入和更新緩存數據
CacheRequestImpl(final DiskLruCache.Editor editor) {
this.editor = editor;
//響應主體
this.cacheOut = editor.newSink(ENTRY_BODY);
//
this.body = new ForwardingSink(cacheOut) {
@Override public void close() throws IOException {
synchronized (Cache.this) {
if (done) {
return;
}
done = true;
writeSuccessCount++;
}
super.close();
editor.commit();
}
};
}
put()總結
- 1、首先判斷緩存請求方法是否是get方法
- 2、如果符合緩存策略,那麼創建一個Entry對象—>用於我們所要包裝的緩存信息
- 3、最終使用DiskLruCache進行緩存
- 4、最後通過返回一個CacheRequestImpl對象,這個對象主要用於CacheInterceptor攔截器服務的。
Cache.get()方法源碼
主要從緩存中讀取緩存的response
@Nullable Response get(Request request) {
//通過傳入的Request對象獲取請求地址 並通過key方法獲取緩存的key
String key = key(request.url());
//緩存快照 記錄緩存在特定時刻緩存的內容
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
//通過key值獲取value
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
//通過獲取到的這個snapshot獲取到Source 最終創建entry對象
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//通過之前創建好的entry實例獲取到我們緩存的response對象
Response response = entry.response(snapshot);
//響應和請求是否一一對應 如果不對應 直接返回null
if (!entry.matches(request, response)) {
//關閉流
Util.closeQuietly(response.body());
return null;
}
//最終返回我們緩存的response實例
return response;
}
entry.response(snapshot)方法
public Response response(DiskLruCache.Snapshot snapshot) {
String contentType = responseHeaders.get("Content-Type");
String contentLength = responseHeaders.get("Content-Length");
//創建request
Request cacheRequest = new Request.Builder()
.url(url)
.method(requestMethod, null)
.headers(varyHeaders)
.build();
return new Response.Builder()
.request(cacheRequest)
.protocol(protocol)
.code(code)
.message(message)
.headers(responseHeaders)
//響應體的讀取
.body(new CacheResponseBody(snapshot, contentType, contentLength))
.handshake(handshake)
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(receivedResponseMillis)
.build();
}
get()方法總結
- 1、根據請求url獲取key(MD5解密)
- 2、通過key值獲取 DiskLruCache.Snapshot(緩存快照)
- 3、通過獲取到的這個snapshot獲取到Source 最終創建entry對象
- 4、通過entry和snapshot(緩存快照)獲取緩存的response並返回
這裏所總結的Cache的get和put的相關知識,主要是爲了爲CacheInterceptor這個緩存攔截器做鋪墊。