Android网络框架volley学习(八)缓存Cache简析
2018-01-26 15:14
375 查看
不管是NetworkDispatcher还是CacheDispatcher它们里面都会涉及到Cache的处理,本篇文章我们来深入了解下Cache的实现原理。
Cache是一个接口,我们首先来看看它的方法。它的里面有个内部类Entry,也就是一个实体,它主要作用用于开放判断缓存结果是否过期以及是否需要验证新鲜度的。既然Cache是一个接口,所以我们通过它的实现类来理解它们的工作流程。
DiskBasedCache实现了Cache接口,直接存储于硬盘中,默认大小为5M,不过可以配置。我们接着看下它的变量。
其次,它有两种构造函数,
接着我们分析一下,插入一条缓存的实现方法put
通过一个key来缓存一条记录,然后通过当前缓存的大小来修改缓存。
接着通过缓存Entry来构造一个CacheHeader,为什么要构造这个CacheHeader呢?因为这个CacheHeader是保存在内存中的,这样当我们下面需要遍历缓存时不需要直接遍历本地缓存,这样就提高了效率。
构造之后通过写文件的方式来写入缓存。
以上就是缓存写入的过程,我们接着分析读取缓存的实现过程。
这就是Volley中关于Cache部分的源码。
Cache
public interface Cache { /** * Retrieves an entry from the cache. * @param key Cache key * @return An {@link Entry} or null in the event of a cache miss */ Entry get(String key); /** * Adds or replaces an entry to the cache. * @param key Cache key * @param entry Data to store and metadata for cache coherency, TTL, etc. */ void put(String key, Entry entry); /** * Performs any potentially long-running actions needed to initialize the cache; * will be called from a worker thread. */ void initialize(); /** * Invalidates an entry in the cache. * @param key Cache key * @param fullExpire True to fully expire the entry, false to soft expire */ void invalidate(String key, boolean fullExpire); /** * Removes an entry from the cache. * @param key Cache key */ void remove(String key); /** * Empties the cache. */ void clear(); /** * Data and metadata for an entry returned by the cache. */ 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; /** * Response headers as received from server; must be non-null. Should not be mutated * directly. * * <p>Note that if the server returns two headers with the same (case-insensitive) name, * this map will only contain the one of them. {@link #allResponseHeaders} may contain all * headers if the {@link Cache} implementation supports it. */ public Map<String, String> responseHeaders = Collections.emptyMap(); /** * All response headers. May be null depending on the {@link Cache} implementation. Should * not be mutated directly. */ public List<Header> allResponseHeaders; /** 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(); } } }
Cache是一个接口,我们首先来看看它的方法。它的里面有个内部类Entry,也就是一个实体,它主要作用用于开放判断缓存结果是否过期以及是否需要验证新鲜度的。既然Cache是一个接口,所以我们通过它的实现类来理解它们的工作流程。
DiskBasedCache
DiskBasedCache实现了Cache接口,我们来了解一下它是如何工作的。/** * Cache implementation that caches files directly onto the hard disk in the specified * directory. The default disk usage size is 5MB, but is configurable. * * <p>This cache supports the {@link Entry#allResponseHeaders} headers field. */
DiskBasedCache实现了Cache接口,直接存储于硬盘中,默认大小为5M,不过可以配置。我们接着看下它的变量。
/** Map of the Key, CacheHeader pairs */ private final Map<String, CacheHeader> mEntries = new LinkedHashMap<String, CacheHeader>(16, .75f, true); //缓存总大小,单位字节 private long mTotalSize = 0; //缓存目录 private final File mRootDirectory; //最大的缓存大小,单位字节 private final int mMaxCacheSizeInBytes; //默认的缓存大小5M private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; /** High water mark percentage for the cache */ private static final float HYSTERESIS_FACTOR = 0.9f; /** Magic number for current version of cache file format. */ private static final int CACHE_MAGIC = 0x20150306;
其次,它有两种构造函数,
//通过缓存路径和最大缓存大小来构造DiskBasedCache public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { mRootDirectory = rootDirectory; mMaxCacheSizeInBytes = maxCacheSizeInBytes; } //通过默认的大小,来构造DiskBasedCache public DiskBasedCache(File rootDirectory) { this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); }
接着我们分析一下,插入一条缓存的实现方法put
put(String key, Entry entry)
@Override public synchronized void put(String key, Entry entry) { pruneIfNeeded(entry.data.length); File file = getFileForKey(key); try { BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(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()); } }
通过一个key来缓存一条记录,然后通过当前缓存的大小来修改缓存。
/** * 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<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, CacheHeader> 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); } }
接着通过缓存Entry来构造一个CacheHeader,为什么要构造这个CacheHeader呢?因为这个CacheHeader是保存在内存中的,这样当我们下面需要遍历缓存时不需要直接遍历本地缓存,这样就提高了效率。
构造之后通过写文件的方式来写入缓存。
fos.write(entry.data); fos.close(); putEntry(key, e);
以上就是缓存写入的过程,我们接着分析读取缓存的实现过程。
public synchronized Entry get(String key) { //根据key获取CacheHeader CacheHeader entry = mEntries.get(key); //CacheHeader中不存在的话则直接return掉 if (entry == null) { return null; } File file = getFileForKey(key); try { CountingInputStream cis = new CountingInputStream( new BufferedInputStream(createInputStream(file)), file.length()); try { CacheHeader entryOnDisk = CacheHeader.readHeader(cis); if (!TextUtils.equals(key, entryOnDisk.key)) { //删除本地Cache removeEntry(key); return null; } //封装成Entry返回 byte[] data = streamToBytes(cis, cis.bytesRemaining()); return entry.toCacheEntry(data); } finally { // Any IOException thrown here is handled by the below catch block by design. //noinspection ThrowFromFinallyBlock cis.close(); } } catch (IOException e) { VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); remove(key); return null; } }
这就是Volley中关于Cache部分的源码。
相关文章推荐
- android网络请求框架的学习之路——浅谈对Volley,OkHttp,Retrofit的了解与对比
- android网络框架volley学习之Volley类
- Android网络框架volley学习(二)工作流程分析
- Android中Http网络请求库框架Volley和Asnyc-http的使用---第三方库学习笔记(一)
- Android网络框架volley学习(十)分发结果ResponseDelivery简析
- Android学习记录之Volley网络通信框架基础解析(1)
- android网络框架volley学习之HttpStack接口
- Android网络框架volley学习(一)基本用法
- Android 网络通信框架Volley学习
- Android网络框架volley学习(十一)volley源码解析总结
- android网络框架volley学习之RequestQueue类
- Android网络通信框架Volley的学习笔记
- Android网络框架volley学习(六)调度器CacheDispatcher简析
- android网络请求框架的学习之路——浅谈对Volley,OkHttp,Retrofit的了解与对比 标签: androidVolleyOkhttpRetrofit网络请求框架
- Android开发利用Volley框架下载和缓存网络图片
- android网络请求框架的学习之路——浅谈对Volley,OkHttp,Retrofit的了解与对比
- Android开发利用Volley框架下载和缓存网络图片
- Android学习记录20171023--Volley网络框架(一)
- Android-Volley网络通信框架(二次封装数据请求和图片请求(包含处理请求队列和图片缓存))
- Android-Volley网络通信框架(二次封装数据请求和图片请求(包括处理请求队列和图片缓存))