天天看点

Volley源码(二)--考虑缓存

在上一篇文章中Volley源码(一)不考虑缓存,以StringRequest为例,讲解了发送不缓存的post请求。这次主要讲解,发送缓存的get请求。为什么这么说。直接翻daima

if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
  }
           
public final boolean shouldCache() {
        
        //Allow caching only if method is a GET request
        if(mMethod == Method.GET) {
            return mShouldCache & true;
        }
        return false;
    }
           

mShouldCache默认为true。所以,在shouldCache方法中,GET请求的话就return true(缓存),POST请求的话就return false(不缓存)。接下来来看看有缓存的GET请求有哪些地方不一样

1.RequestQueue的add方法

public Request add(Request request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.sDebug) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }
           

上篇文章中,因为不需要缓存,所以在

if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
}
           

这里直接return了,这次是GET请求,需要缓存,所以if代码块里面的代码不会被执行到,继续往下走,先拿取request的cacheKey(点进去自己看看,其实就是url)。看mWaitingRequests是否包含这个url。mWaitingRequests是一个HashMap,key是这个请求的url,value是同样请求url的Request的链表。

Volley源码(二)--考虑缓存

mWaitingRequests包含这个url的话,就将这个url的请求加到此url对应的LinkedList中去,不包含的话,将此url加到mWaitingRequests(对应的value是null)和mCacheQueue中。

mWaitingRequests包含这个url的话说明这个request还没请求完毕,因为在RequestQueue的finish方法中有从mWaitingRequests中remove这个request,这个finish方法只有请求执行完毕才会被调用(参考NetworkDispatcher的run方法)。

void finish(Request request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.sDebug) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }
           

2.从缓存队列中取数据

数据加到mCacheQueue后,CacheDispatcher中的run方法就能取到数据啦

public void run() {
        if (VolleyLog.sDebug) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response);
                } else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
           

先不管mCache是什么(肯定提供了get和put方法,用来缓存的嘛,肯定有存有取)

首先从mCacheQueue取到之前add进去的request,再从缓存中拿数据(缓存用到的key还是url),

Cache.Entry entry = mCache.get(request.getCacheKey());      

再对entry做一系列判断,entry为空(表明之前没有缓存过),缓存过期了(entry.isExpired),把请求加到mNetworkDispatcher(等于把请求丢到mNetworkDispatcher里啦)。NetworkDispatcher的run方法中,肯定有对此请求有做缓存

if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
}
           

看到了吧,还是通过mCache缓存该请求。另一方面,如果entry不为空(表明之前有缓存过),或者缓存没有过期。该怎么请求还是怎么请求,只是多一个处理,就是该缓存是否需要更新一下

if (!entry.refreshNeeded()) {
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response);
} else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
 }
           

可以看到,虽然这个类叫CacheDispatcher,实际上,缓存的操作都在NetworkDispatcher里进行的,entry需要刷新的话,将entry设置给request,然后post一个ruannable(为什么不直接加到mNetworkQueue,非要post里面加到mNetworkQueue?因为NetworkDispatcher有用到缓存,这样做可以确保将entry设置给request后,NetworkDispatcher才取到request的)