還是先自己分析一遍源碼,在看大神們的分析更有用。
研究Volley源碼,首先從RequestQueue開始,
1.RequestQueue
/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
分别用來存儲可以從緩存擷取的request與需要聯網擷取的request的阻塞隊列
/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
* @param threadPoolSize Number of network dispatcher threads to create
* @param delivery A ResponseDelivery interface for posting responses and errors
*/
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
首先看RequestQueue的構造函數,其中cache,network分别提供本地緩存查詢與聯網查詢的接口。
在class volley中我們可以看到,我們平時預設建立的時候,cache采用的是DiskBasedCache本地緩存,而network則根據版本選擇HurlStack或HttpClientStack。
NetWorkDispacher的話,實際上繼承自Thread
/**
* Provides a thread for performing network dispatch from a queue of requests.
*
* Requests added to the specified queue are processed from the network via a
* specified {@link Network} interface. Responses are committed to cache, if
* eligible, using a specified {@link Cache} interface. Valid responses and
* errors are posted back to the caller via a {@link ResponseDelivery}.
*/
public class NetworkDispatcher extends Thread {
在這裡相當于用一個threadPoolSize大小的數組來存儲線程的引用,threadPoolSize預設為4。
最後delivery則用來處理聯網或從緩存中擷取的結果。
這樣,請求隊列初始化就完成了,然後需要調用start()方法才會運作,
/**
* 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 = ; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
Start函數中的方法很簡單,
其中CacheDispatcher也是一個Thread子類
是以整個函數就是,啟動一個處理cache請求的線程,還有建立threadPoolSize個聯網請求的線程,預設會建立4個。
然後CacheDispatcher和NetworkDispatcher就可以開始處理各自請求隊列中的請求了。這兩個線程稍後在看。現在還剩下一個比較重要的方法,add(),用于添加新的請求。
/**
* Adds a Request to the dispatch queue.
* @param request The request to service
* @return The passed-in request
*/
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);
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.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);
}
return request;
}
}
首先先設定本次request的處理隊列為目前的RequestQuque
之後将該方法添加到mCurrentRequests中,本質是一個set。
然後判斷該請求是否允許從cache擷取資訊,如果不允許,則直接講該請求添加到mNetworkQueue聯網請求隊列中并傳回。
如果允許,則判斷mWaitingRequests(一個hashMap,用來儲存正在進行中的請求)中是已經有一個相同請求(這裡相同時是通過getCacheKey來判斷的,也就是判斷request中的url是否相同)正在處理了,如果查到有相同的請求正在處理,則擷取該請求的一個等候隊列,将本次請求加入等候隊列,等候結果的傳回。
如果沒有相同的請求在處理,則将本次請求加入mWaitingRequest中,表示這個請求正在被處理,以後相同請求到來,直接等候該結果傳回即可,然後将本次請求加入mCacheQueue隊列中,先嘗試從緩存中擷取資訊。
還剩下幾個方法:
cancleAll方法很簡單,周遊存儲目前請求的集合mCurrentRequest,找到tag相同的請求後,設定為cancel,這樣在處理流程中,檢測到request已被取消則不會再處理。
Finish方法,在request請求完成後,會調用request中的finish,也相應調用了requestQueue中的finish方法。将本次request從mCurrentRequest中移除。然後不要忘記之前有個相同請求的等待隊列存在mWaitingRequests中,如果本次request允許緩存,則可以把結果給這個隊列裡的request使用,否則不行。然後從mWaitingRequests這個Map中取出相同請求的等待隊列,然後将所有請求加入mCacheQueue中,從緩存去擷取值。
接下來先看看CacheDispatcher怎麼來處理mCacheQueue中請求的。
2.CacheDispatcher
直接關注它的run方法
@Override
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;
}
}
}
首先設定本線程優先級為
THREAD_PRIORITY_BACKGROUND
,一個比較低的優先級,不太影響使用者線程。
這個方法是初始化緩存,在DiskBaseCache中有具體實作。
然後就進入了一個死循環中,真正的工作開始了。
mCacheQueue.take()
首先使用take方法,從隊列中取出一個Request,隊列是BlockingQueue阻塞隊列,使用take方法,當隊列為空時,則會阻塞。非空時則會擷取隊列頭的一個元素并移出隊列。
在處理時,每一步都會addMarker,應該是用來标記本request處理到了哪一步。
然後首先先判斷
先判斷本次request是否被取消了,volley的一大好處就是可以通過cancelAll()與tag随時取消一個不需要進行的請求。如果取消了,則不用再做處理,直接調用Request的finish方法傳回。
如果沒被取消,則繼續,
// 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;
}
從mCache中查找是否有本次請求的緩存,請求用的key就是request中的url,如果緩存中沒有找到,則将本request加入到mNetQueue需聯網請求的隊列中。如果找到了則繼續。
// 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;
}
找到了的話,也不是直接用,而是判斷緩存是否過期,如果過期了,還是會聯網請求,不過也會把緩存的結果加入到request中,可能如果聯網請求失敗就用這個了。
如果上方幾步都通過了,則可以直接從緩存擷取本次request的結果了。
然後會
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
先将緩存擷取的資料封裝為NetworkResponse,然後調用request的parseNetworkResponse生成一個Response。
最後一步還要判斷一下,目前緩存是否要求更新,這裡的話和上方不太一樣。先說結果,如果不需要更新,則将本次Response交給mDelivery。否則還會從聯網請求。
mDelivery先放一下,先研究NetWorkDispatcher
3.NetWorkDispatcher
NetWorkDispatcher和CacheDispatcher前邊類似,
也是先從mNetworkQueue阻塞隊列中take一個元素。然後判斷是否被取消了,沒取消則繼續處理。
addTrafficStatsTag(request);
這個方法在API版本14以上才會啟用,需要給request添加一個tag,然後這個tag就是url中的host字元串的hashcode
不很懂,跳過。
然後
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
在這裡就這直接調用網絡API的方法,把本次請求發了出去了,并建立NetworkResponse對象接收請求結果。
這裡的mNetWork在api9以上,使用的都是HurlStack,api9以下則使用HttpClientStack,這個在其他文章中也說了。
接下來處理傳回結果,首先檢查狀态碼如果是304 NotModify (表示自上次請求後,内容未修改),這樣的話,不會傳回資料的,然後直接傳回。
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
然後則會根據網絡傳回的結果,與cacheDispatcher中類似,也會生成一個Response傳回。
然後如果本次請求允許緩存并且緩存值非空,則将本次緩存請求。
這裡緩存值是否為空,跟具體的request中parseNetworkResponse的實作有關,parseNetworkResponse是個抽象方法。
最後
mDelivery.postResponse(request, response);
和CacheDispatcher中一樣也是調用mDelivery的postRespones放法傳回結果。
4.ResponseDelivery
接下來可以看mDelivery源碼了。
ResponseDelivery也是一個接口,他的預設實作方法則是下邊這個,
new ExecutorDelivery(new Handler(Looper.getMainLooper())
在這裡
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
具體實作是ExecutorDelivery
/**
* Creates a new response delivery interface.
* @param handler {@link Handler} to post responses on
*/
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
可以看到,ExecutorDelivery将主線程關聯的Handler作為參數傳了進來,這樣實作了個Executor可以将command傳入主線程中執行。
然後我們在CacheDispatcher和NetworkDispatcher中調用的都是delivery中的postResponse方法,我們看下在這裡的具體實作。
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
在做完标記後,調用了
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
方法,在上邊構造函數中我們已經看到,會将
new ResponseDeliveryRunnable(request, response, runnable)
傳入到主線程中運作。
然後我們繼續分析ResponseDeliveryRunnable,一個内部類
構造方法中傳入的參數,request是request本身,response是聯網請求或緩存請求擷取來的結果,至于runnable,CacheDispatcher和NetworkDispatcher中傳來的都是null。
然後看run方法
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
先判斷請求是否這時被取消了,
沒有則根據本次傳回結果成功與否,分别調用deliverResponse或deliverError
然後就結束了。
然後deliverResponse實際上調用的就是mListener.onResponse(response),也就是我們需要寫的傳回成功的處理方法,
deliverError則調用的mErrorListener.onErrorResponse(error);
至此,Volley的基本流程分析完了。
回顧一下:
說說流程,
1.當往requestQueue中添加一個新的request時,一般情況下,如果此時有相同的url的request請求正在被處理,則将該request加入一個等待隊列中等待正處理的請求傳回,否則的話,則處理這個請求。
2.首先會将其加入mCacheQueue緩存請求隊列中,由CacheDispatcher從本地緩存中找是否有對應結果。查找時根據request中的url進行。如果找到了則用緩生成Response并調用delivery.postResponse 傳回結果。如果沒有或者緩存過期,則将該請求加入mNetworkQueue聯網網球隊列中。
3.由NetworkDispatcher處理,将該請求聯網發出,然後傳回後,也同樣交給delivery.postResponse 傳回結果。
4.delivery.postResponse預設情況下,會組裝一個ResponseDeliveryRunnable線程由handler傳遞給主線程運作,在這裡調用request的deliverResponse
方法,也就是調用我們生成request時所寫的ResponseListener和ErrorListener。
如果覺得寫得不好,可以去看看大神們的解析
http://blog.csdn.net/yanbober/article/details/45307217
http://blog.csdn.net/guolin_blog/article/details/17656437