- 緩存使用前提
- Volley官方提供的流程圖
1.緩存使用前提:
伺服器必須支援,緩存,配置Cache-Control等頭資訊,因為Volley需要從這些頭資訊判斷緩存是否已經過期。以及一些其他Header的參數
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32 http協定文檔
預設問題:
1.緩存機制
2.是如何實作的-技術方案。
3.緩存位置(讀/寫方式)。
4.緩存更新方式!緩存多長時間
2 .Volley官方提供的流程圖
(圖1) volley緩存機制
Volley緩存實作,需要服務端進行配合,根據request—-response 中互動的 http中具體Header 字段。例如下圖
(圖2) Http請求的頭
當然不止這一種:blog
HTTP頭的Expires與Cache-control
request時用到:
| "no-cache"
| "no-store"
| "max-age" "=" delta-seconds
| "max-stale" [ "=" delta-seconds ]
| "min-fresh" "=" delta-seconds
| "no-transform"
| "only-if-cached"
| "cache-extension"
response時用到:
| "public"
| "private" [ "=" <"> field-name <"> ]
| "no-cache" [ "=" <"> field-name <"> ]
| "no-store"
| "no-transform"
| "must-revalidate"
| "proxy-revalidate"
| "max-age" "=" delta-seconds
| "s-maxage" "=" delta-seconds
| "cache-extension"
具體細節下面進行分析
3.具體緩存存放的位置,是在首次初始化傳入進去的。context.getCacheDir()。當然可以随意修改存儲位置,做好相應的容錯即可。
緩存字段主要在HttpHeaderParser 中進行定義,并進行包裝内部類Entity結構
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
當然這些字段都是可以在Http 協定中Header 找到對應的字段。緩存内容也是這些字段
Cache 接口的實作類有兩個 NoCache 和 DiskBasedCache,前一個是空實作。DiskBasedCache進行具體的緩存邏輯
(圖3) Cache接口結構
在網絡請求成功地方進行緩存,key= request url ,entry為具體response相應封裝後的實體
/**
* Puts the entry with the specified key into the cache.
* 在網絡請求成功中進行緩存
*/
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
CacheHeader e = new CacheHeader(key, entry);
boolean success = e.writeHeader(fos);
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
1.首先進行判斷緩存内容是否超過限制的最大容量,超過了就需要清除排在隊列最前面的緩存資料
2.根據一些規則生成檔案名,并以檔案的形式進行傳回
3.将key,entity 包裝成DiskBasedCache 内部類 CacheHeader結構
4.通過OutputStream 序列化到本地檔案中
下面是寫入的方式
/**
* Writes the contents of this CacheHeader to the specified OutputStream.
*/
public boolean writeHeader(OutputStream os) {
try {
writeInt(os, CACHE_MAGIC);
writeString(os, key);
writeString(os, etag == null ? "" : etag);
writeLong(os, serverDate);
writeLong(os, lastModified);
writeLong(os, ttl);
writeLong(os, softTtl);
writeStringStringMap(responseHeaders, os);
os.flush();
return true;
} catch (IOException e) {
VolleyLog.d("%s", e.toString());
return false;
}
}
緩存的讀取同樣根據 key去拿對應的Entity内容。下面是讀取緩存的源碼
/**
* Returns the cache entry with the specified key if it exists, null otherwise.
*/
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = null;
try {
cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
1.根據生成檔案名的方式,拿到存入檔案對應的路徑,
2.BufferedInputStream 進行讀流,然後逆序轉換為Cache.Entity
下面是對緩存檔案的源碼
/**
* Reads the header off of an InputStream and returns a CacheHeader object.
*
* @param is The InputStream to read from.
* @throws IOException
*/
public static CacheHeader readHeader(InputStream is) throws IOException {
CacheHeader entry = new CacheHeader();
int magic = readInt(is);
if (magic != CACHE_MAGIC) {
// don't bother deleting, it'll get pruned eventually
throw new IOException();
}
entry.key = readString(is);
entry.etag = readString(is);
if (entry.etag.equals("")) {
entry.etag = null;
}
entry.serverDate = readLong(is);
entry.lastModified = readLong(is);
entry.ttl = readLong(is);
entry.softTtl = readLong(is);
entry.responseHeaders = readStringStringMap(is);
return entry;
}
何時需要更新緩存,以及如何進行緩存清理。
/* True if a refresh is needed from the original data source. /
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
根據服務端傳回值,進行判斷,是否需要重新整理緩存。設定參數為true
/ Mark the response as intermediate.
response.intermediate = true;
Request時可以動态設定是否進行響應緩存
public final Request<?> setShouldCache(boolean shouldCache) {
mShouldCache = shouldCache;
return this;
}