在移动应用开发中,网络请求是不可或缺的一环。然而,频繁的网络请求不仅会消耗用户的流量,还会影响应用的响应速度和性能。为了解决这些问题,缓存机制应运而生。Android Volley作为一款强大的网络请求库,提供了灵活且高效的缓存策略,能够显著提升应用的性能和用户体验。
本文将深入剖析Android Volley的缓存读取与更新机制,从源码级别详细分析其实现原理、工作流程和关键技术点。通过本文的学习,你将全面掌握Volley缓存读取与更新的核心原理,学会如何根据不同的应用场景优化缓存策略,以及如何解决缓存使用过程中遇到的常见问题。
在Volley中,缓存读取的入口位于RequestQueue
类的add
方法中。当我们将一个请求添加到请求队列时,Volley会首先检查该请求是否应该被缓存,如果是,则会尝试从缓存中获取数据。
/**
* 将请求添加到请求队列中
* @param request 要添加的请求
* @return 返回请求对象本身,便于链式调用
*/
public <T> Request<T> add(Request<T> request) {
// 将请求标记为已添加到队列
request.setRequestQueue(this);
// 同步添加请求到当前请求集合中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 为请求分配序列号
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果请求不需要缓存,直接将其添加到网络队列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// 否则,将请求添加到缓存队列
mCacheQueue.add(request);
return request;
}
从上述代码可以看出,当request.shouldCache()
返回true
时,请求会被添加到mCacheQueue
中,接下来Volley会从缓存中尝试读取数据。
缓存调度器(CacheDispatcher
)是一个独立的线程,负责从缓存队列中取出请求并处理缓存读取操作。
/**
* 缓存调度器,负责处理缓存请求
*/
public class CacheDispatcher extends Thread {
// 缓存队列
private final BlockingQueue<Request<?>> mCacheQueue;
// 网络队列
private final BlockingQueue<Request<?>> mNetworkQueue;
// 缓存接口
private final Cache mCache;
// 响应分发器
private final ResponseDelivery mDelivery;
// 退出标志
private volatile boolean mQuit = false;
/**
* 创建一个新的缓存调度器
* @param cacheQueue 缓存队列
* @param networkQueue 网络队列
* @param cache 缓存接口
* @param delivery 响应分发器
*/
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue,
BlockingQueue<Request<?>> networkQueue,
Cache cache,
ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
/**
* 停止调度器
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
// 设置线程优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化缓存
mCache.initialize();
// 循环处理缓存请求
while (true) {
try {
// 从缓存队列中取出一个请求(阻塞操作)
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 如果请求已被取消,跳过处理
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 从缓存中获取数据
Cache.Entry entry = mCache.get(request.getCacheKey());
// 如果缓存条目不存在,将请求添加到网络队列
if (entry == null) {
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
// 如果缓存条目已过期,将请求添加到网络队列
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 缓存命中且未过期,解析缓存数据
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
// 检查缓存是否需要刷新
if (entry.refreshNeeded()) {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 标记响应为中间响应
response.intermediate = true;
// 将响应立即分发给主线程
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
// 将请求添加到网络队列以刷新缓存
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
}
}
});
} else {
// 缓存不需要刷新,直接将响应分发给主线程
mDelivery.postResponse(request, response);
}
} catch (InterruptedException e) {
// 如果线程被中断且退出标志为true,则退出循环
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
// 否则,继续处理下一个请求
continue;
}
}
}
}
从上述代码可以看出,缓存调度器的主要工作流程如下:
缓存读取的核心方法是Cache
接口的get
方法。Volley默认的实现是DiskBasedCache
,下面我们来分析其实现。
/**
* 基于磁盘的缓存实现
*/
public class DiskBasedCache implements Cache {
/** 缓存条目映射表,用于快速查找缓存条目 */
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<String, CacheHeader>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Entry<String, CacheHeader> eldest) {
// 当缓存大小超过限制时,移除最老的条目
return mTotalSize > mMaxCacheSizeInBytes;
}
};
// 其他成员变量...
/**
* 从缓存中获取指定键的数据
* @param key 缓存键
* @return 缓存条目,如果不存在则返回null
*/
@Override
public synchronized Entry get(String key) {
// 根据缓存键生成对应的文件
File file = getFileForKey(key);
// 如果文件不存在,返回null
if (!file.exists()) {
return null;
}
BufferedInputStream fis = null;
try {
// 打开文件输入流
fis = new BufferedInputStream(new FileInputStream(file));
// 读取缓存头部信息
CacheHeader header = CacheHeader.readHeader(fis);
// 如果头部信息读取失败,删除文件并返回null
if (header == null) {
VolleyLog.d("Cache header corruption for %s", file.getAbsolutePath());
file.delete();
return null;
}
// 计算剩余的文件长度(即数据部分的长度)
int length = (int) (file.length() - fis.available());
// 读取缓存数据
byte[] data = streamToBytes(fis, length);
// 创建缓存条目并返回
Entry entry = new Entry();
entry.data = data;
entry.etag = header.etag;
entry.softTtl = header.softTtl;
entry.ttl = header.ttl;
entry.serverDate = header.serverDate;
entry.responseHeaders = header.responseHeaders;
return entry;
} catch (IOException e) {
// 发生异常时记录日志并删除文件
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
// 关闭输入流
if (fis != null) {
try {
fis.close();
} catch (IOException ignored) {
}
}
}
}
/**
* 将输入流转换为字节数组
* @param in 输入流
* @param length 要读取的长度
* @return 字节数组
* @throws IOException 如果读取过程中发生错误
*/
private static byte[] streamToBytes(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
int count;
int pos = 0;
// 循环读取数据直到达到指定长度或读取完毕
while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
pos += count;
}
// 如果读取的字节数与指定长度不符,抛出异常
if (pos != length) {
throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
}
return bytes;
}
// 其他方法...
}
从上述代码可以看出,DiskBasedCache
的get
方法主要完成以下工作:
在缓存读取过程中,需要判断缓存的状态,包括是否存在、是否过期以及是否需要刷新。这些判断逻辑主要在Cache.Entry
类中实现。
/**
* 缓存条目类,包含缓存数据的元信息
*/
public static class Entry {
/** 缓存数据的字节数组 */
public byte[] data;
/** ETag,用于服务器验证缓存 */
public String etag;
/** 服务器响应的日期(毫秒) */
public long serverDate;
/** 缓存的软过期时间(毫秒) */
public long softTtl;
/** 缓存的硬过期时间(毫秒) */
public long ttl;
/** 响应头信息 */
public Map<String, String> responseHeaders;
/**
* 判断缓存是否已硬过期
* @return 如果已硬过期返回true,否则返回false
*/
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/**
* 判断缓存是否需要刷新
* @return 如果需要刷新返回true,否则返回false
*/
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
从上述代码可以看出:
isExpired()
方法通过比较当前时间和硬过期时间(ttl
)来判断缓存是否已完全过期refreshNeeded()
方法通过比较当前时间和软过期时间(softTtl
)来判断缓存是否需要刷新这两个方法在CacheDispatcher
中被用于决定是直接使用缓存数据,还是需要发起网络请求来刷新缓存。
在Volley中,缓存更新主要在以下几种情况下触发:
invalidate
或remove
方法下面我们分别分析这些触发条件的实现。
当缓存数据不存在或已硬过期时,Volley会发起网络请求获取最新数据,并在请求成功后更新缓存。
网络请求的处理主要在NetworkDispatcher
类中完成:
/**
* 网络调度器,负责处理网络请求
*/
public class NetworkDispatcher extends Thread {
// 网络队列
private final BlockingQueue<Request<?>> mQueue;
// 网络接口
private final Network mNetwork;
// 缓存接口
private final Cache mCache;
// 响应分发器
private final ResponseDelivery mDelivery;
// 退出标志
private volatile boolean mQuit = false;
/**
* 创建一个新的网络调度器
* @param queue 网络队列
* @param network 网络接口
* @param cache 缓存接口
* @param delivery 响应分发器
*/
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
/**
* 停止调度器
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
// 设置线程优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 循环处理网络请求
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 从网络队列中取出一个请求(阻塞操作)
request = mQueue.take();
} catch (InterruptedException e) {
// 如果线程被中断且退出标志为true,则退出循环
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
continue;
}
try {
// 标记请求开始处理
request.addMarker("network-queue-take");
// 如果请求已被取消,跳过处理
if (request.isCanceled()) {
request.finish("network-discard-canceled");
continue;
}
// 执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-attempt");
// 处理HTTP 304(未修改)状态码
if (networkResponse.notModified) {
// 如果服务器返回304,表示缓存数据仍然有效
// 获取缓存中的条目
Cache.Entry entry = mCache.get(request.getCacheKey());
// 如果缓存条目存在,使用缓存的响应
if (entry != null) {
request.addMarker("network-304-not-modified");
// 创建一个新的响应,使用缓存的数据
Response<?> response = Response.success(
request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders)).result,
entry
);
// 分发响应到主线程
mDelivery.postResponse(request, response);
} else {
// 如果缓存条目不存在,创建一个空的响应
Response<?> response = Response.success(null, null);
mDelivery.postResponse(request, response);
}
// 继续处理下一个请求
continue;
}
// 解析网络响应
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 如果请求需要缓存,将响应放入缓存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 标记请求已交付响应
request.markDelivered();
// 分发响应到主线程
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
// 处理网络错误
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
// 处理其他异常
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
// 其他方法...
}
从上述代码可以看出,当网络请求成功后,会执行以下操作:
mCache.put()
方法更新缓存缓存更新的核心方法是Cache
接口的put
方法。同样,我们来看DiskBasedCache
的实现。
/**
* 基于磁盘的缓存实现
*/
public class DiskBasedCache implements Cache {
/** 缓存目录 */
private final File mRootDirectory;
/** 缓存的最大大小(字节) */
private final int mMaxCacheSizeInBytes;
/** 当前缓存的大小(字节) */
private long mTotalSize = 0;
// 其他成员变量...
/**
* 将数据存入缓存
* @param key 缓存键
* @param entry 缓存条目
*/
@Override
public synchronized void put(String key, Entry entry) {
// 在存入新数据前,检查是否需要清理缓存以腾出空间
pruneIfNeeded(entry.data.length);
// 根据缓存键生成对应的文件
File file = getFileForKey(key);
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
// 创建临时文件用于写入
File tmpFile = getTempFileForKey(key);
// 打开临时文件输出流
fos = new FileOutputStream(tmpFile);
bos = new BufferedOutputStream(fos);
// 写入缓存头部信息
CacheHeader header = new CacheHeader(key, entry);
header.writeHeader(bos);
// 写入缓存数据
bos.write(entry.data);
// 刷新输出流
bos.flush();
// 将临时文件重命名为正式的缓存文件
if (!tmpFile.renameTo(file)) {
VolleyLog.e("ERROR: rename failed, tmpFile=%s, file=%s",
tmpFile.getAbsolutePath(), file.getAbsolutePath());
throw new IOException("Rename failed!");
}
// 更新缓存映射表
putEntry(key, header);
} catch (IOException e) {
// 发生异常时删除临时文件
if (file.exists()) {
if (!file.delete()) {
VolleyLog.e("Could not clean up file %s", file.getAbsolutePath());
}
}
VolleyLog.e("Failed to write cache entry for key=%s, filename=%s: %s",
key, file.getAbsolutePath(), e.getMessage());
} finally {
// 关闭输出流
if (bos != null) {
try {
bos.close();
} catch (IOException ignored) {
}
} else if (fos != null) {
try {
fos.close();
} catch (IOException ignored) {
}
}
}
}
/**
* 如果需要,清理缓存以腾出空间
* @param neededSpace 需要的空间大小(字节)
*/
private void pruneIfNeeded(int neededSpace) {
// 如果当前缓存大小加上需要的空间超过最大缓存大小
if ((mTotalSize + neededSpace) > mMaxCacheSizeInBytes) {
// 计算需要清理的目标大小(最大缓存大小的90%)
int targetSize = (int) (mMaxCacheSizeInBytes * 0.9f);
// 遍历缓存条目,按访问顺序删除最老的条目
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext() && mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
// 删除对应的缓存文件
boolean deleted = e.file.delete();
// 更新缓存大小
if (deleted) {
mTotalSize -= e.size;
}
// 从缓存映射表中移除该条目
iterator.remove();
}
// 如果清理后仍然没有足够的空间,记录错误
if (mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
VolleyLog.e("Failed to clear space in cache");
}
}
}
// 其他方法...
}
从上述代码可以看出,DiskBasedCache
的put
方法主要完成以下工作:
当缓存数据达到软过期时间但未达到硬过期时间时,Volley会采用一种特殊的更新策略:先返回缓存数据给用户,同时在后台发起网络请求更新缓存。
这种策略的实现代码在CacheDispatcher
类中:
/**
* 缓存调度器,负责处理缓存请求
*/
public class CacheDispatcher extends Thread {
// 其他成员变量...
@Override
public void run() {
// 其他代码...
// 循环处理缓存请求
while (true) {
try {
// 从缓存队列中取出一个请求
final Request<?> request = mCacheQueue.take();
// 其他代码...
// 从缓存中获取数据
Cache.Entry entry = mCache.get(request.getCacheKey());
// 其他代码...
// 检查缓存是否需要刷新
if (entry.refreshNeeded()) {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 标记响应为中间响应
response.intermediate = true;
// 将响应立即分发给主线程
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
// 将请求添加到网络队列以刷新缓存
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
}
}
});
} else {
// 缓存不需要刷新,直接将响应分发给主线程
mDelivery.postResponse(request, response);
}
} catch (InterruptedException e) {
// 处理中断异常
}
}
}
}
从上述代码可以看出,当缓存需要刷新时,Volley会:
除了自动触发的缓存更新外,我们还可以通过调用Cache
接口的方法手动触发缓存更新。
/**
* 缓存接口,定义了缓存操作的基本方法
*/
public interface Cache {
// 其他方法...
/**
* 刷新指定缓存条目的状态
* @param key 缓存键
* @param fullExpire 如果为true,表示完全过期,需要重新请求
*/
public void invalidate(String key, boolean fullExpire);
/**
* 从缓存中删除指定键的数据
* @param key 缓存键
*/
public void remove(String key);
/**
* 清空所有缓存数据
*/
public void clear();
}
下面我们来看DiskBasedCache
对这些方法的实现:
/**
* 基于磁盘的缓存实现
*/
public class DiskBasedCache implements Cache {
// 其他成员变量...
/**
* 刷新指定缓存条目的状态
* @param key 缓存键
* @param fullExpire 如果为true,表示完全过期,需要重新请求
*/
@Override
public synchronized void invalidate(String key, boolean fullExpire) {
// 从缓存映射表中获取缓存头部信息
CacheHeader entry = mEntries.get(key);
// 如果缓存条目存在
if (entry != null) {
// 如果需要完全过期,设置硬过期时间为当前时间
if (fullExpire) {
entry.softTtl = 0;
entry.ttl = 0;
} else {
// 否则,只设置软过期时间为当前时间
entry.softTtl = 0;
}
// 将更新后的缓存头部信息写入文件
File file = getFileForKey(key);
if (file.exists()) {
// 创建临时文件
File tmpFile = getTempFileForKey(key);
try {
// 写入更新后的缓存头部信息
FileOutputStream fos = new FileOutputStream(tmpFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
entry.writeHeader(bos);
// 将原文件的剩余部分(数据部分)复制到临时文件
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
// 跳过头部信息
bis.skip(CacheHeader.HEADER_LENGTH);
// 复制数据部分
byte[] buffer = new byte[1024];
int count;
while ((count = bis.read(buffer)) != -1) {
bos.write(buffer, 0, count);
}
// 关闭流
bis.close();
bos.close();
// 将临时文件重命名为正式的缓存文件
if (!tmpFile.renameTo(file)) {
throw new IOException("Rename failed!");
}
} catch (IOException e) {
VolleyLog.e("Error writing header for %s", file.getAbsolutePath());
}
}
}
}
/**
* 从缓存中删除指定键的数据
* @param key 缓存键
*/
@Override
public synchronized void remove(String key) {
// 根据缓存键获取对应的文件
File file = getFileForKey(key);
// 删除文件
boolean deleted = file.delete();
// 如果文件删除成功,从缓存映射表中移除该条目
if (deleted) {
CacheHeader entry = mEntries.get(key);
if (entry != null) {
mTotalSize -= entry.size;
mEntries.remove(key);
}
}
}
/**
* 清空所有缓存数据
*/
@Override
public synchronized void clear() {
// 获取缓存目录下的所有文件
File[] files = mRootDirectory.listFiles();
// 删除所有文件
if (files != null) {
for (File file : files) {
if (!file.delete()) {
VolleyLog.e("Could not delete cache file %s", file.getAbsolutePath());
}
}
}
// 重置缓存映射表和总大小
mEntries.clear();
mTotalSize = 0;
}
// 其他方法...
}
从上述代码可以看出:
invalidate
方法通过修改缓存条目的过期时间来触发缓存更新remove
方法用于删除指定的缓存条目clear
方法用于清空所有缓存数据Volley的缓存读取与更新是一个完整的流程,涉及多个组件的协作。下面我们通过一个完整的请求处理流程来分析它们之间的交互。
// 创建请求队列
RequestQueue requestQueue = Volley.newRequestQueue(context);
// 创建请求
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
// 将请求添加到队列
requestQueue.add(request);
上述代码发起了一个简单的GET请求,下面我们来分析这个请求的完整处理流程:
请求添加到队列:调用requestQueue.add(request)
将请求添加到请求队列。
缓存读取尝试:
mCacheQueue
)CacheDispatcher
)从缓存队列中取出请求mCache.get(request.getCacheKey())
)mNetworkQueue
)网络请求处理:
NetworkDispatcher
)从网络队列中取出请求mNetwork.performRequest(request)
)mCache.put()
)缓存更新:
当使用条件请求(如ETag或Last-Modified)时,缓存读取与更新的流程会有所不同。下面我们来分析条件请求的处理流程。
// 自定义请求类,添加ETag条件请求
public class ETagRequest extends StringRequest {
private String mETag;
public ETagRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener, String eTag) {
super(method, url, listener, errorListener);
mETag = eTag;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
// 如果有ETag,添加到请求头中
if (mETag != null) {
headers.put("If-None-Match", mETag);
}
return headers;
}
}
使用ETag进行条件请求的处理流程:
首次请求:
后续请求:
服务器响应处理:
条件请求的处理流程在NetworkDispatcher
类中实现:
/**
* 网络调度器,负责处理网络请求
*/
public class NetworkDispatcher extends Thread {
// 其他成员变量...
@Override
public void run() {
// 其他代码...
// 循环处理网络请求
while (true) {
try {
// 从网络队列中取出一个请求
Request<?> request = mQueue.take();
// 其他代码...
// 执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-attempt");
// 处理HTTP 304(未修改)状态码
if (networkResponse.notModified) {
// 如果服务器返回304,表示缓存数据仍然有效
// 获取缓存中的条目
Cache.Entry entry = mCache.get(request.getCacheKey());
// 如果缓存条目存在,使用缓存的响应
if (entry != null) {
request.addMarker("network-304-not-modified");
// 创建一个新的响应,使用缓存的数据
Response<?> response = Response.success(
request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders)).result,
entry
);
// 分发响应到主线程
mDelivery.postResponse(request, response);
} else {
// 如果缓存条目不存在,创建一个空的响应
Response<?> response = Response.success(null, null);
mDelivery.postResponse(request, response);
}
// 继续处理下一个请求
continue;
}
// 其他代码...
} catch (InterruptedException e) {
// 处理中断异常
}
}
}
}
为了提高缓存读取的性能,可以采取以下优化策略:
// 添加内存缓存层
private final LruCache<String, Cache.Entry> mMemoryCache = new LruCache<String, Cache.Entry>(1024 * 1024) { // 1MB内存缓存
@Override
protected int sizeOf(String key, Cache.Entry entry) {
return entry.data.length;
}
};
// 在请求时优先从内存缓存获取
public Cache.Entry getFromCache(String key) {
// 先从内存缓存获取
Cache.Entry entry = mMemoryCache.get(key);
if (entry != null) {
return entry;
}
// 再从磁盘缓存获取
entry = mDiskBasedCache.get(key);
if (entry != null) {
// 再从磁盘缓存获取
entry = mDiskBasedCache.get(key);
if (entry != null) {
// 将磁盘缓存的条目放入内存缓存
mMemoryCache.put(key, entry);
}
return entry;
}
// 异步读取缓存
public void getCacheAsync(final String key, final CacheCallback callback) {
new Thread(new Runnable() {
@Override
public void run() {
final Cache.Entry entry = mDiskBasedCache.get(key);
if (callback != null) {
// 将结果回调到主线程
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onCacheLoaded(entry);
}
});
}
}
}).start();
}
// 回调接口
public interface CacheCallback {
void onCacheLoaded(Cache.Entry entry);
}
// 批量读取缓存
public Map<String, Cache.Entry> getBulkFromCache(Collection<String> keys) {
Map<String, Cache.Entry> result = new HashMap<>();
// 先从内存缓存中批量获取
for (String key : keys) {
Cache.Entry entry = mMemoryCache.get(key);
if (entry != null) {
result.put(key, entry);
}
}
// 再从磁盘缓存中获取剩余的条目
Set<String> missingKeys = new HashSet<>(keys);
missingKeys.removeAll(result.keySet());
for (String key : missingKeys) {
Cache.Entry entry = mDiskBasedCache.get(key);
if (entry != null) {
result.put(key, entry);
// 放入内存缓存
mMemoryCache.put(key, entry);
}
}
return result;
}
为了提高缓存更新的效率,可以采取以下优化策略:
// 批量写入缓存
public void batchPut(final Map<String, Cache.Entry> entries) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (DiskBasedCache.this) {
for (Map.Entry<String, Cache.Entry> entry : entries.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
}
}).start();
}
// 创建线程池用于异步写入
private final ExecutorService mWriteExecutor = Executors.newSingleThreadExecutor();
// 异步写入缓存
public void putAsync(final String key, final Cache.Entry entry) {
mWriteExecutor.submit(new Runnable() {
@Override
public void run() {
put(key, entry);
}
});
}
// 智能缓存更新请求
public class SmartCacheRequest extends StringRequest {
private static final long UPDATE_INTERVAL = 60 * 1000; // 1分钟更新间隔
public SmartCacheRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
super(method, url, listener, errorListener);
}
@Override
public void deliverResponse(String response) {
// 获取上次更新时间
long lastUpdateTime = getLastUpdateTime();
// 如果距离上次更新时间不足更新间隔,直接使用缓存
if (System.currentTimeMillis() - lastUpdateTime < UPDATE_INTERVAL) {
Cache.Entry entry = getCacheEntry();
if (entry != null) {
Response<String> cachedResponse = Response.success(
new String(entry.data), entry);
super.deliverResponse(cachedResponse.result);
return;
}
}
// 否则,执行正常的响应分发
super.deliverResponse(response);
// 更新上次更新时间
updateLastUpdateTime();
}
// 获取上次更新时间
private long getLastUpdateTime() {
// 从SharedPreferences中获取上次更新时间
SharedPreferences prefs = Volley.getApplicationContext()
.getSharedPreferences("cache_update_times", Context.MODE_PRIVATE);
return prefs.getLong(getCacheKey(), 0);
}
// 更新上次更新时间
private void updateLastUpdateTime() {
SharedPreferences prefs = Volley.getApplicationContext()
.getSharedPreferences("cache_update_times", Context.MODE_PRIVATE);
prefs.edit().putLong(getCacheKey(), System.currentTimeMillis()).apply();
}
}
为了有效管理缓存空间,可以采取以下优化策略:
// 扩展CacheHeader类添加优先级字段
public static class PriorityCacheHeader extends CacheHeader {
public int priority;
public PriorityCacheHeader(String key, Entry entry, int priority) {
super(key, entry);
this.priority = priority;
}
// 其他方法...
}
// 修改pruneIfNeeded方法,优先删除低优先级的缓存
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) > mMaxCacheSizeInBytes) {
// 先按优先级排序缓存条目
List<Map.Entry<String, CacheHeader>> entries = new ArrayList<>(mEntries.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<String, CacheHeader>>() {
@Override
public int compare(Map.Entry<String, CacheHeader> e1, Map.Entry<String, CacheHeader> e2) {
// 按优先级升序排列
return Integer.compare(getPriority(e1.getValue()), getPriority(e2.getValue()));
}
private int getPriority(CacheHeader header) {
if (header instanceof PriorityCacheHeader) {
return ((PriorityCacheHeader) header).priority;
}
return 0; // 默认优先级
}
});
// 按优先级顺序删除缓存条目
for (Map.Entry<String, CacheHeader> entry : entries) {
if (mTotalSize + neededSpace <= mMaxCacheSizeInBytes) {
break;
}
CacheHeader e = entry.getValue();
boolean deleted = e.file.delete();
if (deleted) {
mTotalSize -= e.size;
}
mEntries.remove(entry.getKey());
}
if (mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
VolleyLog.e("Failed to clear space in cache");
}
}
}
// 修改pruneIfNeeded方法,优先删除超过时间窗口的缓存
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) > mMaxCacheSizeInBytes) {
long currentTime = System.currentTimeMillis();
// 首先删除超过最大时间窗口的缓存条目
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext() && mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
// 如果缓存条目超过最大时间窗口(例如7天)
if (currentTime - e.serverDate > MAX_TIME_WINDOW) {
boolean deleted = e.file.delete();
if (deleted) {
mTotalSize -= e.size;
}
iterator.remove();
}
}
// 如果空间仍然不足,再按LRU策略删除
if (mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
iterator = mEntries.entrySet().iterator();
while (iterator.hasNext() && mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = e.file.delete();
if (deleted) {
mTotalSize -= e.size;
}
iterator.remove();
}
}
if (mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
VolleyLog.e("Failed to clear space in cache");
}
}
}
// 动态调整缓存大小
public void adjustCacheSize() {
// 获取可用存储空间
StatFs stat = new StatFs(mRootDirectory.getAbsolutePath());
long availableBytes = (long) stat.getAvailableBlocks() * stat.getBlockSize();
// 根据可用空间比例动态调整缓存大小
if (availableBytes < MIN_AVAILABLE_SPACE) {
// 存储空间不足,减小缓存大小
setMaxCacheSize((int) (MAX_CACHE_SIZE_LOW * availableBytes / MIN_AVAILABLE_SPACE));
pruneIfNeeded(0); // 立即清理缓存
} else if (availableBytes > MAX_AVAILABLE_SPACE) {
// 存储空间充足,增大缓存大小
setMaxCacheSize(MAX_CACHE_SIZE_HIGH);
} else {
// 中等存储空间,使用默认缓存大小
setMaxCacheSize(MAX_CACHE_SIZE_DEFAULT);
}
}
// 设置最大缓存大小
private void setMaxCacheSize(int size) {
mMaxCacheSizeInBytes = size;
}
在实际开发中,常出现设置了缓存但请求仍每次都发起网络请求的情况。
可能原因:
shouldCache
属性被误设为false
Cache-Control: no-cache
或Cache-Control: no-store
解决方案:
shouldCache
属性设置正确。// 确保请求设置了shouldCache为true
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
// 确保启用缓存
request.setShouldCache(true); // 默认为true,但最好显式设置
requestQueue.add(request);
Cache-Control
设置是否符合预期。// 自定义请求类,忽略响应头中的Cache-Control
public class IgnoreCacheControlRequest extends StringRequest {
public IgnoreCacheControlRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
super(method, url, listener, errorListener);
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
// 解析响应数据
String parsed = new String(response.data);
// 创建自定义的缓存条目,设置合理的过期时间
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = response.headers.get("ETag");
// 设置软过期时间为10分钟
entry.softTtl = System.currentTimeMillis() + 10 * 60 * 1000;
// 设置硬过期时间为1小时
entry.ttl = System.currentTimeMillis() + 60 * 60 * 1000;
entry.serverDate = parseDateAsEpoch(response.headers.get("Date"));
entry.responseHeaders = response.headers;
return Response.success(parsed, entry);
}
// 解析日期字符串为时间戳
private long parseDateAsEpoch(String dateStr) {
if (dateStr == null) {
return 0;
}
try {
return DateUtils.parseDate(dateStr).getTime();
} catch (ParseException e) {
return 0;
}
}
}
getCacheKey
方法,确保缓存键生成逻辑正确。// 自定义请求类,确保生成正确的缓存键
public class CustomCacheKeyRequest extends StringRequest {
private final String mCustomCacheKey;
public CustomCacheKeyRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener, String customCacheKey) {
super(method, url, listener, errorListener);
mCustomCacheKey = customCacheKey;
}
@Override
public String getCacheKey() {
// 使用自定义的缓存键,确保唯一性
return mCustomCacheKey != null ? mCustomCacheKey : super.getCacheKey();
}
}
当服务器数据已更新,但应用仍展示旧的缓存数据时,影响用户体验。
可能原因:
解决方案:
// 自定义请求类,设置合理的缓存过期时间
public class CustomExpiryRequest extends StringRequest {
private final long mSoftTtl;
private final long mTtl;
public CustomExpiryRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener, long softTtl, long ttl) {
super(method, url, listener, errorListener);
mSoftTtl = softTtl;
mTtl = ttl;
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
Response<String> parsedResponse = super.parseNetworkResponse(response);
if (parsedResponse != null && parsedResponse.cacheEntry != null) {
// 设置自定义的软过期时间和硬过期时间
parsedResponse.cacheEntry.softTtl = System.currentTimeMillis() + mSoftTtl;
parsedResponse.cacheEntry.ttl = System.currentTimeMillis() + mTtl;
}
return parsedResponse;
}
}
// 使用示例:设置软过期时间为1分钟,硬过期时间为5分钟
CustomExpiryRequest request = new CustomExpiryRequest(
Request.Method.GET,
"https://api.example.com/data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
60 * 1000, // 软过期时间(毫秒)
5 * 60 * 1000 // 硬过期时间(毫秒)
);
// 使用ETag实现条件请求
public class ETagRequest extends StringRequest {
private final String mETag;
public ETagRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener, String eTag) {
super(method, url, listener, errorListener);
mETag = eTag;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
// 添加ETag到请求头
if (mETag != null) {
headers.put("If-None-Match", mETag);
}
return headers;
}
}
// 使用示例
Cache.Entry entry = mCache.get("cache_key");
String eTag = entry != null ? entry.etag : null;
ETagRequest request = new ETagRequest(
Request.Method.GET,
"https://api.example.com/data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
eTag
);
// 手动刷新缓存
public void refreshCache(String cacheKey) {
// 使缓存条目失效
mCache.invalidate(cacheKey, true);
// 重新发起请求
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
requestQueue.add(request);
}
缓存占用过多存储空间,会导致应用占用空间过大,甚至影响设备性能。
可能原因:
解决方案:
// 创建请求队列时设置合理的缓存大小
File cacheDir = new File(context.getCacheDir(), "volley");
// 设置缓存大小为10MB
RequestQueue requestQueue = Volley.newRequestQueue(context, new BasicNetwork(new HurlStack()), 10 * 1024 * 1024);
// 自定义缓存类,优化清理策略
public class OptimizedDiskBasedCache extends DiskBasedCache {
// 其他代码...
@Override
protected void pruneIfNeeded(int neededSpace) {
// 优化清理策略:先删除超过最大时间窗口的缓存
long currentTime = System.currentTimeMillis();
// 首先删除超过最大时间窗口的缓存条目
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext() && mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
// 如果缓存条目超过最大时间窗口(例如7天)
if (currentTime - e.serverDate > MAX_TIME_WINDOW) {
boolean deleted = e.file.delete();
if (deleted) {
mTotalSize -= e.size;
}
iterator.remove();
}
}
// 如果空间仍然不足,再按LRU策略删除
if (mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
super.pruneIfNeeded(neededSpace);
}
}
// 其他代码...
}
shouldCache
属性设置,确保只有必要的数据被缓存。// 对于不需要缓存的请求,设置shouldCache为false
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/sensitive-data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
// 禁用缓存
request.setShouldCache(false);
requestQueue.add(request);
不同类型的数据具有不同的更新频率和时效性要求,应根据数据特性设置不同的缓存策略。
// 对于频繁更新的数据,设置较短的缓存时间
CustomExpiryRequest request = new CustomExpiryRequest(
Request.Method.GET,
"https://api.example.com/latest-news",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
30 * 1000, // 软过期时间30秒
5 * 60 * 1000 // 硬过期时间5分钟
);
// 对于不常更新的数据,设置较长的缓存时间
CustomExpiryRequest request = new CustomExpiryRequest(
Request.Method.GET,
"https://api.example.com/app-config",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
24 * 60 * 60 * 1000, // 软过期时间24小时
7 * 24 * 60 * 60 * 1000 // 硬过期时间7天
);
// 对于一次性数据,禁用缓存
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/verification-code",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
// 禁用缓存
request.setShouldCache(false);
requestQueue.add(request);
对于可能会更新但更新频率不高的数据,使用条件请求可以显著提高缓存效率。
// 使用ETag实现条件请求
public class ETagRequest extends StringRequest {
private final String mETag;
public ETagRequest(int method, String url, Response.Listener<String> listener,
Response.ErrorListener errorListener, String eTag) {
super(method, url, listener, errorListener);
mETag = eTag;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
// 添加ETag到请求头
if (mETag != null) {
headers.put("If-None-Match", mETag);
}
return headers;
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
Response<String> parsedResponse = super.parseNetworkResponse(response);
// 保存新的ETag
if (parsedResponse != null && parsedResponse.cacheEntry != null) {
String newETag = response.headers.get("ETag");
if (newETag != null) {
parsedResponse.cacheEntry.etag = newETag;
}
}
return parsedResponse;
}
}
// 使用示例
public void fetchDataWithETag() {
// 从缓存中获取ETag
Cache.Entry entry = mCache.get("data_cache_key");
String eTag = entry != null ? entry.etag : null;
// 创建ETag请求
ETagRequest request = new ETagRequest(
Request.Method.GET,
"https://api.example.com/data",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
eTag
);
requestQueue.add(request);
}
根据网络状态和用户行为,实现智能缓存策略,可以进一步提高应用性能和用户体验。
// 智能缓存请求类
public class SmartCacheRequest extends StringRequest {
private final Context mContext;
public SmartCacheRequest(Context context, int method, String url,
Response.Listener<String> listener,
Response.ErrorListener errorListener) {
super(method, url, listener, errorListener);
mContext = context.getApplicationContext();
}
@Override
public Priority getPriority() {
// 根据网络状态调整请求优先级
if (!isNetworkConnected()) {
// 无网络连接,从缓存获取
return Priority.IMMEDIATE;
}
// 有网络连接,根据数据时效性调整优先级
Cache.Entry entry = mCache.get(getCacheKey());
if (entry != null && !entry.isExpired()) {
// 缓存有效,降低请求优先级
return Priority.LOW;
}
// 缓存无效,使用默认优先级
return super.getPriority();
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
Response<String> parsedResponse = super.parseNetworkResponse(response);
// 根据网络状态和数据特性调整缓存时间
if (parsedResponse != null && parsedResponse.cacheEntry != null) {
if (!isNetworkConnected() || isNetworkMetered()) {
// 无网络或使用计量网络,延长缓存时间
parsedResponse.cacheEntry.softTtl = System.currentTimeMillis() + 24 * 60 * 60 * 1000;
parsedResponse.cacheEntry.ttl = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000;
} else {
// 使用Wi-Fi,缩短缓存时间
parsedResponse.cacheEntry.softTtl = System.currentTimeMillis() + 5 * 60 * 1000;
parsedResponse.cacheEntry.ttl = System.currentTimeMillis() + 60 * 60 * 1000;
}
}
return parsedResponse;
}
// 检查网络连接状态
private boolean isNetworkConnected() {
ConnectivityManager cm = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
// 检查是否使用计量网络
private boolean isNetworkMetered() {
ConnectivityManager cm = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.isActiveNetworkMetered();
}
}
在开发和测试阶段,监控和调试缓存可以帮助我们发现和解决缓存相关的问题。
// 缓存监控工具类
public class CacheMonitor {
private final Cache mCache;
public CacheMonitor(Cache cache) {
mCache = cache;
}
// 获取缓存大小
public long getCacheSize() {
if (mCache instanceof DiskBasedCache) {
return ((DiskBasedCache) mCache).getTotalSize();
}
return 0;
}
// 获取缓存条目数量
public int getCacheEntryCount() {
if (mCache instanceof DiskBasedCache) {
return ((DiskBasedCache) mCache).getEntryCount();
}
return 0;
}
// 打印缓存信息
public void printCacheInfo() {
Log.d("CacheMonitor", "Cache size: " + getCacheSize() + " bytes");
Log.d("CacheMonitor", "Cache entry count: " + getCacheEntryCount());
if (mCache instanceof DiskBasedCache) {
DiskBasedCache diskCache = (DiskBasedCache) mCache;
Map<String, DiskBasedCache.CacheHeader> entries = diskCache.getAllEntries();
Log.d("CacheMonitor", "Cache entries:");
for (Map.Entry<String, DiskBasedCache.CacheHeader> entry : entries.entrySet()) {
Log.d("CacheMonitor", " Key: " + entry.getKey());
Log.d("CacheMonitor", " Size: " + entry.getValue().size + " bytes");
Log.d("CacheMonitor", " Server Date: " + new Date(entry.getValue().serverDate));
Log.d("CacheMonitor", " Soft TTL: " + new Date(entry.getValue().softTtl));
Log.d("CacheMonitor", " TTL: " + new Date(entry.getValue().ttl));
}
}
}
// 验证缓存条目
public boolean validateCacheEntry(String key) {
Cache.Entry entry = mCache.get(key);
if (entry == null) {
Log.d("CacheMonitor", "Cache entry not found for key: " + key);
return false;
}
boolean isValid = true;
if (entry.isExpired()) {
Log.d("CacheMonitor", "Cache entry expired for key: " + key);
isValid = false;
}
if (entry.data == null || entry.data.length == 0) {
Log.d("CacheMonitor", "Cache entry data is empty for key: " + key);
isValid = false;
}
return isValid;
}
}
在适当的时机清理不再需要的缓存,可以释放存储空间,提高应用性能。
// 缓存清理工具类
public class CacheCleaner {
private final Cache mCache;
public CacheCleaner(Cache cache) {
mCache = cache;
}
// 清理过期的缓存
public void cleanExpiredCache() {
if (mCache instanceof DiskBasedCache) {
DiskBasedCache diskCache = (DiskBasedCache) mCache;
Map<String, DiskBasedCache.CacheHeader> entries = diskCache.getAllEntries();
for (Map.Entry<String, DiskBasedCache.CacheHeader> entry : entries.entrySet()) {
Cache.Entry cacheEntry = mCache.get(entry.getKey());
if (cacheEntry != null && cacheEntry.isExpired()) {
mCache.remove(entry.getKey());
}
}
}
}
// 清理特定前缀的缓存
public void cleanCacheByPrefix(String prefix) {
if (mCache instanceof DiskBasedCache) {
DiskBasedCache diskCache = (DiskBasedCache) mCache;
Map<String, DiskBasedCache.CacheHeader> entries = diskCache.getAllEntries();
for (Map.Entry<String, DiskBasedCache.CacheHeader> entry : entries.entrySet()) {
if (entry.getKey().startsWith(prefix)) {
mCache.remove(entry.getKey());
}
}
}
}
// 清理超过一定大小的缓存
public void cleanLargeCache(int maxSizeInBytes) {
if (mCache instanceof DiskBasedCache) {
DiskBasedCache diskCache = (DiskBasedCache) mCache;
Map<String, DiskBasedCache.CacheHeader> entries = diskCache.getAllEntries();
// 按大小降序排列
List<Map.Entry<String, DiskBasedCache.CacheHeader>> sortedEntries =
new ArrayList<>(entries.entrySet());
Collections.sort(sortedEntries, new Comparator<Map.Entry<String, DiskBasedCache.CacheHeader>>() {
@Override
public int compare(Map.Entry<String, DiskBasedCache.CacheHeader> e1,
Map.Entry<String, DiskBasedCache.CacheHeader> e2) {
return Long.compare(e2.getValue().size, e1.getValue().size);
}
});
// 清理大的缓存条目
long currentSize = diskCache.getTotalSize();
for (Map.Entry<String, DiskBasedCache.CacheHeader> entry : sortedEntries) {
if (currentSize <= maxSizeInBytes) {
break;
}
long entrySize = entry.getValue().size;
mCache.remove(entry.getKey());
currentSize -= entrySize;
}
}
}
}
Android Volley的缓存机制是一个强大而灵活的组件,通过合理配置和优化,可以显著提高应用的性能和用户体验。本文从源码级别深入分析了Volley缓存的读取与更新机制,包括缓存读取的入口、缓存调度器的工作流程、缓存读取的核心方法、缓存状态判断、缓存更新的触发条件、缓存更新的核心方法、软过期时的缓存更新以及手动触发缓存更新等方面。
通过对Volley缓存机制的深入理解,我们可以根据不同的应用场景和数据特性,选择合适的缓存策略,优化缓存读取和更新的性能,解决常见的缓存问题,并实现智能缓存管理。同时,我们还介绍了缓存读取与更新的最佳实践,包括根据数据特性设置缓存策略、使用条件请求提高缓存效率、实现智能缓存策略、监控和调试缓存以及清理不再需要的缓存等方面。
掌握Volley缓存机制的原理和优化方法,对于开发高性能、低流量消耗的Android应用具有重要意义。希望本文能够帮助开发者更深入地理解和使用Volley的缓存功能,提升Android应用的质量和用户体验。