天天看點

Volley源碼簡單解析

還是先自己分析一遍源碼,在看大神們的分析更有用。

研究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