Android Volley缓存与网络请求的高效协同全解析(19)

76 阅读16分钟

探秘!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基于HttpURLConnectionHttpClient实现了实际的网络请求功能,它如同数据的“搬运工”,从服务器获取数据。

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支持条件请求。条件请求通过在请求头中添加ETagLast-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接口提供了invalidateremove方法来实现这些功能。

// 使缓存条目失效
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();
}

这些线程会从网络队列中各自取出请求进行处理,并且在处理过程中,缓存与网络请求的协同逻辑依然适用。每个请求都会根据自身的缓存策略,决定是使用缓存数据还是发起网络请求,同时在获取新数据后更新缓存,确保多个请求之间的缓存与网络请求协同不会相互干扰,保证了应用在高并发请求场景下的稳定性和性能 。