Volley HTTP 缓存机制
来源:互联网 发布:画化学分子式的软件 编辑:程序博客网 时间:2024/06/01 20:26
Volley HTTP 缓存规则
在介绍Volley的HTTP缓存机制之前,我们首先来看一下HTTP HEADER中和缓存有关的字段有:
Volley的缓存机制
Volley的缓存机制在对HTTP RESPONSE的解析中能够明显的看出来:
public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> 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; String headerValue; headerValue = headers.get("Date"); if (headerValue != null) { serverDate = parseDateAsEpoch(headerValue); } // 获取响应体的Cache缓存策略. headerValue = headers.get("Cache-Control"); if (headerValue != null) { hasCacheControl = true; String[] tokens = headerValue.split(","); for (String token : tokens) { token = token.trim(); if (token.equals("no-cache") || token.equals("no-store")) { // no-cache|no-store代表服务器禁止客户端缓存,每次需要重新发送HTTP请求 return null; } else if (token.startsWith("max-age=")) { // 获取缓存的有效时间 try { maxAge = Long.parseLong(token.substring(8)); } catch (Exception e) { maxAge = 0; } } else if (token.startsWith("stale-while-revalidate=")) { try { staleWhileRevalidate = Long.parseLong(token.substring(23)); } catch (Exception e) { staleWhileRevalidate = 0; } } 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"); // 计算缓存的ttl和softTtl 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的本地缓存的关键代码.
L2级硬盘缓存的实现和缓存替换机制
之前介绍了用户使用LruCache实现自定义的L1级缓存,而Volley本身利用了FIFO算法实现了L2级硬盘缓存.接下来,就详细介绍一下硬盘缓存的实现和缓存替换机制.
这里我们也是考虑如果自己实现硬盘缓存,需要实现哪几个步骤:
- 抽象出存储实体类.
- 定义抽象存储接口,包括initialize,get,put,clear等具体缓存系统的操作.
- 对象的序列化.
存储实体
存储的实体肯定是响应的结果,响应结果分为响应头和响应体,抽象类代码如下所示:
/** 真正HTTP请求缓存实体类. */class Entry { /** HTTP响应Headers. */ public Map<String, String> responseHeaders = Collections.emptyMap(); /** HTTP响应体. */ public byte[] data; /** 服务器资源标识ETag. */ public String etag; /** HTTP响应时间. */ public long serverDate; /** 缓存内容最后一次修改的时间. */ public long lastModified; /** Request的缓存过期时间. */ public long ttl; /** Request的缓存新鲜时间. */ public long softTtl; /** 判断缓存内容是否过期. */ public boolean isExpired() { return this.ttl < System.currentTimeMillis(); } /** 判断缓存是否新鲜,不新鲜的缓存需要发到服务端做新鲜度的检测. */ public boolean refreshNeeded() { return this.softTtl < System.currentTimeMillis(); }}
抽象缓存系统类
public interface Cache { /** 通过key获取请求的缓存实体. */ Entry get(String key); /** 存入一个请求的缓存实体. */ void put(String key, Entry entry); void initialize(); void invalidate(String key, boolean fullExpire); /** 移除指定的缓存实体. */ void remove(String key); /** 清空缓存. */ void clear();}
在Volley中,实现Cache接口的硬盘缓存类是DiskBasedCache.接下来,具体介绍每个方法的具体实现.
构造函数
我们先来看一下DiskBasedCache的构造函数实现:
public DiskBasedCache(File rootDirectory) { this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);}public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { mRootDirectory = rootDirectory; mMaxCacheSizeInBytes = maxCacheSizeInBytes;}
类似于LruCache,DiskBasedCache的构造函数做了两件事:
- 指定硬盘缓存的目录.
- 指定硬盘缓存的大小,默认为5M.
initialize函数
在介绍put和get函数之前,先介绍一下硬盘缓存的初始化函数,这个函数主要是用来遍历缓存的文件,从而获取当前缓存大小,和构造
/** * 初始化Disk缓存系统. * 作用是:遍历Disk缓存系统,将缓存文件中的CacheHeader和key存储到Map对象中. */public void initialize() { if (!mRootDirectory.exists() && !mRootDirectory.mkdirs()) { // 硬盘缓存目录不存在直接返回即可 return; } // 获取硬盘缓存目录所有文件集合.每个HTTP请求结果对应一个文件. 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,单位字节 entry.size = file.length(); // 在内存中维护一张硬盘<key,value>映射表 putEntry(entry.key, entry); }catch (IOException e) { file.delete(); e.printStackTrace(); }finally { if (fis != null) { try { fis.close(); } catch (IOException ignored) { } } } }}/** 将key和CacheHeader存入到Map对象中.并更新当前占用的总字节数. */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);}
put函数
接下来我们讲解put函数,是因为一个缓存系统最为关键的操作就是put,这其中还设计到缓存替换策略的实现.
首先是缓存替换策略.
private void pruneIfNeeded(int neededSpace) { if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { return; } Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, CacheHeader> entry = iterator.next(); CacheHeader e = entry.getValue(); // 这里的替换策略不太好,其实可以按照serverDate排序,从而实现FIFO的缓存替换策略. boolean deleted = getFileForKey(e.key).delete(); if (deleted) { mTotalSize -= e.size; } iterator.remove(); // 当硬盘大小满足可以存放新的HTTP请求结果时,停止删除操作 if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { break; } }}
接下来,是硬盘缓存的插入操作,准备是对象序列化的一些内容.
/** 将Cache.Entry存入到指定的缓存文件中. 并在Map中记录<key,CacheHeader>. */@Overridepublic synchronized void put(String key, Entry entry) { pruneIfNeeded(entry.data.length); // 根据HTTP的url生成缓存文件(ps:根据hash值生成文件名) File file = getFileForKey(key); try { BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); // 这里有个bug,插入时的size只计算响应体,没有考虑响应头部缓存字段的大小. CacheHeader e = new CacheHeader(key, entry); boolean success = e.writeHeader(fos); if (!success) { fos.close(); throw new IOException(); } fos.write(entry.data); fos.close(); putEntry(key, e); return; } catch (IOException e) { e.printStackTrace(); } file.delete();}
get函数
get函数比较简单了,源码如下:
/** 从Disk中根据key获取并构造HTTP响应体Cache.Entry. */@Overridepublic synchronized Entry get(String key) { CacheHeader entry = mEntries.get(key); if (entry == null) { return null; } File file = getFileForKey(key); CountingInputStream cis = null; try { cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file))); // 读完CacheHeader部分,并通过CountingInputStream的bytesRead成员记录已经读取的字节数. CacheHeader.readHeader(cis); // 读取缓存文件存储的HTTP响应体内容. byte[] data = streamToBytes(cis, (int)(file.length() - cis.bytesRead)); return entry.toCacheEntry(data); } catch (IOException e) { remove(key); return null; } finally { if (cis != null) { try { cis.close(); } catch (IOException ignored) { } } }}
clear函数
clear顾名思义,就是清空硬盘缓存的操作:
public synchronized void clear() { File[] files = mRootDirectory.listFiles(); if (files != null) { for (File file : files) { file.delete(); } } mEntries.clear(); mTotalSize = 0;}
所做的事情也比较简单,包括:
- 情况缓存文件.
- 将使用size置为0.
- 清空内存中维护的硬盘
remove函数
remove函数也就是删除指定key对应的硬盘缓存,代码很简单:
@Overridepublic synchronized void remove(String key) { boolean deleted = getFileForKey(key).delete(); removeEntry(key); if (!deleted) { Log.e("Volley", "没能删除key=" + key + ", 文件名=" + getFilenameForKey(key) + "缓存."); }}/** 从Map对象中删除key对应的键值对. */private void removeEntry(String key) { CacheHeader entry = mEntries.get(key); if (entry != null) { mTotalSize -= entry.size; mEntries.remove(key); }}
2 1
- Volley HTTP 缓存机制
- Volley Http缓存机制
- 看Volley源码,对HTTP缓存机制分析
- 看Volley源码,对HTTP缓存机制分析
- Volley Cache缓存机制
- Volley的缓存机制
- Volley缓存机制
- Android Volley http缓存
- 从源码上看Volley的缓存机制,volley缓存
- HTTP缓存机制--客户端缓存
- Http缓存机制
- iOS http缓存机制
- Http缓存机制
- 浅谈HTTP缓存机制
- Http中缓存机制
- 浅谈HTTP缓存机制!
- http 缓存机制
- http缓存机制
- POJ 3258 River Hopscotch (二分法)
- 多态性----vptr----vtable
- python+webpy, to deploy the Nao Controller Server
- copy file to system/lib on adroid root devices
- 第三条 Singleton 模式
- Volley HTTP 缓存机制
- POJ 1679 最小生成树
- 【hibernate】HQL初识
- Easy-题目20:202. Happy Number
- Android:Intent Filter 和 action 简介AndroidMainfest.xm
- ORM框架
- Easy-题目21:326. Power of Three
- H-Index
- Andriod Toast 自定义