探秘!Android Volley缓存与网络请求的高效协同全解析
一、开篇:为何聚焦Volley缓存与网络请求协同
在移动应用开发领域,网络请求如同应用的“生命线”,源源不断地为应用输送数据。然而,频繁的网络请求不仅会消耗用户流量,还会降低应用响应速度,影响用户体验。Android Volley作为一款备受开发者青睐的网络请求库,其缓存与网络请求的协同机制堪称“性能优化利器”。通过巧妙地管理缓存和网络请求的交互,Volley能大幅减少不必要的网络请求,提升数据加载效率。本文将深入Volley源码,逐行剖析缓存与网络请求协同的底层逻辑,带你揭开这一机制的神秘面纱。
二、Volley核心架构:协同的基础
2.1 请求队列:请求的“调度中心”
Volley的RequestQueue类是整个网络请求与缓存协同工作的核心枢纽,它就像一个智能的“调度中心”,负责管理所有的网络请求。
public class RequestQueue {
// 缓存队列,用于存放可缓存的网络请求
private final BlockingQueue<Request<?>> mCacheQueue = new LinkedBlockingQueue<>();
// 网络队列,存放需要进行网络请求的任务
private final BlockingQueue<Request<?>> mNetworkQueue = new LinkedBlockingQueue<>();
// 缓存对象,用于读写缓存数据
private final Cache mCache;
// 网络对象,负责实际的网络请求操作
private final Network mNetwork;
// 响应分发器,将处理后的响应数据分发到主线程
private final ResponseDelivery mDelivery;
// 缓存调度器线程,处理缓存队列中的请求
private CacheDispatcher mCacheDispatcher;
// 网络调度器线程数组,处理网络队列中的请求
private NetworkDispatcher[] mNetworkDispatchers;
// 构造函数,初始化请求队列相关组件
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDelivery = delivery;
// 创建缓存调度器线程
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
// 创建并启动多个网络调度器线程
mNetworkDispatchers = new NetworkDispatcher[threadPoolSize];
for (int i = 0; i < threadPoolSize; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mNetworkDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
// 将请求添加到请求队列中
public <T> Request<T> add(Request<T> request) {
// 设置请求所属的请求队列
request.setRequestQueue(this);
// 判断请求是否可缓存,决定放入哪个队列
if (request.shouldCache()) {
mCacheQueue.add(request);
} else {
mNetworkQueue.add(request);
}
return request;
}
// 启动请求队列,开始处理请求
public void start() {
mCacheDispatcher.start();
for (NetworkDispatcher networkDispatcher : mNetworkDispatchers) {
networkDispatcher.start();
}
}
}
从上述源码可以看出,RequestQueue在初始化时,会创建缓存调度器和多个网络调度器线程。当有新的请求加入时,根据请求是否可缓存,将其分别放入缓存队列或网络队列,为后续的缓存与网络请求协同处理奠定基础。
2.2 缓存模块:数据的“临时仓库”
Volley的缓存模块通过Cache接口定义了一系列操作规范,默认的实现类DiskBasedCache基于文件系统实现了缓存功能,它就像是数据的“临时仓库”,保存着网络请求的响应数据。
public class DiskBasedCache implements Cache {
// 缓存目录,用于存储缓存文件
private final File mRootDirectory;
// 缓存的最大容量(字节)
private final int mMaxCacheSizeInBytes;
// 当前缓存已使用的大小(字节)
private long mTotalSize = 0;
// 缓存条目映射表,通过缓存键快速查找缓存条目
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, 0.75f, true);
// 构造函数,初始化缓存相关参数
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
// 从缓存中获取指定键的数据条目
@Override
public Entry get(String key) {
// 根据缓存键生成对应的缓存文件
File file = getFileForKey(key);
// 如果文件不存在,说明缓存中没有该数据
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;
}
// 读取缓存数据部分
byte[] data = streamToBytes(fis, (int) (file.length() - fis.available()));
// 创建缓存条目对象并填充数据
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) {
}
}
}
}
// 将数据存入缓存
@Override
public 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) {
}
}
}
}
// 如果缓存空间不足,清理部分缓存
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在存储数据时,会先检查缓存空间,不足时按策略清理,然后将数据写入文件;读取数据时,从文件中解析头部信息和实际数据,为缓存与网络请求的协同提供了数据存储和读取的基础。
2.3 网络模块:数据的“搬运工”
网络模块通过Network接口定义了网络请求的操作,默认实现类BasicNetwork基于HttpURLConnection或HttpClient实现了实际的网络请求功能,它如同数据的“搬运工”,从服务器获取数据。
public class BasicNetwork implements Network {
// HTTP堆栈对象,用于发送HTTP请求
private final HttpStack mHttpStack;
// 重试策略,用于处理请求失败时的重试逻辑
private final RetryPolicy mRetryPolicy;
// 构造函数,初始化HTTP堆栈和重试策略
public BasicNetwork(HttpStack httpStack) {
this(httpStack, new DefaultRetryPolicy());
}
public BasicNetwork(HttpStack httpStack, RetryPolicy retryPolicy) {
mHttpStack = httpStack;
mRetryPolicy = retryPolicy;
}
// 执行网络请求
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
// 记录请求开始时间
long requestStart = SystemClock.elapsedRealtime();
while (true) {
try {
// 构建HTTP请求
HttpStack.HttpRequestStackEntry entry = mHttpStack.performRequest(request, new HashMap<String, String>());
// 获取HTTP响应
HttpResponse httpResponse = entry.response;
// 获取响应状态码
int statusCode = httpResponse.getStatusLine().getStatusCode();
// 读取响应内容
byte[] responseContents = EntityUtils.toByteArray(httpResponse.getEntity());
// 解析响应头部信息
Map<String, String> responseHeaders = entry.responseHeaders;
// 如果响应状态码在200 - 299之间,表示请求成功
if (statusCode >= 200 && statusCode < 300) {
return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
}
// 如果响应状态码为304,表示缓存未失效
if (statusCode == 304) {
return new NetworkResponse(statusCode, null, responseHeaders, true);
}
// 其他错误状态码,抛出异常
throw new IOException();
} catch (IOException e) {
// 处理请求失败的情况,根据重试策略进行重试
request.addMarker("network-http-attempt-failed");
if (!request.shouldRetryNetworkException(new VolleyError(e))) {
throw new VolleyError(e);
}
if (mRetryPolicy.hasAttemptRemaining()) {
try {
mRetryPolicy.retry(new VolleyError(e));
} catch (VolleyError retryError) {
throw retryError;
}
} else {
throw new VolleyError(e);
}
}
}
}
}
BasicNetwork在执行请求时,会构建HTTP请求,处理响应,并根据响应状态码和重试策略决定是否重试,为获取最新数据提供保障,与缓存模块协同工作。
三、缓存与网络请求协同的具体流程
3.1 请求加入队列:缓存与网络的“抉择”
当开发者创建一个网络请求并调用RequestQueue.add(request)方法将其加入请求队列时,Volley会根据请求的shouldCache属性判断该请求是否可缓存。
public <T> Request<T> add(Request<T> request) {
// 设置请求所属的请求队列
request.setRequestQueue(this);
// 判断请求是否可缓存
if (request.shouldCache()) {
// 可缓存的请求放入缓存队列
mCacheQueue.add(request);
} else {
// 不可缓存的请求直接放入网络队列
mNetworkQueue.add(request);
}
return request;
}
若请求可缓存,它将进入缓存队列,等待缓存调度器处理;若不可缓存,则直接进入网络队列,由网络调度器进行网络请求操作。这一过程就像是一个“分流站”,将请求合理地分配到不同的处理路径,开启了缓存与网络请求协同的第一步。
3.2 缓存调度器:缓存优先的“执行者”
缓存调度器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;
// 构造函数,初始化相关组件
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);
// 解析缓存数据,生成响应
Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
// 如果缓存需要刷新(软过期)
if (entry.refreshNeeded()) {
request.addMarker("cache-hit-refresh-needed");
// 标记响应为中间响应(表示后续还会有更新)
response.intermediate = true;
// 将当前缓存数据的响应分发给主线程,让用户先看到旧数据
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
// 再次将请求放入网络队列,获取最新数据更新缓存
mNetworkQueue.put(request);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
} else {
// 缓存过期但不需要刷新,直接将缓存数据的响应分发给主线程
mDelivery.postResponse(request, response);
}
continue;
}
// 缓存未过期,直接解析缓存数据生成响应并分发给主线程
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(new Network
3.2 缓存调度器:缓存优先的“执行者”(续)
Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
// 缓存未过期,直接将响应分发给主线程
mDelivery.postResponse(request, response);
} catch (InterruptedException e) {
// 如果线程被中断且退出标志为真,结束线程
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
// 否则继续处理下一个请求
continue;
}
}
}
}
在这个过程中,缓存调度器就像一个“精明的管家”,先检查缓存中是否存在请求的数据。若存在且未过期,直接将数据返回给应用,避免了网络请求;若数据过期,它会先将过期数据返回给应用,同时将请求转发到网络队列,让网络调度器去获取最新数据并更新缓存,实现了缓存与网络请求的无缝衔接。
3.3 网络调度器:数据更新的“主力军”
网络调度器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;
// 构造函数,初始化相关组件
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) {
Request<?> request;
try {
// 从网络队列中取出一个请求(阻塞操作,若无请求则等待)
request = mQueue.take();
request.addMarker("network-queue-take");
// 如果请求已被取消,跳过该请求
if (request.isCanceled()) {
request.finish("network-discard-canceled");
continue;
}
// 执行网络请求,获取服务器响应
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-attempt");
// 如果服务器返回304状态码,表示缓存未失效
if (networkResponse.notModified) {
request.addMarker("network-304-not-modified");
// 从缓存中获取对应的缓存条目
Cache.Entry entry = mCache.get(request.getCacheKey());
// 解析缓存数据,生成响应
Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
// 将响应分发给主线程
mDelivery.postResponse(request, response);
} else {
// 服务器返回其他状态码,解析网络响应数据
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");
}
// 将响应分发给主线程
mDelivery.postResponse(request, response);
}
} catch (VolleyError volleyError) {
// 处理网络请求错误
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - request.getStartTime());
parseAndDeliverNetworkError(request, volleyError);
} catch (InterruptedException e) {
// 如果线程被中断且退出标志为真,结束线程
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
// 否则继续处理下一个请求
continue;
} catch (Exception e) {
// 处理其他异常情况
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - request.getStartTime());
mDelivery.postError(request, volleyError);
}
}
}
// 处理网络请求错误并分发错误响应
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
Response<?> response = request.parseNetworkError(error);
mDelivery.postError(request, response);
}
}
当网络调度器从网络队列中取出请求后,会执行网络请求。若服务器返回304状态码,说明缓存数据仍然有效,直接使用缓存数据并分发响应;若返回其他状态码,在解析响应数据后,如果请求需要缓存,会将新数据存入缓存,然后再将响应分发给主线程,确保了缓存与网络请求获取的数据一致性。
3.4 响应分发:数据呈现的“最后一环”
响应分发器ResponseDelivery负责将处理后的响应数据分发到主线程,以便更新UI或进行其他业务逻辑处理,是数据呈现的“最后一环”。
public class ExecutorDelivery implements ResponseDelivery {
// 主线程的Handler,用于在主线程执行任务
private final Handler mMainThreadHandler;
// 后台线程的Executor,用于执行非UI相关任务
private final Executor mExecutor;
// 构造函数,初始化Handler和Executor
public ExecutorDelivery(Handler mainThreadHandler) {
mMainThreadHandler = mainThreadHandler;
mExecutor = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
}
// 分发正常响应
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
// 分发响应,并可指定在响应分发后执行的Runnable任务
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
// 如果响应是中间响应(例如缓存过期但先返回旧数据)
if (response.intermediate) {
// 在后台线程执行响应分发
mExecutor.execute(new ResponseDeliveryRunnable(request, response, runnable));
} else {
// 在主线程执行响应分发
mMainThreadHandler.post(new ResponseDeliveryRunnable(request, response, runnable));
}
}
// 分发错误响应
@Override
public void postError(Request<?> request, VolleyError error) {
// 创建错误响应对象
Response<?> response = Response.error(error);
// 在主线程分发错误响应
mMainThreadHandler.post(new ResponseDeliveryRunnable(request, response, null));
}
// 响应分发的Runnable任务类
private class ResponseDeliveryRunnable implements Runnable {
private final Request<?> mRequest;
private final Response<?> mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request<?> request, Response<?> response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@Override
public void run() {
// 如果请求已被取消,直接结束
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// 调用请求的响应监听器处理响应
mRequest.deliverResponse(mResponse.result);
// 如果有指定的Runnable任务,执行该任务
if (mRunnable != null) {
mRunnable.run();
}
// 标记请求已完成响应分发
mRequest.finish("done");
}
}
}
无论是缓存调度器还是网络调度器处理后的响应,最终都由响应分发器传递到主线程。对于中间响应,它会在后台线程处理;对于正常响应和错误响应,则在主线程处理,确保了应用能够及时、正确地展示数据或反馈错误信息,完成了缓存与网络请求协同的整个流程。
四、特殊场景下的协同处理
4.1 条件请求:减少不必要的网络流量
在实际应用中,有些数据更新频率较低,为了避免每次都从服务器获取数据,Volley支持条件请求。条件请求通过在请求头中添加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;
}
}
当网络调度器执行这类请求时,如果服务器返回304状态码,说明缓存数据有效,直接使用缓存数据,减少了不必要的网络流量;只有当服务器返回200状态码时,才更新缓存并使用新数据,实现了更高效的缓存与网络请求协同。
4.2 缓存失效与强制更新
有时候,开发者需要手动使缓存失效,或者强制更新缓存数据。Volley的Cache接口提供了invalidate和remove方法来实现这些功能。
// 使缓存条目失效
public void invalidate(String key, boolean fullExpire) {
// 从缓存中获取对应的缓存头部信息
CacheHeader entry = mEntries.get(key);
if (entry != null) {
// 如果是完全过期
if (fullExpire) {
entry.softTtl = 0;
entry.ttl = 0;
} else {
// 否则只设置软过期时间为0
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());
}
}
}
}
// 从缓存中移除指定键的条目
public 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);
}
}
}
当调用invalidate方法时,缓存数据会被标记为过期,下次请求时会发起网络请求获取新数据;调用remove方法则直接从缓存中删除数据。这些操作在需要更新缓存策略或处理数据变更时,保证了缓存与网络请求的协同能够满足实际业务需求 。
4.3 多请求并发处理
在实际应用中,可能会同时发起多个网络请求,Volley通过请求队列和多线程机制来处理这种情况。多个网络调度器线程可以同时处理网络队列中的请求,提高了请求处理的效率。
// RequestQueue中创建网络调度器线程数组
mNetworkDispatchers = new NetworkDispatcher[threadPoolSize];
for (int i = 0; i < threadPoolSize; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mNetworkDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
这些线程会从网络队列中各自取出请求进行处理,并且在处理过程中,缓存与网络请求的协同逻辑依然适用。每个请求都会根据自身的缓存策略,决定是使用缓存数据还是发起网络请求,同时在获取新数据后更新缓存,确保多个请求之间的缓存与网络请求协同不会相互干扰,保证了应用在高并发请求场景下的稳定性和性能 。