对于这张图我们只需要知道:
RequestQueue封装了众多重要参数,重点有缓存队列,网络请求队列,cache,network,缓存调度,网络调度数组。
流程全部在图中。
Volley的缓存,主要是通过DiskBasedCache实现的。DiskBasedCache类,继承于Cache接口。DiskBasedCache保存CacheHeader,具体的数据并不会全部缓存到内存中(只有一个CacheHeader)。
在初始化时,会先把存在的cache(CacheHeader)保存进mEntries。
可以从get方法看出,先从mEntries找到CacheHeader,再去读文件,根据entry.toCacheEntry(data);构建出Entry类对象。
public class DiskBasedCache implements Cache {
/** Map of the Key, CacheHeader pairs */
private final Map mEntries =
new LinkedHashMap(16, .75f, true);
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
/** The root directory to use for the cache. */
private final File mRootDirectory;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
......
public synchronized void initialize() {
//初始化,会读取缓存目录下的文件,全部放入mEntries中
...
File[] files = mRootDirectory.listFiles();
...
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);//重点
} catch (IOException e) {
...
} finally {
...
}
}
}
@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 FileInputStream(file));
CacheHeader.readHeader(cis); // eat header //先读header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);//重点
} catch (IOException e) {
...
} finally {
...
}
}
static class CacheHeader{见下代码}
private static class CountingInputStream extends FilterInputStream {...}
}
//DiskBasedCache内部类,提出来分析 注意没有data信息
static class CacheHeader {
/** The size of the data identified by this CacheHeader. (This is not
* serialized to disk. */
public long size;
/** 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 responseHeaders;
private CacheHeader() { }
public CacheHeader(String key, Entry entry) {
this.key = key;
this.size = entry.data.length;
this.etag = entry.etag;
this.serverDate = entry.serverDate;
this.lastModified = entry.lastModified;
this.ttl = entry.ttl;
this.softTtl = entry.softTtl;
this.responseHeaders = entry.responseHeaders;
}
public static CacheHeader readHeader(InputStream is) throws IOException{...}
public boolean writeHeader(OutputStream os){...}
public Entry toCacheEntry(byte[] data){
Entry e = new Entry();
e.data = data;
e.etag = etag;
e.serverDate = serverDate;
e.lastModified = lastModified;
e.ttl = ttl;
e.softTtl = softTtl;
e.responseHeaders = responseHeaders;
return e;
}
}
Cache接口及静态内部类Entry
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 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();
}
}
}
有人说Volley的缓存替换策略是FIFO的,不是LRU。我看了下源码,似乎不是这样(如果我弄错了,希望各位指正)
FIFO 说法来源 Volley HTTP 缓存机制 手撕 Volley(二)
DiskBasedCache中的相关属性方法
private final Map mEntries =
new LinkedHashMap(16, .75f, true);
//重点,这个true是设置成access order, false是insertion order
/**
* 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());
}
}
/**
* Prunes the cache to fit the amount of bytes specified.
* @param neededSpace The amount of bytes we are trying to fit into the cache.
*/
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
if (VolleyLog.DEBUG) {
VolleyLog.v("Pruning old cache entries.");
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
//从头开始删除,linkedhashmap又是access order ,所以是LRU
Map.Entry entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
if (VolleyLog.DEBUG) {
VolleyLog.v("pruned %d files, %d bytes, %d ms",
prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
}
}
缓存的添加逻辑在NetworkDispatcher.java中展开
//NetworkDispatcher.java
public class NetworkDispatcher extends Thread {
/** The queue of requests to service. */
private final BlockingQueue> mQueue;
/** The network interface for processing requests. */
private final Network mNetwork;
/** The cache to write to. */
private final Cache mCache;
/** For posting responses and errors. */
private final ResponseDelivery mDelivery;
/** Used for telling us to die. */
private volatile boolean mQuit = false;
...
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request> request;
try {
// Take a request from the queue.
request = mQueue.take();
} catch ...
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);//发起请求
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response> response = request.parseNetworkResponse(networkResponse);//转1 目的在于构建Cache.Entry
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);//放入mCache中(cacheHeader),同时保存在磁盘中(这个方法在上文FIFO/LRU讨论有提及)
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch ...
}
}
//其他方法
}
//Request.java
//接 转1
public abstract class Request<T> implements Comparable<Request<T>> {
...
//在具体的Request类中实现,这里以JsonObjectRequest类为例,见下代码
abstract protected Response parseNetworkResponse(NetworkResponse response);
...
}
//JsonObjectRequest.java 以JsonObjectRequest的实现为例
public class JsonObjectRequest extends JsonRequest<JSONObject> {
public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener listener, ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
errorListener);
}
...
//实现抽象方法
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
// HttpHeaderParser.parseCacheHeaders() -> 完成response的header解析,构建成Cache.Entry(这是有数据的)
// Response.success() -> new Response(T result, Cache.Entry cacheEntry);
return Response.success(new JSONObject(jsonString),
HttpHeaderParser.parseCacheHeaders(response));//静态方法
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException je) {
return Response.error(new ParseError(je));
}
}
}
//HttpHeaderParser.java
public class HttpHeaderParser {
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map headers = response.headers;
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;
String serverEtag = null;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
return null;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
mustRevalidate = true;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
...
}
Volley对于图片加载做了内存缓存的处理(ImageLoader),详情见Volley图片加载功能
手撕Volley(一、二、三)
Volley HTTP 缓存机制
Volley图片加载功能
彻底弄懂HTTP缓存机制及原理
浅谈浏览器http的缓存机制