版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/immrwk/article/details/82590812
volley的缓存目录
通过上一篇分析中我们发现,创建一个请求队列的同时,会同时创建Volley的缓存目录和DiskBasedCache缓存对象,我们可以得知Volley的缓存目录就是在我们应用内置的cacheDir目录下的volley文件夹中,然后把这个目录用作DiskBasedCache硬盘缓存的目录,源码如下:
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), "volley");
...
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
return queue1;
}
volley为我们实现了磁盘缓存我们来详细分析一下,主要是DiskBasedCache这个类。
缓存容量控制
// volley缓存的默认容量 默认5M的大小
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
//系数 需要缓存占用总容量>volley设置的最大总容量的90%时,需要扩容
private static final float HYSTERESIS_FACTOR = 0.9f;
缓存初始化
//初始化操作 扫描缓存目录,并将缓存文件(header)添加到内存中,即map对象
@Override
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) {
try {
long entrySize = file.length();
CountingInputStream cis =
new CountingInputStream(
new BufferedInputStream(createInputStream(file)), entrySize);
try {
CacheHeader entry = CacheHeader.readHeader(cis);
// NOTE: When this entry was put, its size was recorded as data.length, but
// when the entry is initialized below, its size is recorded as file.length()
entry.size = entrySize;
putEntry(entry.key, entry);
} finally {
// Any IOException thrown here is handled by the below catch block by design.
//noinspection ThrowFromFinallyBlock
cis.close();
}
} catch (IOException e) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
}
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, .75f, true);
//将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);
}
可以看到,当缓存初始化的时候会读取设置的缓存目录,然后将缓存通过putEntry放到内存对象mEntries当中,并会记录当前已使用的总容量大小mTotalSize。
插入一条缓存
//根据key存缓存
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(createOutputStrea
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());
}
}
/**
这段代码我们分开来看,首先看最开始的pruneIfNeeded,pruneIfNeeded是检查当前可用空间是否满足新加入缓存的需要,如果容量不够,会删除旧的缓存。
1. 判断剩余容量是否够用
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();
// 循环遍历删除缓存文件,直到占用空间小于最大可用空间的90%时停止
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);
}
}
2. 缓存文件的命名
//生成缓存文件名
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
生成缓存文件名的时候主要根据key来区分,一般用url做key,这里会把key从中间一分为二,分别取hashCode(),最后再拼接到一起。
为什么不直接取hashCode()呢?一般认为可以这样可以从更大的程度上避免hash冲突。
3.写入缓存
fos.write(entry.data);
fos.close();
putEntry(key, e);
再后面一步就是写入文件,并且在内存对象mEntries中插入一条记录。
读取一条缓存记录
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// 检查entry是否存在,不存在直接返回null
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)) {
// File was shared by two keys and now holds data for a different entry!
VolleyLog.d(
"%s: key=%s, found=%s", file.getAbsolutePath(), key, entryOnDisk.key);
// Remove key whose contents on disk have been replaced.
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;
}
}
删除一条缓存
@Override
public synchronized void remove(String key) {
boolean deleted = getFileForKey(key).delete();
removeEntry(key);
if (!deleted) {
VolleyLog.d(
"Could not delete cache entry for key=%s, filename=%s",
key, getFilenameForKey(key));
}
}
private void removeEntry(String key) {
CacheHeader removed = mEntries.remove(key);
if (removed != null) {
mTotalSize -= removed.size;
}
}
分两步来操作,首先获取到缓存文件删除,然后将mEntries中的记录删除,mTotalSize减去相应的大小。
清空缓存
// 清除所有缓存
@Override
public synchronized void clear() {
File[] files = mRootDirectory.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
mEntries.clear();
mTotalSize = 0;
VolleyLog.d("Cache cleared.");
}
也是分两步,第一步遍历缓存目录下的所有文件删除,第二步是清空mEntries。