Volley 框架解析(二)--RequestQueue核心解讀


主要圍繞RequestQueue進行解讀,它的兩個請求隊列CacheQueue、NetworkQueue是如何調用的,第一條請求的執行過程及如何處理重復請求?對RequestQueue及相關的類進行詳細解讀。

1.RequestQueue:

Volley 框架的核心類,將請求 Request 加入到一個運行的RequestQueue中,來完成請求操作。

RequestQueue:表示請求隊列,里面包含一個CacheDispatcher(用於處理走緩存請求的調度線程)、NetworkDispatcher數組(用於處理走網絡請求的調度線程),一個ResponseDelivery(返回結果分發接口),通過 start() 函數啟動時會啟動CacheDispatcherNetworkDispatchers

CacheDispatcher:一個線程,用於調度處理走緩存的請求。啟動后會不斷從緩存請求隊列中取請求處理,隊列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行后續處理。當結果未緩存過、緩存失效或緩存需要刷新的情況下,該請求都需要重新進入NetworkDispatcher去調度處理。

NetworkDispatcher:一個線程,用於調度處理走網絡的請求。啟動后會不斷從網絡請求隊列中取請求處理,隊列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行后續處理,並判斷結果是否要進行緩存。

ResponseDelivery:返回結果分發接口,目前只有基於ExecutorDelivery的在入參 handler 對應線程內進行分發。

HttpStack:處理 Http 請求,返回請求結果。目前 Volley 中有基於 HttpURLConnection 的HurlStack和 基於 Apache HttpClient 的HttpClientStack

Network:調用HttpStack處理請求,並將結果轉換為可被ResponseDelivery處理的NetworkResponse

Cache:緩存請求結果,Volley 默認使用的是基於 sdcard 的DiskBasedCacheNetworkDispatcher得到請求結果后判斷是否需要存儲在 Cache,CacheDispatcher會從 Cache 中取緩存結果。

 (1). 主要成員變量

RequestQueue 中維護了兩個基於優先級的 Request 隊列,緩存請求隊列和網絡請求隊列。
放在緩存請求隊列中的 Request,將通過緩存獲取數據;放在網絡請求隊列中的 Request,將通過網絡獲取數據。

mCacheQueue緩存請求隊列,mNetworkQueue發起網絡請求的隊列。

private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();

維護了一個正在進行中,尚未完成的請求集合。//add方法會把請求先加入到mCurrentRequests中,也就是說mCurrentRequests包含了當前所有的請求,Request 請求結束時,首先從正在進行中請求集合mCurrentRequests中移除該請求。

this.mCurrentRequests = new HashSet();

維護了一個等待請求的集合,如果一個請求正在被處理並且可以被緩存,后續的相同 url 的請求,將進入此等待隊列。Request 請求結束(finish), 然后查找請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待隊列移除,並將等待隊列所有的請求添加到緩存請求隊列中mCacheQueue,讓緩存請求處理線程CacheDispatcher自動處理。

this.mWaitingRequests = new HashMap();

構造方法: threadPoolSize//默認是4,

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

(2). 啟動隊列

創建出 RequestQueue 以后,調用 start 方法,啟動隊列。一個cache調度線程,4個network調度線程,加上主線程共六個線程。

public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        //mCache是硬盤緩存策略
        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();
        }
    }

 3)add加入請求

public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this); //把requestQueue綁定到request中
        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();//就是他的url
            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.DEBUG) {
                    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);  //加入到cachequeue中
            }
            return request;
        }
    }

 4). 請求完成

void finish(Request<?> request)
<T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {  //這個接口是在requestQueue中寫的
          for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> 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. 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);
                }
            }
        }
    }

Request 請求結束

(1). 首先從正在進行中請求集合mCurrentRequests中移除該請求。
(2). 然后查找請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待隊列移除,並將等待隊列所有的請求添加到緩存請求隊列中,讓緩存請求處理線程CacheDispatcher自動處理。

(5). 請求取消

public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)

取消當前請求集合中所有符合條件的請求。
filter 參數表示可以按照自定義的過濾器過濾需要取消的請求。
tag 表示按照Request.setTag設置好的 tag 取消請求,比如同屬於某個 Activity 的。

3.CacheDispatcher

一個線程,用於調度處理走緩存的請求。啟動后會不斷從緩存請求隊列中取請求處理,隊列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery 去執行后續處理。當結果未緩存過、緩存失效或緩存需要刷新的情況下,該請求都需要重新進入NetworkDispatcher去調度處理。

(1). 成員變量

BlockingQueue<Request<?>> mCacheQueue 緩存請求隊列
BlockingQueue<Request<?>> mNetworkQueue 網絡請求隊列
Cache mCache 緩存類,代表了一個可以獲取請求結果,存儲請求結果的緩存
ResponseDelivery mDelivery 請求結果傳遞類

run代碼://無限遍歷mCacheQueue,取出request,從cache中獲取相應結果,如果結果為空或者過期,則發起網絡請求。否則就將解析結果返回。

public void run() {
        if (DEBUG) 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;
            }
        }

(2). 處理流程圖

 **在具體的請求子類中解析結果,用ExecutorDelivery(new Handler(MainLooper))傳遞結果,代碼如下:

//以StringRequest為例,就是把結果按照他的編碼格式存到response中。
protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    } 

ExecutorDelivery的postResponse(request,response):

public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }
//用主線程的handler發送任務到主線程的messageQueue中。
mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };

 4.NetworkDispatcher.java

一個線程,用於調度處理走網絡的請求。啟動后會不斷從網絡請求隊列中取請求處理,隊列為空則等待,請求處理結束則將結果傳遞給 ResponseDelivery 去執行后續處理,並判斷結果是否要進行緩存。

(1). 成員變量

BlockingQueue<Request<?>> mQueue 網絡請求隊列
Network mNetwork 網絡類,代表了一個可以執行請求的網絡
Cache mCache 緩存類,代表了一個可以獲取請求結果,存儲請求結果的緩存
ResponseDelivery mDelivery 請求結果傳遞類,可以傳遞請求的結果或者錯誤到調用者

run代碼:

public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {//notModified:HTTP 304,參考https://www.douban.com/note/161120791/,
                                                                                   //就是服務器告訴客戶端,文件沒有更新,不用在請求啦。
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // 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");
                }

                // Post the response back.
                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);
            }
        }
    }

(2). 處理流程圖

5. Cache.java

緩存接口,代表了一個可以獲取請求結果,存儲請求結果的緩存。

(1). 主要方法:

public Entry get(String key); 通過 key 獲取請求的緩存實體
public void put(String key, Entry entry); 存入一個請求的緩存實體
public void remove(String key); 移除指定的緩存實體
public void clear(); 清空緩存

(2). 代表緩存實體的內部類 Entry

成員變量和方法
byte[] data 請求返回的數據(Body 實體)
String etag Http 響應首部中用於緩存新鮮度驗證的 ETag
long serverDate Http 響應首部中的響應產生時間
long ttl 緩存的過期時間
long softTtl 緩存的新鮮時間
Map<String, String> responseHeaders 響應的 Headers
boolean isExpired() 判斷緩存是否過期,過期緩存不能繼續使用
boolean refreshNeeded() 判斷緩存是否新鮮,不新鮮的緩存需要發到服務端做新鮮度的檢測

public static class Entry {
        /** The data returned from cache. */
        public byte[] data;
        /** ETag for cache coherency. Http 響應首部中用於緩存新鮮度驗證的 ETag*/
        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;
        /** Soft TTL for this record. */
        public long softTtl;
        /** Immutable response headers as received from server; must be non-null. */
        public Map<String, String> responseHeaders = Collections.emptyMap();
        /** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }
        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }

6.第一條請求的執行過程及如何處理重復請求

 6.1成員變量:

維護了一個正在進行中,尚未完成的請求集合。HashSet實現的不可重復,所以這個集合里是正在進行中,尚未完成的請求,並且沒有重復!Request 請求結束(finish),移出請求。

private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>(); 

一個等待請求的集合,如果一個請求正在被處理並且可以被緩存,后續的相同 url 的請求,將進入此等待隊列。Request 請求結束(finish), 然后查找請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待隊列移除,並將等待隊列所有的請求添加到緩存請求隊列中mCacheQueue,讓緩存請求處理線程CacheDispatcher自動處理。

HashMap實現key不可重復,queue是linkedList實現的。

private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>(); 

mCacheQueue緩存請求隊列,CacheDispatcher無限遍歷這個隊列。

private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

mNetworkQueue網絡請求隊列,發起網絡請求得隊列。NetworkDispatcher無限循環這個隊列,取出request訪問網絡。

private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

6.2第一條請求的處理過程

   1)在RequestQueue的add方法中,先把請求加入mCurrentRequests(hashSet實現的,元素不可重復,后續相同的加不了)。

   2)判斷請求是否允許緩存,不允許就是加入mNetworkQueue,允許執行第三步。

   3)mWaitingRequests中是否包含此請求,如果包含就加入到相同請求的等待隊列中。如果不包含,說明之前沒有請求過。就加入到mWaitingRequests和mCacheQueue中mWaitingRequests.put(cacheKey,null),mCacheQueue.add(request);這里主要看mCacheQueue,這時候CacheDispatcher就會把request取出來,判斷是否在緩存cache,這是第一個,肯定不在,就加入mNetWorkQueue,NetworkDispatcher就會把它取出來,執行網絡請求。

   4)請求完成時,通過ResponseDelivery傳送Response給主線程,request調用finish方法結束請求, 首先從正在進行中請求集合mCurrentRequests中移除該請求。
        然后查找請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待隊列移除,並將等待隊列所有的請求添加到緩存請求隊列mCacheQueue中,讓緩存請求處         理線程CacheDispatcher自動處理。

//在NetworkDispatcher中,傳送
mDelivery.postResponse(request, response);   

6.3有重復請求時,但是該請求正在訪問網絡

    1)add方法加入,因為mCurrentRequests中已經包含,不能加入到mCurrentRequests。

    2)判斷請求是否允許緩存,不允許就是加入mNetworkQueue,允許執行第三步。

    3)檢查mWaitingRequests中是否包含此請求,因為之前已經有啦,就加入到相同請求的等待隊列中(LinkedList實現的隊列)。等待訪問網絡的請求完成。

    4)然后就是6.2的第四步

 6.4有重復請求,已經在mCache中

    這個最簡單,第一個重復的請求加入mCacheQueue ,由CacheDispatcher處理,在Cache取出相應結果,傳給主線程。request調用finish方法

   1)在RequestQueue的add方法中,先把請求加入mCurrentRequests(hashSet實現的,元素不可重復,后續相同的加不了)。

   2)判斷請求是否允許緩存,不允許就是加入mNetworkQueue,允許執行第三步。

   3)mWaitingRequests中是否包含此請求,如果包含就加入到相同請求的等待隊列中。如果不包含,說明之前沒有請求過。就加入到mWaitingRequests和mCacheQueue中                 mWaitingRequests.put(cacheKey,null),mCacheQueue.add(request);這里主要看mCacheQueue,這時候CacheDispatcher就會把request取出來,判斷是否在緩存cache,

    找到緩存結果​用ExecutorDelivery傳遞給主線程,request調用finish·方法。

mDelivery.postResponse(request, response);  //還是這個方法    

  

轉發請注明出處:http://www.cnblogs.com/jycboy/p/volley-requestqueue.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM