Volley是一個網絡請求的庫,固然如今你們都用OkHttp或者Retrofit, 寫這篇博客目的是想自定義一個線程模型處理網路請求,Volley比較簡單並且採用了面向接口的設計,拿來做參考,順便把分析作一下記錄。php
咱們須要建立一個RequestQueue queue,若是項目中的網路請求比較少的話那推薦用一個queue,在Application中對它進行初始化,若是有頻繁的網絡請求能夠在每一個Activity裏面建立一個queue。在作網絡請求的時候只須要這樣:先建立一個RequestQueue,再建立一個Request,能夠是StringRequest,也能夠是JsonRequest,而後調用RequestQueue對象的add方法,把剛建立的Request對象添加到隊列就能夠了。
以StringRequest爲例html
RequestQueue queue = Volley.newRequestQueue(context); StringRequest request = new StringRequest("http://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { Log.i("VolleyRequest", response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.i("VolleyRequest", error.getMessage()); } }); queue.add(request);
下面是主要的類關係圖
先來看RequestQueue,下面這行代碼會建立一個RequestQueuejava
Volley.newRequestQueue(context);
Volley.java是一個初始化RequestQueue的類,它有兩個重載的方法android
public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); } public static RequestQueue newRequestQueue(Context context, HttpStack stack) { ... if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; }
最終都是經過new RequestQueue(new DiskBasedCache(cacheDir), network)建立了一個RequestQueue,能夠看到在初始化了queue以後立刻調用了start方法,因此在使用的時候不須要顯示調用queue的start方法。另一點它會根據編譯版本的API選擇不一樣的網絡實現類,若是API小於9就是用Apache的HttpClient,不然使用基於HttpURLConnection的HurlStack。這個Network等會再說,先看RequestQueue,它有兩個帶有優先級的阻塞式隊列:數組
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
mCacheQueue是存放的緩存過的請求隊列,mNetworkQueue是須要從網絡獲取數據的請求隊列,這兩個隊列都是PriorityBlockingQueue,與普通的阻塞式隊列相比它要求裏面的元素必須實現Comparable接口或者在構造隊列的時候須要傳一個實現了 Comparator接口的比較器。它是按照待比較對象的降序排列的,也就是說在比較的時候小的會比大的優先級高。Request實現了Comparable接口,看它的compare方法:緩存
@Override public int compareTo(Request<T> other) { Priority left = this.getPriority(); Priority right = other.getPriority(); return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal(); }
Priority是一個常量類,有四個值從小到大分別是LOW,NORMAL,HIGH和IMMEDIATE。每次在調用getPriority()取值的時候會返回指定值:默認是返回NORMAL,ImageRequest是返回LOW。這樣的話有一個ImageRequest imageRequest先進隊列接着又進來一個StringRequest stringRequest,stringRequest跟imageRequest相比,後者getPriority()返回LOW而前者返回NORMAL取ordinary時後者小於前者,在執行stringRequest.compareTo(imageRequest)時返回負數,那麼StringRequest會先出隊列。若是是相同類型的Request那麼就比較它們的mSequence值,mSequence是在RequestQueue的add方法裏被賦值的服務器
public <T> Request<T> add(Request<T> request) { ... // Process requests in the order they are added. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); ... }
再看getSequenceNumber()方法:網絡
public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); }
這個mSequenceGenerator是一個AtomicInteger,每次調incrementAndGet()它會自增1,這樣每個剛加入的Request的mSequence值都會比上一個Request高,在調用compareTo方法比較的時候越日後的Request就越大,這樣越早加入的Request的優先級就越高,實際上就是維護了一個FIFO的Request隊列,保證先來的請求會優先被處理。
再看一下它的構造方法,總共有三個:異步
public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
兩個重載的方法最終調用了4個參數的構造方法初始化了它的4個成員變量,其中mNetwork就是上面Volley在初始化隊列時會區分API版本的參數,這4個參數分別什麼含義呢ide
/** Cache interface for retrieving and storing responses. */ private final Cache mCache; /** Network interface for performing requests. */ private final Network mNetwork; /** The network dispatchers. */ private NetworkDispatcher[] mDispatchers; /** Response delivery mechanism. */ private final ResponseDelivery mDelivery;
四個參數分別對應了它的緩存策略,網絡請求實現,網路請求調度器和執行結果的傳遞者。
它是一個接口,它的實現類是DiskBasedCache,它會以請求的URL做爲Key(不是隻把URL當作key),請求結果的byte數組爲Value存儲請求結果,它默認是5M的存儲空間,若是不夠了它會把先緩存的數據給刪掉,再存新數據,同時會緩存請求的Header信息而且根據header的信息判斷緩存是否過時。Cache的實現類是DiskBasedCache,它有一個內部類CacheHeader,成員跟Cache的內部類Entry基本一致,存放的是請求結果的Header信息,與Entry相比少了一個byte數組data,data是存放請求結果的,它只存Header因此不須要結果,多了一個key,用來標識緩存的位置,還多了一個size,用來標識Header的大小。DiskBasedCache還有一個LinkedHashMap類型的的成員mEntries,它以String爲鍵以CacheHeader爲值保存請求的信息,這樣每次在須要對緩存作IO操做的時候取它的值來比較,避免了頻繁的IO操做提高了效率。
它也是一個接口,它的實現類是BasicNetwork,Volley用它來進行網路操做,可是實際執行又對它作了一次封裝,具體網絡實現依賴於它的成員HttpStack
public interface Network { public NetworkResponse performRequest(Request<?> request) throws VolleyError; } public class BasicNetwork implements Network { @Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { ... while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; httpResponse = mHttpStack.performRequest(request, headers); return new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); } ... } } }
能夠看到BasicNetwork在調用performRequest時是經過調用mHttpStack.performRequest來拿到數據,這個HttpStack也是一個接口(Volley的設計給了開發者充分自定義的空間),這就表示開發者可使用自定義的HttpStack來進行網絡操做
public interface HttpStack { public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; }
它的實現類有兩個:使用了Apache的HttpClientStack和基於HttpURLConnection的HurlStack,這兩個也是開發者初次接觸網絡請求會遇到的,至於應該用哪一個能夠參考使用HttpURLConnection仍是HttpClient
這是一個線程,裏面會關聯一個請求隊列mQueue和一個網絡處理類mNetwork,用於對Volley的請求作調度,它會在run方法裏面會執行一個死循環從mQueue裏挨個取出Request而後調用mNetwork的performRequest方法作處理,最後把結果經過ResponseDelivery傳遞給主線程。默認會開啓4個線程,看RequestQueue代碼,DEFAULT_NETWORK_THREAD_POOL_SIZE會做爲參數傳遞給構造方法,這個mDispatchers是一個線程數組,能夠對每一線程作調度,儘管沒有用ThreadPoolExecutor,可是也就至關於一個線程池,線程池的目的就是爲了能夠對每個線程作調度重用線程已有線程。
/** 要啓動的網絡請求調度線程個數 */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
毫無疑問這也是一個接口,它的實現類是ExecutorDelivery,網絡操做是耗時的須要在子線程中完成,可是結果是在主線程經過Listener拿到的,那怎麼辦,確定是經過Handler了,一個關聯了主線程Looper的Handler
public ExecutorDelivery(final Handler handler) { mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; }
若是有請求結果須要傳遞給主線程就會執行一個新的Runnable,任務的執行是在主線程完成。但在使用的時候並無要求開發者須要傳遞一個這樣的Handler,是怎麼回事,看RequestQueue的構造方法
public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }
也就明白了,它在初始化queue的時候就同時把這個Handler給初始化了。
下面來看它的執行過程,由主線程發起,交給子線程處理,最後把結果經過回調傳遞給主線程
任務的發起從RequestQueue的start方法開始
public void start() { stop(); // 把當前線程中止 // 建立一個緩存調度線程並啓動 mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // 建立4個網絡請求調度線程 for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
首先會調用stop方法把線程給停掉,而後再初始化新的線程執行任務。這裏要額外說一下它在初始化NetworkDispatcher的同時還初始化了一個CacheDispatcher,這個CacheDispatcher也是一個線程,Volley是自帶緩存功能的,這個線程就是負責對已經緩存的請求作調度
線程開啓了會作哪些事情呢,先看緩存調度線程
public class CacheDispatcher extends Thread { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 初始化緩存,會在這裏建立緩存目錄 mCache.initialize(); // 執行一個死循環輪詢mCacheQueue while (true) { try { // 從緩存的請求隊列裏取一個請求,若是沒有當前線程會被阻塞直到拿到一個能夠用的 final Request<?> request = mCacheQueue.take(); // 添加標記,過程當中會添加不少標記都是爲了在Logcat中顯示標記信息 request.addMarker("cache-queue-take"); // 若是請求被取消了就再也不對結果進行分發 if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } // Cache.Entry是用來保存請求結果的 // 嘗試從緩存中恢復 Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); // 若是根據URL獲得的Entry爲空就把這個請求放到須要從網絡獲取數據的mNetworkQueue裏 mNetworkQueue.put(request); continue; } // Entry不爲空可是已經失效(經過驗證ttl)也把這個請求放到須要從網絡獲取數據的mNetworkQueue裏 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"); // 是否須要更新(經過驗證softTtl) if (!entry.refreshNeeded()) { // 若是不須要更新就把結果分發給用戶 mDelivery.postResponse(request, response); } else { // 須要更新 request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // 添加一個標記 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) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } } } }
每一步操做都加了註釋應該很清楚,另外有兩點須要說明,第一點就是開始的
` Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
`這條語句,它是設置當前線程爲後臺線程,不然儘管是工做線程但它會擁有跟UI線程相同的的搶奪CPU資源的能力,跟UI線程的優先級是同樣的,這就可能致使UI線程得到的CPU時間較短形成界面的卡頓,而調用這條語句之後系統就會知道它是屬於background group的,最多隻分給它5%-10%的時間片,從而保障UI線程能夠獲取更多的CPU時間。第二點是它緩存的失效時間,緩存是否須要刷新是怎麼控制的
/** 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(); }
這是Cache的兩個方法,經過跟當前時間作對比,那結果就是看ttl和softTtl了,它們又是在哪被賦值的呢,是經過調用HttpHeaderParser的靜態方法parseCacheHeaders解析請求結果獲得的
public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> headers = response.headers; long serverDate = 0; long lastModified = 0; long serverExpires = 0; long softExpire = 0; long finalExpire = 0; long maxAge = 0; long staleWhileRevalidate = 0; boolean hasCacheControl = false; boolean mustRevalidate = false; String serverEtag = null; String headerValue; // 下面這些都是Http協議裏規定的字段 headerValue = headers.get("Date"); if (headerValue != null) { serverDate = parseDateAsEpoch(headerValue); } headerValue = headers.get("Cache-Control"); if (headerValue != null) { hasCacheControl = true; String[] tokens = headerValue.split(","); for (int i = 0; i < tokens.length; i++) { String token = tokens[i].trim(); if (token.equals("no-cache") || token.equals("no-store")) { return null; } else if (token.startsWith("max-age=")) { try { maxAge = Long.parseLong(token.substring(8)); } catch (Exception e) { } } else if (token.startsWith("stale-while-revalidate=")) { try { staleWhileRevalidate = Long.parseLong(token.substring(23)); } catch (Exception e) { } } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { mustRevalidate = true; } } } headerValue = headers.get("Expires"); if (headerValue != null) { serverExpires = parseDateAsEpoch(headerValue); } headerValue = headers.get("Last-Modified"); if (headerValue != null) { lastModified = parseDateAsEpoch(headerValue); } serverEtag = headers.get("ETag"); // Cache-Control takes precedence over an Expires header, even if both exist and Expires // is more restrictive. if (hasCacheControl) { softExpire = now + maxAge * 1000; finalExpire = mustRevalidate ? softExpire : softExpire + staleWhileRevalidate * 1000; } else if (serverDate > 0 && serverExpires >= serverDate) { // Default semantic for Expire header in HTTP specification is softExpire. softExpire = now + (serverExpires - serverDate); finalExpire = softExpire; } Cache.Entry entry = new Cache.Entry(); entry.data = response.data; entry.etag = serverEtag; entry.softTtl = softExpire; entry.ttl = finalExpire; entry.serverDate = serverDate; entry.lastModified = lastModified; entry.responseHeaders = headers; return entry; }
它同時使用了Expires和max-age來計算時間,是由於Expires在HTTP/1.0中已經定義,Cache-Control:max-age在HTTP/1.1中才有定義,爲了向下兼容,僅使用max-age不夠。能夠看到ttl值和softTtl值都是在當前時間的基礎上加上了解析header裏面的值,因此在緩存調度的時候緩存有效時間就是maxAge * 1000,緩存更新時間就是轉換後的headers.get("Expires")時間。在緩存的調度裏面只是取緩存那麼緩存文件是在何時被建立的呢,這就得看它的網絡調度了,下面是NetworkDispatcher
public class NetworkDispatcher extends Thread { @Override public void run() { // 也是先把線程設置爲background group Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { long startTimeMs = SystemClock.elapsedRealtime(); Request<?> request; try { // 在緩存調度時就是把請求加到了這個隊列裏面,它是在RequeQueue裏被初始化的 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 (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); // 終於到了它的網絡執行了 // 這裏在前面已經介紹過了,具體的網絡操做是由mHttpStack負責 NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // 當請求結果狀態碼爲304時,NetworkResponse的notModified會被賦值爲true // 當服務器返回304而且結果已經被傳遞過就終止此次請求 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // 解析Header信息更新ttl和softTtl值 Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // 就是在這裏加入了緩存 // 若是須要緩存而且該請求結果的Header信息已經被解析過 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); } } } }
該線程會輪詢mNetworkQueue挨個取出request,而後調用Network的performRequest聯網取數據,結果會被封裝成一個Response交由ExecutorDelivery將結果傳遞給主線程。NetworkDispatcher和CacheDispatcher共享了同一個mNetworkQueue減小了線程間數據傳遞的維護成本。這裏有一點我還不是很清楚,緩存在加入時會檢查緩存文件是否已到達上限,若是是的話會一直刪除直到達到指定條件,下面是DiskBasedCache
private void pruneIfNeeded(int neededSpace) { if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { return; } ... Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, CacheHeader> entry = iterator.next(); CacheHeader e = entry.getValue(); boolean deleted = getFileForKey(e.key).delete(); if (deleted) { mTotalSize -= e.size; } else { VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", e.key, getFilenameForKey(e.key)); } iterator.remove(); prunedFiles++; if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { break; } } ... }
刪除終止的條件是((mTotalSize + neededSpace) < mMaxCacheSizeInBytes HYSTERESIS_FACTOR),就是:當前緩存空間大小 + 須要的緩存空間大小 < 緩存總空間大小 HYSTERESIS_FATOR,這個HYSTERESIS_FATOR是在該類被初始化的時候就被初始化的private static final float HYSTERESIS_FACTOR = 0.9f;
爲何是0.9我猜是一個經驗值,刪的太多會致使緩存的命中率下降,刪的少了這個方法就得頻繁執行。(有待驗證)
最後看下結果是怎麼處理的,下面是ExecutorDelivery
private class ResponseDeliveryRunnable implements Runnable { ... @Override 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(); } }
它是ExecutorDelivery的一個內部類,結果的傳遞都是經過調用ResponseDelivery的postResponse來完成,postResponse方法裏面會在主線程執行一個Runnable,在這個Runnable裏面作了什麼事情呢,調用了Request的deliverResponse和deliverError方法,它們各自攜帶的參數最後傳遞給了Request裏面的Listener,因此在主線程裏面能夠經過監聽Listener來拿到網絡操做的異步執行結果。好了,整個過程就分析結束了。
已經介紹過了Volley的網絡操做能夠本身決定實現者,因此能夠用OKHttp做爲Volley的HttpStack
具體使用能夠參考 使用OKHttp處理Volley的底層HTTP請求