Android 网络请求框架volley源码解析(二) —— volley缓存原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 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。

猜你喜欢

转载自blog.csdn.net/immrwk/article/details/82590812