天天看點

volley源代碼分析-整體把控

0. 說在前面

對于Volley的介紹,網絡上有很多,在這裡我就不多廢話,直接切入到我們進入的重點,在接下來的幾篇部落格,我将竭盡我的能力去徹底的分析一下Volley的奧秘,可能由于我的水準有限,還是不能與大神們的部落格相提并論,但是我的分析還在站在一個新手的角度,最大可能的去讓自己了解,也讓有些迷惑的同學們去了解,網上看了很多介紹Volley的架構的文章,有些文章講的很好,但是一開始沒有給一個整體的概念,而是直接切入到了源碼的層次,我今天要做的就是按照Volley的工作流程,先大概的從源碼的層次去了解一下,其工作的原理,随後幾篇文章可能會從自定義請求和一些Volley的封裝。

開始!!!!

1. 首先我們看一下工作流程圖,這也是GOOGLE I/O大會放出的一張圖,下面的分析也是基于該圖。

volley源代碼分析-整體把控

從使用的流程我們知道,首先我們會調用Volley類的newRequestQueue方法,當然,這個類也是Volley架構暴露出來的入口,因為,我們在執行請求時使用的處理隊列就是從該方法傳回出來的。下面我們看看它到底做了些什麼工作?

newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes)

我們隻需看這個3個參數的方法即可,因為其他參數方法也是直接調用之。

這個方法裡面的語句不是很多,我們挑一些重要的語句進行分析;

if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } }

在這裡我們先解釋一下HttpStack這個接口的作用:

該接口是進行Http請求的,該接口所做的具體工作就是發送Http請求,并将請求的結果傳回出來,我們可以了解為它是一個對Http請求的處理,這個之後我會有一些更加細緻的說明,現在暫且說到這裡。

從上面的語句我們可以發現,這個處理接口有兩種具體的實作,一種是HurlStack,另一種就是HttpClientStack。前者是通過HttpURLConnection實作的Http處理類,後一種是通過HttpCient實作的Http處理類。二者的選擇是根據開發時使用的SDK版本所決定的(當然如果你自己傳入參數時指定了HttpStack時就不會交于系統選擇了),選擇的原則是當SDK大于等于9時,采用HttpURLConnection的處理方式,而9之前就使用HttpClientStack的處理方式,原因是在9之前,HttpURLConnection有一些小的BUG,造成使用這種方式的請求不穩定,在官方的注釋裡你可以看到“Prior to Gingerbread, HttpUrlConnection was unreliable.”

再往下來看:

Network network = new BasicNetwork(stack);

下面解釋一下NetWork這個接口:

public interface Network { public NetworkResponse performRequest(Request<?> request) throws VolleyError; }

從上面的源碼,你會發現這個接口隻有一個方法。且該方法就是處理指定的請求,但是并不依賴于特有的請求方式,這是一個很好的設計,通過這種設計,将處理請求者與具體的請求任務相分離,這就大大的增加了擴充性,因為對于HttpStack而言,它隻是具有了網絡請求的能力,而通過NetWork的包裝後就具有了通過這種能力做具體的工作的能力。它的擴充性就可以展現在你可以建立一個同樣具有網絡請求能力的處理者來滿足你的需要,隻需NetWork的包裝,直接就可以與Volley的整個架構融合在一起。

下面的語句就是在Volley中非常重要的一個功能體: RequestQueue

RequestQueue queue; if (maxDiskCacheBytes <= -1) { // No maximum size specified queue = new RequestQueue(new DiskBasedCache(cacheDir), network); } else { queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network); } queue.start();

上句很簡單,就是通過是否指定最大磁盤緩存大小來決定建立請求隊列的方式,然後,開啟隊列。這本沒有什麼好說的,但是看到RequestQueue對整個架構的重要性,我覺得有必要去了解一下它的源代碼。

在分析之前,我們有必要簡單的了解一下在該類中的幾個成員變量:

NetworkDispatcher mDispatchers

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

CacheDispatcher mCacheDispatcher

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

ResponseDelivery mDelivery

傳回結果分發接口,目前隻有基于ExecutorDelivery的在入參 handler 對應線程内進行分發。

看到上面的兩個分發器你有沒有一些疑問?為什麼會有兩個分發器?

如果你有這樣的疑問,請上拉,看最初的那張流程圖,當緩存排程線程從隊列中取出一個請求的時候會判斷目前的請求是否存在與緩存中,如果存在緩存就直接從緩存中讀取資料并傳回,否則的話就交由網絡排程線程去處理,此時網絡排程線程就會從請求網絡,擷取網絡中的資料,處理傳回的資料,如果允許寫入緩存的話就寫入緩存(預設開啟緩存的寫入)。

我們一會可以通過源碼來證明我們的分析。

下面看一下 在newRequestQueue方法中使用的RequestQueue的start方法的源碼。

public void start() { stop(); // Make sure any currently running dispatchers are stopped mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }

首先會調用stop()方法,確定目前運作的排程器都被關閉。

然後建立了一個緩存排程,并開啟。

在建立一個網絡排程,并開啟。

其實,在這個方法裡,就僅僅隻是建立了兩個排程器,一個是緩存排程,另一個就是網絡排程,我在剛才對這兩個類做了一些簡單的說明,我們可以知道,實際上這兩個類是一個線程,調用了線程的start方法就是運作了一個線程,打開各自的run方法,你會發現各自的run方法都是死循環,在這裡,筆者隻對網絡請求的排程工作做說明,對于緩存的排程大家自己分析吧!
           

之前,我想介紹一個資料結構 BlockingQueue:

特殊的隊列,如果BlockingQueue是空的,從BlockingQueue取東西的操作将會被阻斷進入等待狀态,直到BlockingQueue進了東西才會被喚醒,同樣,如果BlockingQueue是滿的,任何試圖往裡存東西的操作也會被阻斷進入等待狀态,直到BlockingQueue裡有空間時才會被喚醒繼續操作。(來源于網絡)

下面分析網絡排程中的流程:

1. 從BlockingQueue中取出一個請求;

request = mQueue.take();

2. 執行網絡請求,得到請求的傳回資料

NetworkResponse networkResponse = mNetwork.performRequest(request);

3. 解析網絡請求資料

Response<?> response = request.parseNetworkResponse(networkResponse);

4. 判斷是否需要緩存,預設支援緩存,可通過

request.setShouldCache(boolean)

來指定此次請求是否緩存來禁用此次緩存。如果本次請求支援緩存,則将本次請求的響應寫入到緩存中

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

5. 将結果傳遞給主線程

mDelivery.postResponse(request, response);

上述就是網絡排程的工作原理,對于緩存的排程也請讀者自己分析。

對于上述的第五步,将結果傳送到主線程我覺得還是有必要說一下的,

對于架構中的ResponseDelivery隻有一個實作類-ExecutorDelivery

下面我們就注重分析ExecutorDelivery的源代碼

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); }

最終有效的代碼就是線程中:

` 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();

}

}`

首先會判斷目前的請求是否已經被删除了,如果被删除了就什麼也不做,如果請求成功,就交由目前request去傳遞給主線程,這也就是通過每個具體的請求都要使用的方法deliverResponse(),

在request的具體實作就是:

@Override protected void deliverResponse(T response) { if (mListener != null) { mListener.onResponse(response); } }

而mListener就是我們在new出一個具體請求方式的時候傳入了監聽器,至此就将結果的處理交給了主線程。

對于整個流程,我們再重新理一下。

  1. 首先通過Volley的newRequestQueue建立一個RequestQueue,該RequestQueue可以作為整個app的請求隊列來用,是以可以一開始在Application中注冊一下,随後在項目的其他位置就可以直接使用該隊列去處理我們的網絡請求,當然你要對Volley進行一下簡單的封裝,在建立隊列的同時,會建立兩個排程器,緩存排程器和網絡排程器,并阻塞執行,等待着處理并分發請求。
  2. 建立一個請求,并把請求添加到上述的隊列中。
  3. 首先緩存排程會判斷該請求是否存在于緩存中,如果存在于緩存中,就直接将結果傳遞給主線程。
  4. 如果在緩存排程中沒有命中,就送出到網絡排程,由網絡排程調用網絡請求,之後将網絡請求的結果傳回給主線程。