本文学习一下volley的缓存设计写法。
首先看下哪些类使用了cache。
Cache.java
public interface Cache {
public Entry get(String key);
public void put(String key, Entry entry);
public void initialize();
public void invalidate(String key, boolean fullExpire);
public void remove(String key);
public void clear();
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
DiskBasedCache是唯一的实现类。
DiskBasedCache.java
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return; //路径不存在则只需创建路径即可返回
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return; //路径存在 但不存在子目录即可返回
}
for (File file : files) {
BufferedInputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(file));
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry); //若路径下的子路径不为空 则将子路径处理成cache
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
private void putEntry(String key, CacheHeader entry) {
if (!mEntries.containsKey(key)) {
mTotalSize += entry.size;
} else {
CacheHeader oldEntry = mEntries.get(key);
mTotalSize += (entry.size - oldEntry.size);
}
mEntries.put(key, entry); //mEntries—>new LinkedHashMap<String,CacheHeader>(16,.75f,true);
//volley是这样做的 在内存中保留一个map 在硬盘中存储实际的内容
}
@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); //将除data外的信息保存到cacheHeader进而保存到map 内存
boolean success = e.writeHeader(fos); //将header信息写入硬盘
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
fos.write(entry.data); //将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());
}
}
看一个cache put行为的实例。
NetworkDispatcher.java
public void run()
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
看一个Response实现parseNetworkResponse的例子。
ImageRequest.java
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response)
return doParse(response);
private Response<Bitmap> doParse(NetworkResponse response)
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
//由下面的接口知 第二个参数就是cache.Entry—>回忆一下 自定义GsonRequest最后也用到了这个
//具体实现类HttpHeaderParser 在com.android.volley.toolbox包下 此处不做分析 就是构造出一个entry而已
/******
Response.java
public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
return new Response<T>(result, cacheEntry);
}
*****///
回到DiskBasedCache.java
static class CacheHeader {
public long size; //观察到这些字段和Cache Entry是一样的 唯独少了data字段
/** The key that identifies the cache entry. */
public String key;
/** ETag for cache coherence. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Headers from the response resulting in this cache entry. */
public Map<String, String> responseHeaders; //观察到这些字段和Cache Entry是一样的 唯独少了data字段
public CacheHeader(String key, Entry entry); //构造函数
public static CacheHeader readHeader(InputStream is); //读取header 其实是剔除文件中header部分的内容
public Entry toCacheEntry(byte[] data); //根据data还原出一个Entry对象
public boolean writeHeader(OutputStream os); //写header
}
最后看一下get的实现
@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;
} catch (NegativeArraySizeException 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;
}
}
}
} //没啥特别
ok~这篇文章就先到这里啦~
这篇文章分析了volley关于cache的实现。
1 volley封装了一个Cache接口 用于操作cache比如插入cache 清除cache等
2 volley封装了的Cache.Entry实体用于保存cache实际的内容 比如cache中内容从服务器获取的时间 cache
的内容等
3 为了构建cache.Entry实体 volley封装了一个HttpHeaderParser类 在http请求成功时将http请求 比如请求头 data封装成一个Cache.Entry。
4 volley为了加快cache的查找速度保存了一个类型为LinkedHashMap<String,CacheHeader>的变量保存在内容中。
5 volley再次封装Cache.Entry 封装出一个CacheHeader保存cache.Entry除data外的内容(这样可以作为map的value) 而将实际data存储在硬盘。