Volley之CacheDispatcher缓存

QuibblerQuibbler 2021-02-06 约 50 分钟 1608 次阅读

Volley之CacheDispatcher缓存

在源码解析Volley请求队列RequestQueue一文最后,我们看了向请求队列添加Request网络请求的逻辑。

<T> void beginRequest(Request<T> request) {
        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            sendRequestOverNetwork(request);
        } else {
            mCacheQueue.add(request);
        }
    }

如果请求开启缓存,那么会先将请求放到缓存队列中处理,缓存队列没有或者缓存过期的话再放到网络请求队列中处理并返回。


1、CacheDispatcher

CacheDispatcher也是继承自Thread。

1.1、CacheDispatcher初始化

在RequestQueue的start()方法中创建一个缓存调度程序CacheDispatcher,以及默认4个网络请求调度程序NetworkDispatcher。

/** Starts the dispatchers in this queue. */
    public void start() {
        stop(); // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();
        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher =
                    new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

CacheDispatcher的构造函数如下,将RequestQueue中的全局变量:缓存队列、网络请求队列、缓存、结果回调分发器,传入CacheDispatcher中处理缓存使用。

/**
     * Creates a new cache triage dispatcher thread. You must call {@link #start()} in order to
     * begin processing.
     *
     * @param cacheQueue Queue of incoming requests for triage
     * @param networkQueue Queue to post requests that require network to
     * @param cache Cache interface to use for resolution
     * @param delivery Delivery interface to use for posting responses
     */
    public CacheDispatcher(
            BlockingQueue<Request<?>> cacheQueue,
            BlockingQueue<Request<?>> networkQueue,
            Cache cache,
            ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
        mWaitingRequestManager = new WaitingRequestManager(this, networkQueue, delivery);
    }

1.2、CacheDispatcher成员

CacheDispatcher类前4个成员都是从RequestQueue初始化获得:

/** The queue of requests coming in for triage. */
    private final BlockingQueue<Request<?>> mCacheQueue;
    
    /** The queue of requests going out to the network. */
    private final BlockingQueue<Request<?>> mNetworkQueue;
    
    /** The cache to read from. */
    private final Cache mCache;
    
    /** For posting responses. */
    private final ResponseDelivery mDelivery;
    
    /** Used for telling us to die. */
    private volatile boolean mQuit = false;
    
    /** Manage list of waiting requests and de-duplicate requests with same cache key. */
    private final WaitingRequestManager mWaitingRequestManager;

最后一个WaitingRequestManager类,用来处理缓存miss时,需要通过网络请求,网络结果返回时回调的处理。

1.3、启动CacheDispatcher

重写Thread的run()方法,开启无限循环,从阻塞队列中取数据处理。在这里对Cache进行初始化

@Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        
        // 进行阻塞调用,初始化缓存。
        mCache.initialize();
        
        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of CacheDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }

1.4、终止CacheDispatcher

CacheDispatcher继承自线程,quit()方法中就是调用自身线程的interrupt()方法,并将退出标志位置为true。

/**
     * Forces this dispatcher to quit immediately. If any requests are still in the queue, they are
     * not guaranteed to be processed.
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

2、缓存处理

启动缓存调度程序后,开启无线循环,processRequest()方法从mCacheQueue缓存队列中取出缓存请求处理。

2.1、二级调用processRequest()

先调用processRequest()方法,内部先从mCacheQueue缓存队列拿出一个Request实例

// Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
    // This is needed to avoid keeping previous request references alive for an indeterminate amount
    // of time. Update consumer-proguard-rules.pro when modifying this. See also
    // https://github.com/google/volley/issues/114
    private void processRequest() throws InterruptedException {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.
        final Request<?> request = mCacheQueue.take();
        processRequest(request);
    }

将缓存实例传递给processRequest(final Request<?> request)处理,完整方法一共93行:

@VisibleForTesting
    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
        try {
            // If the request has been canceled, don't bother dispatching it.
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                return;
            }
            // 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.
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
            // Use a single instant to evaluate cache expiration. Otherwise, a cache entry with
            // identical soft and hard TTL times may appear to be valid when checking isExpired but
            // invalid upon checking refreshNeeded(), triggering a soft TTL refresh which should be
            // impossible.
            long currentTimeMillis = System.currentTimeMillis();
            // If it is completely expired, just send it to the network.
            if (entry.isExpired(currentTimeMillis)) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
            // 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 (!response.isSuccess()) {
                request.addMarker("cache-parsing-failed");
                mCache.invalidate(request.getCacheKey(), true);
                request.setCacheEntry(null);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
            if (!entry.refreshNeeded(currentTimeMillis)) {
                // 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;
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    // 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) {
                                        // Restore the interrupted status
                                        Thread.currentThread().interrupt();
                                    }
                                }
                            });
                } else {
                    // request has been added to list of waiting requests
                    // to receive the network response from the first request once it returns.
                    mDelivery.postResponse(request, response);
                }
            }
        } finally {
            request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
        }
    }

和NetworkDispatcher一样,每次处理请求都会先判断请求是否已被取消,在多处关键节点都会判断请求是否取消。

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

下面逐段分解该方法,看看如何处理缓存逻辑的:

2.2、缓存miss

从mCache缓存中取当前Request请求对应的Key,如果缓存中没有那么将其放入到网络请求队列mNetworkQueue中。Volley网络请求逻辑见另一篇博客Volley之网络请求NetworkDispatcher。

// 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.
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }

2.3、缓存失效

接着,如果有缓存,会判断缓存Cache.Entry是否有效。如果失效就将请求放入到网络请求队列中处理。并且将其放入WaitingRequestManager中标记等待网络请求结果返回后回调。

long currentTimeMillis = System.currentTimeMillis();
            // If it is completely expired, just send it to the network.
            if (entry.isExpired(currentTimeMillis)) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }

Entry是定义在Cache中的缓存实体类,内部存放数据、最大生命有效期TTL等信息。

/** Data and metadata for an entry returned by the cache. */
    class Entry {
        /** The data returned from cache. */
        public byte[] data;
        /** ETag for cache coherency. */
        public String etag;
        /** Date of this response as reported by the server. */
        public long serverDate;
        /** The last modified date for the requested object. */
        public long lastModified;
        /** TTL for this record. */
        public long ttl;
        
        ...
        
        /** True if the entry is expired. */
        public boolean isExpired() {
            return isExpired(System.currentTimeMillis());
        }
        boolean isExpired(long currentTimeMillis) {
            return this.ttl < currentTimeMillis;
        }
        ...
    }

2.4、WaitingRequestManager

获取缓存失败都会将请求放入到WaitingRequestManager中管理,该类实现Request.NetworkRequestCompleteListener接口

/** Callback to notify when the network request returns. */
    /* package */ interface NetworkRequestCompleteListener {
        /** Callback when a network response has been received. */
        void onResponseReceived(Request<?> request, Response<?> response);
        /** Callback when request returns from network without valid response. */
        void onNoUsableResponseReceived(Request<?> request);
    }

获取缓存失败的情况都会调用WaitingRequestManager的maybeAddToWaitingRequests(Request<?> request)方法,将其放入到等待请求队列中,等待网络结果请求返回回调处理缓存相关的逻辑:

synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
        String cacheKey = request.getCacheKey();
        // Insert request into stage if there's already a request with the same cache key
        // in flight.
        if (mWaitingRequests.containsKey(cacheKey)) {
            // There is already a request in flight. Queue up.
            List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            if (stagedRequests == null) {
                stagedRequests = new ArrayList<>();
            }
            request.addMarker("waiting-for-response");
            stagedRequests.add(request);
            mWaitingRequests.put(cacheKey, stagedRequests);
            if (VolleyLog.DEBUG) {
                VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
            }
            return true;
        } else {
            // Insert 'null' queue for this cacheKey, indicating there is now a request in
            // flight.
            mWaitingRequests.put(cacheKey, null);
            request.setNetworkRequestCompleteListener(this);
            if (VolleyLog.DEBUG) {
                VolleyLog.d("new request, sending to network %s", cacheKey);
            }
            return false;
        }
    }

如果请求已在该等待请求队列中,那么刷新。否者将当前请求加入到等待请求队列中。

3、回调处理

有两条回调路径:缓存命中回调 和 缓存miss后的网络请求结果回调。

3.1、缓存命中回调

①对于获取缓存后过期的缓存命中。只需提供回调响应即可。

if (!entry.refreshNeeded(currentTimeMillis)) {
                // Completely unexpired cache hit. Just deliver the response.
                mDelivery.postResponse(request, response);
            }

②将缓存命中的响应发回给用户并进行传递,然后再将请求转发到网络,这一步应该是为了刷新缓存。

if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    // 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) {
                                        // Restore the interrupted status
                                        Thread.currentThread().interrupt();
                                    }
                                }
                            });
                }

③该请求已添加到等待请求的列表中,以便在第一个请求返回时从第一个请求接收网络响应。

// request has been added to list of waiting requests
                    // to receive the network response from the first request once it returns.
                    mDelivery.postResponse(request, response);

这三种都是通过ExecutorDelivery回调Request分发相应,见另一篇博客Volley之网络请求NetworkDispatcher。网络请求完毕之后再回到缓存回调中处理,继续3.2节代码解读。

3.2、网络请求结果回调:WaitingRequestManager

在Volley之网络请求NetworkDispatcher一文第三节网络结果回调中,在NetworkDispatcher的processRequest(Request<?> request)方法中有一步操作是将网络结果通过WaitingRequestManager回调通知CacheDispatcher。并且在这一步将响应写入缓存(需要的话)。

// Write to cache if applicable.
            // TODO: Only update cache metadata instead of entire record for 304s.
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }
            ...
            request.notifyListenerResponseReceived(response);

调用Request中的NetworkRequestCompleteListener,这个监听器就是在前面缓存失效时给Request所设置的WaitingRequestManager,WaitingRequestManager类实现NetworkRequestCompleteListener接口。

/**
     * Notify NetworkRequestCompleteListener that a valid response has been received which can be
     * used for other, waiting requests.
     *
     * @param response received from the network
     */
    /* package */ void notifyListenerResponseReceived(Response<?> response) {
        NetworkRequestCompleteListener listener;
        synchronized (mLock) {
            listener = mRequestCompleteListener;
        }
        if (listener != null) {
            listener.onResponseReceived(this, response);
        }
    }

举个例子,网络请求结果返回,回调WaitingRequestManager的onResponseReceived(Request<?> request, Response<?> response)方法,最终通过ExecutorDelivery分发结果。

/** Request received a valid response that can be used by other waiting requests. */
    @Override
    public void onResponseReceived(Request<?> request, Response<?> response) {
        if (response.cacheEntry == null || response.cacheEntry.isExpired()) {
            onNoUsableResponseReceived(request);
            return;
        }
        String cacheKey = request.getCacheKey();
        List<Request<?>> waitingRequests;
        synchronized (this) {
            waitingRequests = mWaitingRequests.remove(cacheKey);
        }
        if (waitingRequests != null) {
            if (VolleyLog.DEBUG) {
                VolleyLog.v(
                        "Releasing %d waiting requests for cacheKey=%s.",
                        waitingRequests.size(), cacheKey);
            }
            // Process all queued up requests.
            for (Request<?> waiting : waitingRequests) {
                mResponseDelivery.postResponse(waiting, response);
            }
        }
    }

而对于网络请求结果失败、无可用相应内容,则回调WaitingRequestManager的onNoUsableResponseReceived(Request<?> request)方法:

/** No valid response received from network, release waiting requests. */
    @Override
    public synchronized void onNoUsableResponseReceived(Request<?> request) {
        String cacheKey = request.getCacheKey();
        List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
        if (waitingRequests != null && !waitingRequests.isEmpty()) {
            if (VolleyLog.DEBUG) {
                VolleyLog.v(
                        "%d waiting requests for cacheKey=%s; resend to network",
                        waitingRequests.size(), cacheKey);
            }
            Request<?> nextInLine = waitingRequests.remove(0);
            mWaitingRequests.put(cacheKey, waitingRequests);
            nextInLine.setNetworkRequestCompleteListener(this);
            // RequestQueue will be non-null if this instance was created in AsyncRequestQueue.
            if (mRequestQueue != null) {
                // Will send the network request from the RequestQueue.
                mRequestQueue.sendRequestOverNetwork(nextInLine);
            } else if (mCacheDispatcher != null && mNetworkQueue != null) {
                // If we're not using the AsyncRequestQueue, then submit it to the network queue.
                try {
                    mNetworkQueue.put(nextInLine);
                } catch (InterruptedException iex) {
                    VolleyLog.e("Couldn't add request to queue. %s", iex.toString());
                    // Restore the interrupted status of the calling thread (i.e. NetworkDispatcher)
                    Thread.currentThread().interrupt();
                    // Quit the current CacheDispatcher thread.
                    mCacheDispatcher.quit();
                }
            }
        }
    }

Volley的CacheDispatcher缓存调度程序这一条线的逻辑梳理完了,大家从Volley的源码中也要领悟一些设计模式。优秀的框架设计不需要复杂的逻辑。

相关推荐

精选
Airbnb开源项目
开源

Airbnb开源项目

Airbnb Engineering & Data ScienceAirflow Use Apache Airflow (incubating) to author workflows as directed acyclic graphs (DAGs) of tasks12,263Airpal Web UI for PrestoDB2,502Aerosolve A machine learning

1.8k
OkHttp线程池和连接池
开源

OkHttp线程池和连接池

OkHttp线程池和连接池了解了OkHttp的网络请求流程以及拦截器实现原理,再关注OkHttp中两个重要的:OkHttp的线程池和连接池。1、OkHttp线程池在OkHttp网络请求流程一文中,我们分析了OkHttp异步和同步请求流程。请求最后都在Dispatcher中分发调度处理,最后被ExecutorService执行。1.1、DispatcherDispatcher中执行任务的执行器是ex

5.2k
优美的开源动效库:Lottie
开源

优美的开源动效库:Lottie

优美的开源动效库:Lottie1、强大的动效LottieLottie是一个适用于Android,iOS,Web和Windows的库,它可以使用Bodymovin解析以json格式导出的Adobe After Effects动画,并在移动设备和Web上原生渲染它们!GitHub:https://github.com/airbnb/lottie-androidLottie官网:http://airbn

4.3k