最近實習一直都沒有寫博客了,如今實習也穩定了,決定從新開始堅持寫博客,爭取每週寫一篇。
打算寫一個Volley系列,雖然網上已經有許多關於Volley的文章,而且好文章也很多,但仍是要寫。一方面是由於Volley太經典了,另外一方面閱讀源碼也不是很困難,能從Volley源碼中學到許多知識,設計模式、緩存策略、打log方法、網絡知識等等。就把它看成課本同樣,一行一行代碼去閱讀,去學習,去思考它爲何這樣作,這樣作的好處以及弊端。好了,廢話少說,讓咱們開始Volley之旅吧!
這篇文章重在激發你的興趣,帶着問題和興趣去看,效果會更好。看看Volley有什麼厲害的地方,令那麼多人都去看去學:html
一、首先擴展性強,主要是基於接口設計,可配置性也強。
二、許多地方都體現了單一職責,後面會娓娓道來
三、Volley的緩存設計有一些地方設計的至關巧妙,真的很贊!有一種中單和打野聯動起來的妙趣橫生。
四、網絡問題的處理,例如地址重定向,重複請求等等。
五、如何設置日誌來記錄請求信息,用於調試?
六、怎麼管理那麼多的請求,又是怎麼來分發他們?
七、怎麼來進行組合而不是繼承,讓代碼更加靈活,更加易擴展。
上面列舉了幾個有趣的地方,還有許許多多的祕密等着咱們去探索。不過仍是建議先去看看別的博客Volley的基本使用,不過下面我會簡單的介紹Volley的框架,以及請求的分發流程。這些都是爲之後的文章打基礎。android
RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);複製代碼
先從Volley中得到一個RequestQueue,看名字知道是請求隊列,而後建立一個Request,將Request加入到RequestQueue中就完成了一次網絡請求。看起來是真簡單,可是背後是怎麼發送網絡的,又是怎麼將傳回的數據交給咱們在Request中兩個監聽來處理的?接着往下看:設計模式
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
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) {
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;
if (maxDiskCacheBytes <= -1)
{
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
// Disk cache size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
queue.start();
return queue;
}複製代碼
咱們先挑重點的看,細節咱們後面都會說到的。先看返回結果,是一個RequstQueue,緩存
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
或者是
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);複製代碼
能夠看到他須要兩個參數,DiskBasedCache和Network,Network是一個接口,能夠看到他真正穿進來的實現類是BasicNetwork。安全
Network network = new BasicNetwork(stack);複製代碼
上面的代碼都是爲了製造這兩個參數,先略過,挑重點的看,先把總體脈絡掌握,細節就好說了。而後在倒數第二行還執行了queue.start();上面的代碼都是爲了建立一個RequestQueue,而後又執行了這個方法,確定不簡單,讓咱們跟進去看看。bash
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 = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}複製代碼
滑上去看看上面的圖,是否是感受有點眼熟。在這個方法裏面也僅僅是建立CacheDispatcher和NetworkDispatcher,而後執行本身相應的start方法,其實它兩本質上都是線程,執行start方法就是將線程開啓,而mDispatchers.length默認值爲4,也就是說開啓了四個NetworkDispatcher線程(不要太操心爲何是4,默認值就是4,可能通過一些測試吧。或者說4更適合通常狀況,後面會說到這是什麼)具體線程裏面在執行什麼方法,咱們待會在看。網絡
public class CacheDispatcher extends Thread
public class NetworkDispatcher extends Thread複製代碼
好了,讓咱們先緩一會,讓咱們回到最初的起點,一個ResquestQueue引起的血案,讓咱們知道了併發
RequestQueue mQueue = Volley.newRequestQueue(context);複製代碼
這一行代碼最後是開啓了幾個線程。順着主路線往下走app
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);複製代碼
這一大段代碼前面都是爲了創造一個StringRequest ,他須要3個參數,一個url,兩個監聽器。一個是處理返回正確的數據,另外一個是處理錯誤的結果。這兒先劇透一下,不要慌,後面咱們都會說到。最後把StringRequest添加到隊列當中。讓咱們跟進去瞧一瞧。框架
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; } }複製代碼
代碼比較長,不要怕,咱們一點一點看。
// 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);
}複製代碼
先將當前隊列設置到requset成爲他的成員變量,這兒先不深究,後面會涉及到的。而後將request添加到當前隊列當中。注意上面還有一個synchronized,爲了保證線程安全。
// 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;
}複製代碼
而後給request設置序列號,這兒也是爲請求的順序以及優先級考慮,後面也會一一說到,因此說仍是好好的把這個系列看完(逃)。而後就判斷他是否須要緩存,若是不須要直接將它添加到網絡的這個隊列,還記得star方法裏面初始化那兩類分發器,把mNetworkQueue做爲參數傳進了網絡分發器,沒錯,NetworkDispatcher 會在這個隊列取request而後進行網絡請求。而後返回該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; } }複製代碼
這段代碼又是加了synchronized,你會看見只要涉及到集合操做幾乎都要設置線程安全,往下看,先獲取到他的cacheKey,其實就是和url有關的一個字符串。下面一大堆代碼都是爲了減小重複請求,若是有相同的請求就會從緩存裏面獲取response。而後看看她是怎麼實現的。首先看mWaitingRequests是否包含它,包含的話就獲取他對應的queue,而後將它添加進去。若是queue爲空就建立一個而後再把它添加進去。而後cacheKey爲key,對應的queue爲value,添加到mWaitingRequests中。若是mWaitingRequests不包含它,說明緩存裏面沒有對應他的值。而後也是cacheKey爲key,不過null爲value,添加到mWaitingRequests中。最後放進mCacheQueue裏面,而mCacheQueue又是做爲參數穿進了CacheDispatcher分發器裏面。分發器裏面究竟是幹啥了,是否是已經火燒眉毛的想去看一看了。
先不着急,總結一下,從上面的volley直接用法咱們已經知道通過一系列的波折開啓了幾個線程,將request添加到mCacheQueue或者mNetworkQueue中。而後就沒了。下來咱們看看兩個分發器裏面究竟幹了什麼?首先這兩個分發器本質都是線程,上面也說過了,因此他們開啓start方法也就是運行線程的run方法。
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();
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
request = mCacheQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
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. final Request<?> finalRequest = request; mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(finalRequest); } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); } } }複製代碼
第一眼看上去好嚇人,這麼長,其實好多都是註釋,還有一些異常處理,除過這些基本剩下很少了。首先給線程設置優先級,而後初始化緩存,而後有一個while(true),說明線程是一直在執行,而後從隊列裏面取request,細心的同窗可能會發現,怎麼沒有線程同步呢?
/** 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<?>>();複製代碼
由於它兩都是PriorityBlockingQueue,是一個基於優先級堆的無界的併發安全的優先級隊列(FIFO),有兩點,一是線程安全,因此不用加synchronized 關鍵字,二是它是有優先級的,能夠設置request的優先級,後面會說到怎麼來設置request的優先級。接着往下看,若是request設置取消了直接返回繼續,而後再根據request的cacheKey獲取它的緩存實體,若是爲空就將它加到mNetworkQueue,從網絡中獲取。若是緩存過時也讓他從網絡中獲取。若是都沒問題就從從緩存中獲取response,而後再判斷response是否須要刷新,若是不須要刷新,就將該response返回給request。若是須要刷新就從網絡獲取新的responseesponse而後返回給request。中間有一些細節可能沒說,咱們先抓住主幹,後面都會提到的。
總結一下,先進行一系列的判斷,而後若是緩存裏面有就從緩存裏面取,若是沒有就將request加入mNetworkQueue從網絡中獲取。而後看看NetworkDispatcher 怎麼進行網絡獲取的。
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
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 the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
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);
}
}
}複製代碼
仍是好嚇人,毛主席的話,都是紙老虎(真的是紙老虎)。讓咱們繼續看,發現其實和上一個是同樣的套路,只看一個:
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);複製代碼
能夠看出是從這來進行網絡獲取的。mNetwork是一個接口,他有直接的實現類,從他的子類來進行網絡請求的。就不繼續往下追了,後面的文章會一一介紹的。好了,這篇文章基本也接近尾聲了。讓咱們回憶一下,首先從Volley中獲取一個requestQueue,裏面會開啓幾個線程,而後建立一個request,添加到requestQueue,若是該request有緩存從緩存裏面取,若是沒有就從網絡中獲取,再看一下上面的圖,是否是感受清晰了好多。這篇文章說這麼可能是讓你瞭解一下volley的流程,爲後面的文章打基礎。看的不是很明白也沒有關係,讓你有一個概念。可能有的人已經發現圖是郭神的,文章也有點像,沒錯,我也是看郭神的文章過來的,站在巨人的肩膀上纔會看的更遠嘛。下篇文章會以緩存爲切入點,讓你瞭解Volley是怎麼巧妙的設計緩存的,真的很精彩,敬請期待吧!