Volley有以下優勢:
1. 自動調度網絡請求
2. 多併發請求 (源於開了多個線程)
3. 本地Cache自動緩存網絡請求結果
4. 支持設置請求優先級
5. 支持取消單個請求或者取消全部請求
6. 易於定製請求(好比:自定義重試機制,自定義Request請求等)
7. 提供完善的Log打印跟蹤工具
數組
Google的一張Volley原理圖來簡單解釋下Volley的工做原理。 緩存
Volley請求處理是一個異步的過程:網絡
1.在主線程中按照請求的優先級把Request添加到本地緩存隊列CacheQueue中,
2.緩存分發器CacheDispatcher輪詢本地是否已經緩存了此次請求的結果,
3.若是命中,則從緩存中讀取數據而且解析。解析完的結果被ResponseDelivery 分發到主線程中。
4.若是沒有命中,則將此次請求添加到網絡請求隊列NetworkQueue中,
5.網絡分發器NetworkDispatcher處理網絡請求,獲取請求結果並解析同時把結果寫入緩存。解析完的結果被分發到主線程中。多線程
兩個分發器、兩個隊列、五個線程併發
Volley初始化之後就建立了5個後臺線程(1個緩存線程和4個網絡線程來處理Request請求)在處理請求。只要你沒作處理,這5個線程一直在後臺跑。爲了節省資源,在同一個App中最好使用同一個單例Volley RequestQueue隊列來處理全部請求,以避免建立過多線程浪費資源。還有在退出這個應用時,應該調用 RequestQueue#stop方法來幹掉全部Volley線程。如此纔是使用Volley最優雅的方式app
Volley最基本的使用代碼以下(開發者用法):框架
//建立請求隊列 RequestQueue mQueue = Volley.newRequestQueue(context); //構建一個Request請求 StringRequest request = new StringRequest(url, new Response.Listener<String>() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); //將請求添加到隊列中 mQueue.add(request);
咱們來看看Volley#newRequestQueue()方法如何實現的?異步
public class Volley { /**默認緩存目錄 */ private static final String DEFAULT_CACHE_DIR = "volley"; public static RequestQueue newRequestQueue(Context context, HttpStack stack) { //建立默認緩存文件 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) {//API>=9使用HttpURLConnection訪問網絡 stack = new HurlStack(); } else {//API<9時使用HttpClient訪問網絡 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } //建立網絡訪問類 Network network = new BasicNetwork(stack); //建立請求隊列 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); //開始執行隊列中的任務 queue.start(); return queue; } /**靜態方法建立請求隊列*/ public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); } } ---------------------
代碼第8行: 建立一個默認的緩存文件目錄,該路徑在應用的私有目錄data/data/your_package/cache/volley/ 下。ide
代碼28行:建立一個網絡請求隊列RequestQueue 對象,而後調用start()方法啓動執行隊列中的任務。那麼RequestQueue#start()方法到底作了什麼?接下來分析下RequestQueue類的實現。工具
public class RequestQueue { /** 用於標識Request的編號. */ private AtomicInteger mSequenceGenerator = new AtomicInteger(); /**保存添加到RequestQueue隊列中相同key的請求*/ private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>(); /**保存當前全部添加到RequestQueue隊列中的請求*/ private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>(); /** 帶有優先級的緩存請求隊列. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); /** 帶有優先級的網絡請求隊列. */ private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>(); /** 默認開啓4個線程處理網絡請求 */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; /** 本地緩存,用於保存網絡請求結果 */ private final Cache mCache; /** 用於執行網絡請求. */ private final Network mNetwork; /** 請求結果分發器. */ private final ResponseDelivery mDelivery; /** 網絡處理請求分發器. */ private NetworkDispatcher[] mDispatchers; /** 本地緩存處理請求分發器. */ private CacheDispatcher mCacheDispatcher; public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } /** * 啓動隊列中的任務調度 */ public void start() { stop(); //中止當前全部任務. // 建立緩存任務調度器,而且啓動它 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(); } } /** * 中止緩存和網絡調度 */ public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } } } /** * 獲取隊列編號. */ public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); } /** * 獲取本地緩存. */ public Cache getCache() { return mCache; } /** 完成一次請求,當該請求被執行結束或者該請求被取消時調用該方法 */ <T> void finish(Request<T> request) { // 從當前隊列中移除該請求,標誌着該請求獲得執行。 synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } synchronized (mFinishedListeners) { 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); } //處理等待隊列中全部的請求. mCacheQueue.addAll(waitingRequests); } } } } ---------------------
RequestQueue#start方法
有上面的代碼可知:start方法中建立了一個CacheDispatcher緩存調度處理器和4個NetworkDispatcher網絡調度處理器,而他們都是繼承自Thread線程的,因此這裏建立了1個緩存線程和4個網絡線程來處理Request請求。至關於此處啓動了5個線程來處理請求,這就是爲何Volley框架支持多併發請求了。那麼咱們看看它們都作了些什麼??
--------------------- ------------------------------------------------------------
public class CacheDispatcher extends Thread { /** 緩存阻塞隊列. */ private final BlockingQueue<Request<?>> mCacheQueue; /** 網絡阻塞隊列. */ private final BlockingQueue<Request<?>> mNetworkQueue; /** 本地緩存. */ private final Cache mCache; /** 結果分發器. */ private final ResponseDelivery mDelivery; /** 標記當前線程是否死亡. */ private volatile boolean mQuit = false; public CacheDispatcher( BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue, Cache cache, ResponseDelivery delivery) { mCacheQueue = cacheQueue; mNetworkQueue = networkQueue; mCache = cache; mDelivery = delivery; } /**退出當前線程*/ public void quit() { mQuit = true; interrupt(); } @Override public void run() { //設置該線程的優先級爲後臺線程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //初始化本地緩存. mCache.initialize(); //死循環 while (true) { try { //從阻塞的緩存隊列中取出一個請求. final Request<?> request = mCacheQueue.take(); request.addMarker("cache-queue-take"); // 若是該請求被取消,就不處理該請求 if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue;//繼續下次循環,等待下一個請求 } //試圖從本地緩存中讀取本次請求結果. Cache.Entry entry = mCache.get(request.getCacheKey()); //本地沒有命中則將該請求投放到網絡請求隊列中處理 if (entry == null) { request.addMarker("cache-miss"); mNetworkQueue.put(request); continue;//結束本次循環 } // 本地命中,可是過時了,也需再次將請求投放到網絡請求隊列中 if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue;//結束本次循環 } // 本地緩存命中該請求之後解析該請求 request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { // 本地緩存命中且沒有過時,則將解析的結果發送到主線程中. mDelivery.postResponse(request, response); } else { // 本地緩存命中,但須要刷新,從新將此次請求投放到網絡請求隊列中 request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // Mark the response as intermediate. response.intermediate = true; 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) { // 此處用於退出當前線程,當該線程發生中斷異常時執行. if (mQuit) { return; } continue; } } } } ---------------------
解析:CacheDispatcher類繼承自Thread(緩存線程),實現了run方法,在run方法中寫了一個while(true)死循環,用於一直讀取緩存隊列中的請求任務(輪詢)。由於緩存隊列mCacheQueue是一個阻塞隊列,因此只有隊列不爲空時while循環纔會取出下一個新的請求任務執行,不然while循環一直阻塞直到有新任務添加進來。
run方法實現的邏輯是:先從本地緩存中去讀本次請求,若是該請求命中本地緩存且緩存未過時,則解析結果而且由分發器ResponseDelivery 將結果發送到主線程中。若是本地沒有命中或者命中的請求過時了,則將該請求投放到網絡請求隊列中,由NetworkDispatcher來處理網絡請求。
--------------------- ----- ------------------------------------------------------------
public class NetworkDispatcher extends Thread { .................. @Override public void run() { //設置線程優先級爲後天線程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { long startTimeMs = SystemClock.elapsedRealtime(); Request<?> request; try { // 從隊列中取出一個請求任務. request = mQueue.take(); } catch (InterruptedException e) { // 當線程發生中斷異常時,判斷該線程是否死亡?若是死亡則結束循環,不然跳出本次循環繼續等待下一個請求任務到來。 if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); // 該Request請求若是被取消,則跳出本次循環,結束本地請求處理 // network request. if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); // 執行網絡請求. NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // 相同的結果不發送第二次 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // 在工做線程中解析網絡結果. Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // 將結果保存到緩存中. if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } 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); } } } ........... ---------------------
解析:NetworkDispatcher 類一樣繼承自Thread,實現了其run方法。在該方法中寫了一個while(true)死循環,用於讀取網絡隊列中的Request請求任務,一樣因爲網絡隊列也是一個阻塞隊列,因此當隊列不爲空就取出一個Request任務,而後將該任務值執行網絡請求,而且解析請求結果,在獲得網絡請求結果之後首先將結果保存到本地緩存,然看結果將由分發器ResponseDelivery發送到主線程中。
--------------------- ----------------------------------------------
到此,RequestQueue#start方法分析結束,總結起來以下:Volley會建立一個RequestQueue對象,該對象會建立一個Cache對象用於保存請求結果,建立一個帶有優先級以及阻塞的緩存隊列mCacheQueue用於保存用戶添加的請求,建立一個CacheDispatcher線程調度器來輪詢緩存隊列mCacheQueue執行請求任務。建立了4個帶有優先級和阻塞的網絡隊列mNetWorkQueue用於保存沒有命中本地緩存的請求,匹配的也建立了4個NetworkDispatcher線程調度器來輪詢mNetWorkQueue隊列執行請求任務。
--------------------- -------------------- ----------------------------------------------
........ /**添加一個請求到帶有分發器的隊列中*/ public <T> Request<T> add(Request<T> request) { //Request請求和請求隊列關聯 request.setRequestQueue(this); synchronized (mCurrentRequests) { //Request請求添加到當前請求隊列中 mCurrentRequests.add(request); } // 給該請求設置一個順序編號. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); // 若是該請求不須要緩存,則將Request請求直接添加至網絡請求隊列中. if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } // 如下代碼處理緩存請求等待隊列. synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); //若是等待隊列中已經存在該請求的key,則說明此時有一個相同的請求正在被處理,所以將該request放在等待隊列中,等待前一個request處理完以後在finish方法中處理。 if (mWaitingRequests.containsKey(cacheKey)) { 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 { //若是等待隊列中沒有改請求的key,則將之添加到等待隊列,注意:添加到等待隊列中的value 是一個null,目的用於標記該請求在等待隊列中且處於正在被處理的狀態。 mWaitingRequests.put(cacheKey, null); //添加都緩存隊列中讓該請求獲得相應的執行。 mCacheQueue.add(request); } return request; } } ........
代碼第13行:給當前請求設置一個編號,後面將用於設置請求的優先級,這裏暫且不詳細講解。
代碼第17-20行:判斷當前請求是否容許本地緩存,若是不容許,則直接將本次請求添加到網絡請求隊列中。不然添加到緩存請求隊列中。
代碼26-35行:判斷請求等待隊列中是否包含本次請求的key,若是包含,則說明有相同的請求正在被執行,此時將該請求放入到等待隊列中,等待上一個請求被執行完成之後再來處理這次的請求,見方法 finish()。
代碼38-39行:表示請求等待隊列中並不包含本次請求的key,則先將本次請求在等待隊列中置空,置空的目的是告訴別人該請求正在被執行,若是有其餘相同的請求來時,請先等待我此次請求執行結束。而後將本次請求投放到緩存隊列中,讓CacheDispatcher調度器執行本次請求。
---------------------
該方法的調用,標誌着一次請求的結束,結束一次請求包括:
<T> void finish(Request<T> request) { // 從當前隊列中移除該請求 synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } synchronized (mFinishedListeners) { //請求結束的監聽回調 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) { //將等待隊列中全部相同的請求一次性添加到緩存隊列中,讓CacheDispatcher線程處理。此處不敢苟同,既然是相同的請求,爲啥要所有放入隊列中?放一個就行了嘛!! mCacheQueue.addAll(waitingRequests); } } } }
這段代碼的解釋在註釋裏寫的很清楚了。
咱們都知道,Volley有個優點就是能夠取消指定的Tag標記請求或者取消全部請求,那麼Volley是怎麼作到的呢?仍是從源碼中找答案吧。
/** *定義的過濾請求接口,用於構建取消某個請求或者全部請求 */ public interface RequestFilter { public boolean apply(Request<?> request); } /** 根據給定的過濾條件取消隊列中全部的請求 */ public void cancelAll(RequestFilter filter) { synchronized (mCurrentRequests) { //遍歷當前請求隊列中全部的請求,找到匹配的請求而後取消該請求。 for (Request<?> request : mCurrentRequests) { if (filter.apply(request)) { request.cancel(); } } } } /** 根據給定的tag標籤取消隊列中全部的請求隊列 */ public void cancelAll(final Object tag) { if (tag == null) { throw new IllegalArgumentException("Cannot cancelAll with a null tag"); } cancelAll(new RequestFilter() { @Override public boolean apply(Request<?> request) { return request.getTag() == tag; } }); }
咱們在添加請求的時候一般會給每一個請求這是一個Tag,好比:
request.setTag("request1");
那麼咱們須要取消該請求的時候就簡單了:
RequestQueue.cancelAll("request1");
如此就取消了tag=request1的請求。
如此一來每一個請求都須要設置不一樣的tag來肯定惟一的請求標記。那麼問題來了,我在整個應用退出時該如何取消全部的請求呢?不可能我每一個請求都去調用一次cancleAll吧?此時只要調用cancelAll(RequestFilter filter)方法就能夠垂手可得的取消全部request請求啦,代碼以下:
requestQueue.cancelAll(new RequestQueue.RequestFilter() { @Override public boolean apply(Request<?> request) { return true; } });
解析:以上代碼僅僅是修改了RequestFilter接口中apply方法的返回值永遠爲true而已。如此一來就會致使以下方法會所有遍歷一次當前請求隊列。
public void cancelAll(RequestFilter filter) { synchronized (mCurrentRequests) { //遍歷當前請求隊列中全部的請求,找到匹配的請求而後取消該請求。 for (Request<?> request : mCurrentRequests) { if (filter.apply(request)) { request.cancel(); } } } }
這篇博客主要介紹了Volley的總體工做機制,從整篇博客咱們知道:
Volley默認建立1個cache Thread和4個network Thread來處理網絡請求,固然你也能夠建立更多的network Thread來處理更多的網絡請求,正由於如此,Volley才支持多併發網絡鏈接。
Volley建立1個本地緩存隊列(cacheQueue)和1個網絡請求隊列(networkQueue)來保存全部網絡請求,而隨之對應的是一個cache Thread處理緩存隊列,4個network thread處理網絡請求隊列,因爲隊列的實現都是帶有優先級的阻塞隊列,所以4個network thread是自動調度處理網絡請求的。
Volley默認先將請求提交給cache Thread來處理,cache Thread會查找本地是否緩存了本次請求結果,若是緩存了且該結果未過時,則直接讀取本地緩存結果,而無須再次請求網絡。所以Volley默認自動緩存網絡請求結果。
Volley支持取消某個或者全部的網絡請求,通常在某個activity退出時調用Request#cancelAll()方法來取消全部網絡請求以便出現內存泄漏。
Volley初始化之後就建立了5個後臺線程在處理請求。只要你沒作處理,這5個線程一直在後臺跑。爲了節省資源,在同一個App中最好使用同一個單例Volley RequestQueue隊列來處理全部請求,以避免建立過多線程浪費資源。還有在退出這個應用時,應該調用 RequestQueue#stop方法來幹掉全部Volley線程。如此纔是使用Volley最優雅的方式
後續博客會繼續分析Volley是怎麼實現RetryPolicy錯誤重試機制的,以及本地緩存的策略和請求優先級的設置。
---------------------
volley爲何不適合傳輸大數據:
volley中爲了提升請求處理的速度,採用了ByteArrayPool進行內存中的數據存儲的,若是下載大量的數據,這個存儲空間就會溢出,因此不適合大量的數據,
可是因爲他的這個存儲空間是內存中分配的,當存儲的時候優是從ByteArrayPool中取出一塊已經分配的內存區域, 沒必要每次存數據都要進行內存分配,
而是先查找緩衝池中有無適合的內存區域,若是有,直接拿來用,從而減小內存分配的次數 ,因此他比較適合大量的數據量少的網絡數據交互狀況。
Volley有用到線程池嗎?
volley雖然沒有用ThreadPoolExecutor,但volley 裏面使用了一個數組來存放 NetworkDispatcher 這功能就至關因而線程池,只不過本身寫了管理,默認開啓4個線程。
------------------------------------------------------------