在講解CacheInterceptor以前,咱們先了解一下OkHttp的緩存機制,主要是Cache這個類,演示下如何使用OkHttp的緩存:java
private void cacheOkHttpRequest(){
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.cache(new Cache(new File(Environment.getExternalStorageDirectory()+ "/okttp_caches"),24*1024*1024))
.build();
Request request = new Request
.Builder()
.url("http://www.ifeng.com")
.build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
Log.e(TAG,"response: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
上述代碼中在OkHttpClient的Builder中配置了cache,傳入緩存目錄的File對象以及緩存最大容量(單位字節),這裏請求了鳳凰網,請求成功後,OkHttp會將請求的相關數據進行緩存,當下次請求沒法連接到網絡的時候,它會讀取緩存並將數據返回。緩存
根據緩存的流程,咱們能夠猜想到Cache類會有保存和讀取緩存的方法,咱們經過查看Cache類的源碼,果真發現了put()和get()方法,分別用於保存緩存和讀取緩存:bash
@Nullable CacheRequest put(Response response) {
//獲取請求方法
String requestMethod = response.request().method();
//1、傳入請求方法,判斷是否要緩存
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//2、非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;
}
//3、建立緩存實體
Entry entry = new Entry(response);
//4、使用 DiskLruCache緩存策略
DiskLruCache.Editor editor = null;
try {
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;
}
}
複製代碼
在分析這個方法以前,須要先知道的是,OkHttp默認只會對get請求進行緩存,post請求是不會進行緩存,這也是有道理的,由於get請求的數據通常是比較持久的,而post通常是交互操做,沒太大意義進行緩存;固然,也能夠本身實現post請求的緩存操做,這個須要根據本身的項目需求來。網絡
回到put()方法的分析,上述代碼中,先是對請求方法進行了判斷,是否須要緩存下來。框架
註釋一處,調用了HttpMethod的invalidatesCache():ide
public final class HttpMethod {
public static boolean invalidatesCache(String method) {
return method.equals("POST")
|| method.equals("PATCH")
|| method.equals("PUT")
|| method.equals("DELETE")
|| method.equals("MOVE"); // WebDAV
}
...
}
複製代碼
若是請求方法是POST、PATCH、PUT、DELETE以及MOVE中一個,就會將當前請求的緩存移除。源碼分析
接着判斷是不是非GET請求,若是是則不進行緩存。post
最後,開始進行緩存的操做,註釋三處建立了一個Entry類,傳入Response對象,查看其構造方法:ui
Entry(Response response) {
this.url = response.request().url().toString();
this.varyHeaders = HttpHeaders.varyHeaders(response);
this.requestMethod = response.request().method();
this.protocol = response.protocol();
this.code = response.code();
this.message = response.message();
this.responseHeaders = response.headers();
this.handshake = response.handshake();
this.sentRequestMillis = response.sentRequestAtMillis();
this.receivedResponseMillis = response.receivedResponseAtMillis();
}
複製代碼
Entry會將請求url、頭部、請求方式、協議、響應碼等一系列參數保存下來。this
註釋四處,建立緩存策略,OkHttp的緩存策略使用的是DiskLruCache,DiskLruCache是用於磁盤緩存的一套解決框架,OkHttp對DiskLruCache稍微作了點修改,而且OkHttp內部維護着清理內存的線程池,經過這個線程池完成緩存的自動清理和管理工做,本篇不作過多介紹。
獲取到DiskLruCache的Editor對象後,經過它的edit方法建立緩存文件,傳入緩存的文件名,文件名的生成是經過key()方法將請求url進行MD5加密並獲取它的十六進制表示形式。
接着執行Entry對象的writeTo()方法並傳入Editor對象,writeTo()方法是將緩存信息存儲在本地:
public void writeTo(DiskLruCache.Editor editor) throws IOException {
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
//緩存請求的url
sink.writeUtf8(url)
.writeByte('\n');
//緩存請求方法
sink.writeUtf8(requestMethod)
.writeByte('\n');
//緩存請求頭部大小
sink.writeDecimalLong(varyHeaders.size())
.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();
}
複製代碼
上述代碼中並無對響應主體的body進行緩存,在調用Entry的writeTo()方法以後,返回了一個CacheRequestImpl對象:
private final class CacheRequestImpl implements CacheRequest {
private final DiskLruCache.Editor editor;
private Sink cacheOut;
private Sink body;
boolean done;
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();
}
};
}
複製代碼
這裏咱們能夠看到Repsonse的body是在這裏被寫入緩存的,CacheRequestImpl實現CacheRequest接口,用於暴露給緩存攔截器,緩存攔截器能夠經過這個類來寫入或更新緩存的數據。
接下來分析獲取緩存的get()方法:
@Nullable Response get(Request request) {
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
Response response = entry.response(snapshot);
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
複製代碼
顯示經過請求的url獲取到緩存對應的文件名key,而後建立一個DiskLruCache.Snapshot緩存快照對象,根據key獲取對應的緩存快照,若是獲取到的爲null,則說明沒有找到緩存,直接返回null;若是找到對應的緩存快照,則根據快照生成Entry對象,再經過調用Entry的response()方法獲取到緩存的Response對象。
response()方法:
public Response response(DiskLruCache.Snapshot snapshot) {
String contentType = responseHeaders.get("Content-Type");
String contentLength = responseHeaders.get("Content-Length");
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();
}
}
複製代碼
這裏實際上根據緩存的數據建立了Request請求,並返回生成的Response對象。
獲取到緩存的Response對象後,get()方法裏將其與傳入的request進行比對,若是兩者不是成對的,則關閉流且返回null,若是是的話,則返回由緩存數據生成的Response對象。
關於OkHttpClient的緩存機制,這裏已經初步介紹完了,本篇主要是爲介紹CacheInterceptor作鋪墊,下一篇咱們將瞭解第三個攔截器CacheInterceptor緩存攔截器,感興趣的朋友能夠繼續閱讀: