天天看點

Volley 資料緩存分析

  • 緩存使用前提
  • Volley官方提供的流程圖

1.緩存使用前提:

伺服器必須支援,緩存,配置Cache-Control等頭資訊,因為Volley需要從這些頭資訊判斷緩存是否已經過期。以及一些其他Header的參數

​​https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32​​ http協定文檔

預設問題:

1.緩存機制

2.是如何實作的-技術方案。

3.緩存位置(讀/寫方式)。

4.緩存更新方式!緩存多長時間

2 .Volley官方提供的流程圖

Volley 資料緩存分析
(圖1) volley緩存機制      

Volley緩存實作,需要服務端進行配合,根據request—-response 中互動的 http中具體Header 字段。例如下圖

Volley 資料緩存分析

(圖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進行具體的緩存邏輯

Volley 資料緩存分析
(圖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;
}