今天來順手分析一下谷歌的volley http通訊框架。首先從github上 下載volley的原始碼,
然後新建你自己的工程以後 選擇import module 然後選擇volley。 最後還需要更改1個
配置檔案
就是我選中的那句話。記得要加。不然會報錯。把volley作為一個module 在你的專案中引用的原因是,因為我們要分析原始碼,需要測試我們心中所想。所以這麼做是最方便的。
就相當於eclipse裡面的工程依賴。
有關於volley 如何使用的教程 我就不在這寫了,請自行谷歌,我們直接看原始碼。
1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley.toolbox; 18 19 import android.content.Context; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager.NameNotFoundException; 22 import android.net.http.AndroidHttpClient; 23 import android.os.Build; 24 import android.util.Log; 25 26 import com.android.volley.Network; 27 import com.android.volley.RequestQueue; 28 29 import java.io.File; 30 31 public class Volley { 32 33 /** 34 * Default on-disk cache directory. 35 */ 36 private static final String DEFAULT_CACHE_DIR = "volley"; 37 38 /** 39 * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. 40 * 41 * @param context A {@link Context} to use for creating the cache dir. 42 * @param stack An {@link HttpStack} to use for the network, or null for default. 43 * @return A started {@link RequestQueue} instance. 44 */ 45 public static RequestQueue newRequestQueue(Context context, HttpStack stack) { 46 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); 47 String userAgent = "volley/0"; 48 try { 49 String packageName = context.getPackageName(); 50 PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); 51 userAgent = packageName + "/" + info.versionCode; 52 } catch (NameNotFoundException e) { 53 } 54 55 /** 56 * 注意android 2.3之前一般用httpcilent進行網路互動 2.3包括2.3以後才使用HttpURLConnection 57 * 這裡面 實際上hurlstack就是 HttpURLConnection的一個變種 58 */ 59 if (stack == null) { 60 if (Build.VERSION.SDK_INT >= 9) { 61 62 stack = new HurlStack(); 63 } else { 64 // Prior to Gingerbread, HttpUrlConnection was unreliable. 65 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html 66 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); 67 } 68 } 69 70 Network network = new BasicNetwork(stack); 71 72 //從下面這行語句來看,我們的RequestQueue 是由一個硬碟快取和BasicNetwork 來組成的 73 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); 74 queue.start(); 75 76 return queue; 77 } 78 79 /** 80 * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. 81 * 82 * @param context A {@link Context} to use for creating the cache dir. 83 * @return A started {@link RequestQueue} instance. 84 */ 85 public static RequestQueue newRequestQueue(Context context) { 87 return newRequestQueue(context, null); 88 } 89 }
我們知道一般我們在使用volley的時候 第一句話就是
RequestQueue queue = Volley.newRequestQueue(this);
實際上他的呼叫主要過程就是上面的45-77行。
46-54行 主要是在構造volley的快取目錄,實際上你最後列印出來可以發現volley的快取目錄 一般都在data/data/你程式的包名/volley下。這麼做有一個好處就是一般情況下別的app是無法清除你的快取的。除非root。有一些網路或者是圖片框架的 磁碟快取喜歡放在sd卡上,但是這麼做有的時候會被誤刪除 不是很安全。
59-70行 主要是在構造http請求類,注意70行的BasicNetwork這個類,他就是最終傳送http請求的地方。他的建構函式實際上是接受一個介面
1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley.toolbox; 18 19 import com.android.volley.AuthFailureError; 20 import com.android.volley.Request; 21 22 import org.apache.http.HttpResponse; 23 24 import java.io.IOException; 25 import java.util.Map; 26 27 /** 28 * An HTTP stack abstraction. 29 */ 30 public interface HttpStack { 31 /** 32 * Performs an HTTP request with the given parameters. 33 * 34 * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, 35 * and the Content-Type header is set to request.getPostBodyContentType().</p> 36 * 37 * @param request the request to perform 38 * @param additionalHeaders additional headers to be sent together with 39 * {@link Request#getHeaders()} 40 * @return the HTTP response 41 */ 42 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) 43 throws IOException, AuthFailureError; 44 45 }
所以在這裡你們就應該明白 我們可以自己構造喜歡的http請求實體類,只要他實現了HttpStack這個介面即可,甚至於連NetWork我們都可以自己定義一個實現類。擴充套件性非常好。
73行 透露了兩個資訊,一個是volley使用的硬碟快取類是DiskBasedCache,另外就是告訴我們requestqueue是從哪來的。
我們來簡單看一下這個DiskBasedCache 因為程式碼過多 我只說重要的。他首先規定了一個最大的快取空間 。
1 /** 2 * Default maximum disk usage in bytes. 3 */ 4 private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
然後我們來看看快取是怎麼被存入的
1 /** 2 * Puts the entry with the specified key into the cache. 3 */ 4 @Override 5 public synchronized void put(String key, Entry entry) { 6 //這個地方在存入硬碟快取的時候會先看看是否超過最大容量如果超過要刪除 7 pruneIfNeeded(entry.data.length); 8 File file = getFileForKey(key); 9 try { 10 BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); 11 CacheHeader e = new CacheHeader(key, entry); 12 boolean success = e.writeHeader(fos); 13 if (!success) { 14 fos.close(); 15 VolleyLog.d("Failed to write header for %s", file.getAbsolutePath()); 16 throw new IOException(); 17 } 18 fos.write(entry.data); 19 fos.close(); 20 putEntry(key, e); 21 return; 22 } catch (IOException e) { 23 } 24 boolean deleted = file.delete(); 25 if (!deleted) { 26 VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); 27 } 28 }
這個地方要說一下 那個引數key 實際上就是我們request的cachekey 也就是url.put的操作其實很簡單 我就不過多分析了,我們可以稍微看一下pruneIfNeed這個函式。
1 /** 2 * Map of the Key, CacheHeader pairs 3 */ 4 private final Map<String, CacheHeader> mEntries = 5 new LinkedHashMap<String, CacheHeader>(16, .75f, true);
這個mEntries就是存放我們硬碟快取的地方,注意這邊使用的是linkedHashMap 他的特點就是最近最少使用的在前面,所以你看下面的程式碼
在遍歷刪除時,好處就是刪除的都是最少使用的。這個地方很多寫硬碟快取的人可能都想不到,可以學習一下。
1 /** 2 * Prunes the cache to fit the amount of bytes specified. 3 * 4 * @param neededSpace The amount of bytes we are trying to fit into the cache. 5 */ 6 private void pruneIfNeeded(int neededSpace) { 7 if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { 8 return; 9 } 10 if (VolleyLog.DEBUG) { 11 VolleyLog.v("Pruning old cache entries."); 12 } 13 14 long before = mTotalSize; 15 int prunedFiles = 0; 16 long startTime = SystemClock.elapsedRealtime(); 17 18 Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); 19 while (iterator.hasNext()) { 20 Map.Entry<String, CacheHeader> entry = iterator.next(); 21 CacheHeader e = entry.getValue(); 22 boolean deleted = getFileForKey(e.key).delete(); 23 if (deleted) { 24 mTotalSize -= e.size; 25 } else { 26 VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", 27 e.key, getFilenameForKey(e.key)); 28 } 29 iterator.remove(); 30 prunedFiles++; 31 32 if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { 33 break; 34 } 35 } 36 37 if (VolleyLog.DEBUG) { 38 VolleyLog.v("pruned %d files, %d bytes, %d ms", 39 prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); 40 } 41 }
然後我們接著來看 RequestQueue是怎麼構造的。
1 public RequestQueue(Cache cache, Network network) { 2 this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); 3 }
1 /** 2 * Number of network request dispatcher threads to start. 3 */ 4 private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
1 public RequestQueue(Cache cache, Network network, int threadPoolSize) { 2 this(cache, network, threadPoolSize, 3 new ExecutorDelivery(new Handler(Looper.getMainLooper()))); 4 }
1 public RequestQueue(Cache cache, Network network, int threadPoolSize, 2 ResponseDelivery delivery) { 3 mCache = cache; 4 mNetwork = network; 5 mDispatchers = new NetworkDispatcher[threadPoolSize]; 6 mDelivery = delivery; 7 }
具體的構造呼叫鏈就是這樣的,這個地方就出現了2個新類,一個是NetworkDispatcher 他實際上就是工作執行緒 用來發http請求的。另外還有一個就是ExecutorDelivery 他是傳遞結果的,就是解析出來的流資料 由他來傳遞,注意他的寫法 實際上表明瞭
這個訊息傳遞者實在主執行緒裡工作的。
然後看看我們的start函式 這邊我就不多分析了 註釋裡都有 比較好理解。另外著重解釋一下 那兩個佇列
//PriorityBlockingQueue:類似於LinkedBlockQueue,但其所含物件的排序不是FIFO, //而是依據物件的自然排序順序或者是建構函式的Comparator決定的順序. //對於volley而言 你可以重寫request的getPriority方法 這個方法的返回值越高 //在這個佇列裡的優先順序就越高 就越排在前面 並非是FIFO佇列 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<?>>();
/** * 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); //啟動了一個執行緒 這個執行緒是快取執行緒 所以快取執行緒在volley中只有一個 mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. //DEFAULT_NETWORK_THREAD_POOL_SIZE 這個預設值是4 所以mDispatchers的預設大小是4 //這個地方就是在給這個陣列賦值 賦值結束以後就直接啟動了 for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } //所以這個函式結束的時候volley一共啟動了5個子執行緒 () 這個地方要注意他們2是公用的mNetworkQueue這個佇列 //mCache 磁碟快取 mDelivery用於分發處理好的結果 mDelivery就是ExecutorDelivery的物件 }
當我們構造完這個佇列以後,我們就會構造一個request 然後把這個request物件 add到這個佇列裡。然後就能看到伺服器返回的資料。
那我們就來最終看看add函式 做了哪些操作。這個地方很多人搞不清mWaitingRequests是幹嘛的,實際上他也是快取,只不過他快取的東西比較特殊。
1 /** 2 * 這個地方mWaitingRequests是一個雜湊表,注意這個雜湊表的key 為request的url, 3 * 而value則是一個佇列,他的作用是 如果請求的url是一樣的,那麼就把這些請求 4 * 也就是request放到一個佇列裡面,舉例來說,如果abc三條request的url是一樣的, 5 * 那麼假設a是第一個被add的,那麼後面的bc將會放到這個map裡value裡的佇列裡面 6 * bc並不會得到真正的執行。真正執行的只有a 7 * 這麼做的好處 其實顯而易見 ,比如我們介面上點選某個按鈕我們不想讓他點選的時候 8 * 彈進度狂,只想在後臺發請求,但是如果你這麼做的話,如果使用者連續點選誤操作, 9 * 就會一直髮請求會浪費很多流量,而在這裡 有這個hashmap的保護,多數情況下 10 * 大部分的重複url請求會被遮蔽掉,只有一開始的才會得到執行 11 */ 12 private final Map<String, Queue<Request<?>>> mWaitingRequests = 13 new HashMap<String, Queue<Request<?>>>();
1 /** 2 * 實際上這個地方add函式並沒有執行網路請求的任何操作, 3 * 4 */ 5 public <T> Request<T> add(Request<T> request) { 6 // Tag the request as belonging to this queue and add it to the set of current requests. 7 request.setRequestQueue(this); 8 synchronized (mCurrentRequests) { 9 mCurrentRequests.add(request); 10 } 11 12 // Process requests in the order they are added. 13 request.setSequence(getSequenceNumber()); 14 request.addMarker("add-to-queue"); 16 // If the request is uncacheable, skip the cache queue and go straight to the network. 17 //判斷是否能快取 不能快取 就直接加入mNetworkQueue佇列執行 能快取的話就放在快取佇列裡面 18 //注意這個地方的標誌位是我們手動可以設定的,也就是說你寫的request想讓他使用快取機制就 19 //使用預設的,不想使用快取機制 可以手動設定shouldCache為false然後這條request 會直接進入 20 //網路請求佇列 21 if (!request.shouldCache()) { 22 mNetworkQueue.add(request); 23 return request; 24 } 25 26 // Insert request into stage if there's already a request with the same cache key in flight. 27 synchronized (mWaitingRequests) { 28 String cacheKey = request.getCacheKey(); 29 if (mWaitingRequests.containsKey(cacheKey)) { 30 // There is already a request in flight. Queue up. 31 Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); 32 if (stagedRequests == null) { 33 stagedRequests = new LinkedList<Request<?>>(); 34 } 35 stagedRequests.add(request); 36 mWaitingRequests.put(cacheKey, stagedRequests); 37 if (VolleyLog.DEBUG) { 38 VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); 39 } 40 } else { 41 // Insert 'null' queue for this cacheKey, indicating there is now a request in 42 // flight. 43 mWaitingRequests.put(cacheKey, null); 44 mCacheQueue.add(request); 45 } 46 return request; 47 } 48 }
所以這個地方mWaitngRequests的作用就是 如果你在極短的時間內訪問同一個url,volley是不會幫你每次都發請求的,只有最初的會得到請求,後面的是不會有任何操作的。
這個地方大家可以寫一段程式碼去試試,我寫過一個程式碼 就是add一個StringRequst的陣列,陣列大小為20,然後抓包看,實際上最終發的請求 走流量的也就是3-4個,其他都是
直接返回的結果,但是你如果更改了shouldCache的標誌位 那就是直接傳送請求,會請求20次,所以這個地方要注意。當然你在實際使用的時候 要不要使用這個機制 都是看實際效果的。
那假設 我們是使用的shouldcache預設值,然後這個request 被add了進來(在這之前相同url的request還沒有被add) 那這個時候我們真正的快取佇列 mCacheQueue佇列就
add了一個request值,所以這個時候我們去看看我們的快取執行緒都幹了些什麼
1 @Override 2 public void run() { 3 if (DEBUG) VolleyLog.v("start new dispatcher"); 4 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 5 6 // Make a blocking call to initialize the cache. 7 mCache.initialize(); 8 //這裡證明快取執行緒是一直無限執行下去的 9 while (true) { 10 try { 11 // Get a request from the cache triage queue, blocking until 12 // at least one is available. 13 //從快取佇列裡面取出一個request 14 final Request<?> request = mCacheQueue.take(); 15 request.addMarker("cache-queue-take"); 16 17 // If the request has been canceled, don't bother dispatching it. 18 if (request.isCanceled()) { 19 request.finish("cache-discard-canceled"); 20 continue; 21 } 22 23 // Attempt to retrieve this item from cache. 24 Cache.Entry entry = mCache.get(request.getCacheKey()); 25 //如果為空就放到請求佇列中 26 if (entry == null) { 27 request.addMarker("cache-miss"); 28 // Cache miss; send off to the network dispatcher. 29 mNetworkQueue.put(request); 30 continue; 31 } 32 33 // If it is completely expired, just send it to the network. 34 //如果entry不為空的話 判斷這個快取是否過期 如果過期也要放到網路請求佇列中 35 if (entry.isExpired()) { 36 request.addMarker("cache-hit-expired"); 37 request.setCacheEntry(entry); 38 mNetworkQueue.put(request); 39 continue; 40 } 41 42 //這下面的就說明不需要髮網路請求 可以直接解析了 43 // We have a cache hit; parse its data for delivery back to the request. 44 request.addMarker("cache-hit"); 45 Response<?> response = request.parseNetworkResponse( 46 new NetworkResponse(entry.data, entry.responseHeaders)); 47 request.addMarker("cache-hit-parsed"); 48 49 //判斷是否過期以後還要判斷是否需要重新整理 50 if (!entry.refreshNeeded()) { 51 // Completely unexpired cache hit. Just deliver the response. 52 //不需要重新整理就直接解析 53 mDelivery.postResponse(request, response); 54 } else { 55 //如果需要重新整理的話則必須重新將這個request放到mNetworkQueue裡面去請求一次 56 // Soft-expired cache hit. We can deliver the cached response, 57 // but we need to also send the request to the network for 58 // refreshing. 59 request.addMarker("cache-hit-refresh-needed"); 60 request.setCacheEntry(entry); 61 62 // Mark the response as intermediate. 63 response.intermediate = true; 64 65 // Post the intermediate response back to the user and have 66 // the delivery then forward the request along to the network. 67 mDelivery.postResponse(request, response, new Runnable() { 68 @Override 69 public void run() { 70 try { 71 mNetworkQueue.put(request); 72 } catch (InterruptedException e) { 73 // Not much we can do about this. 74 } 75 } 76 }); 77 } 78 79 } catch (InterruptedException e) { 80 // We may have been interrupted because it was time to quit. 81 if (mQuit) { 82 return; 83 } 84 continue; 85 } 86 } 87 }
這個地方註釋也比較多,所以大概的流程就是 先判斷是否有硬碟快取,如果沒有就直接放到network佇列裡去,如果有的話 還要判斷是否過期,沒過期就接著判斷是否需要重新整理。這邊邏輯其實很簡單。
所謂的 "過期" "重新整理” 什麼的 實際上就是對http協議裡面的一些欄位的判斷罷了,這個地方大家在使用的時候一定要注意和你們伺服器的情況結合來使用,否則這邊volley的快取機制 會失效。
比方說一個最簡單的場景 很多公司圖片快取的時候 都是根據url來判斷 本地是否有快取圖片的,但是volley在這個地方 不是使用的url來判斷 他是使用http 協議裡面 頭部的那些資訊來判斷的。
這種寫法很規範 很標準,但是缺陷就是對於那些不遵守http協議的伺服器來說 這邊程式碼是要自己重新寫一遍的。
45-46行就是解析出response用的,呼叫的是虛方法
1 abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
由此可見是由他的子類 來完成的 我們就看看子類stringrequest是怎麼做的。
1 @Override 2 protected Response<String> parseNetworkResponse(NetworkResponse response) { 3 String parsed; 4 try { 5 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 6 } catch (UnsupportedEncodingException e) { 7 parsed = new String(response.data); 8 } 10 return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); 11 }、
然後我們接著看 如果快取有效,那我們是如何解析出來然後傳遞訊息的。
1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley; 18 19 import android.os.Handler; 20 21 import java.util.concurrent.Executor; 22 23 /** 24 * Delivers responses and errors. 25 */ 26 public class ExecutorDelivery implements ResponseDelivery { 27 /** Used for posting responses, typically to the main thread. */ 28 private final Executor mResponsePoster; 29 30 /** 31 * Creates a new response delivery interface. 32 * @param handler {@link Handler} to post responses on 33 */ 34 public ExecutorDelivery(final Handler handler) { 35 // Make an Executor that just wraps the handler. 36 mResponsePoster = new Executor() { 37 @Override 38 public void execute(Runnable command) { 39 handler.post(command); 40 } 41 }; 42 } 43 44 /** 45 * Creates a new response delivery interface, mockable version 46 * for testing. 47 * @param executor For running delivery tasks 48 */ 49 public ExecutorDelivery(Executor executor) { 50 mResponsePoster = executor; 51 } 52 53 @Override 54 public void postResponse(Request<?> request, Response<?> response) { 55 postResponse(request, response, null); 56 } 57 58 @Override 59 public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { 60 request.markDelivered(); 61 request.addMarker("post-response"); 62 mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); 63 } 64 65 @Override 66 public void postError(Request<?> request, VolleyError error) { 67 request.addMarker("post-error"); 68 Response<?> response = Response.error(error); 69 mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); 70 } 71 72 /** 73 * A Runnable used for delivering network responses to a listener on the 74 * main thread. 75 */ 76 @SuppressWarnings("rawtypes") 77 private class ResponseDeliveryRunnable implements Runnable { 78 private final Request mRequest; 79 private final Response mResponse; 80 private final Runnable mRunnable; 81 82 public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { 83 mRequest = request; 84 mResponse = response; 85 mRunnable = runnable; 86 } 87 88 @SuppressWarnings("unchecked") 89 @Override 90 public void run() { 91 // If this request has canceled, finish it and don't deliver. 92 if (mRequest.isCanceled()) { 93 mRequest.finish("canceled-at-delivery"); 94 return; 95 } 96 97 // Deliver a normal response or error, depending. 98 if (mResponse.isSuccess()) { 99 //這個地方就能看出來這是最終呼叫子類的方法去傳遞解析好的資料 100 mRequest.deliverResponse(mResponse.result); 101 } else { 102 mRequest.deliverError(mResponse.error); 103 } 104 105 // If this is an intermediate response, add a marker, otherwise we're done 106 // and the request can be finished. 107 if (mResponse.intermediate) { 108 mRequest.addMarker("intermediate-response"); 109 } else { 110 mRequest.finish("done"); 111 } 112 113 // If we have been provided a post-delivery runnable, run it. 114 if (mRunnable != null) { 115 mRunnable.run(); 116 } 117 } 118 } 119 }
實際上訊息的傳遞 就是在77-119這個runnable裡面做的。注意100行程式碼 那個deliverResponse實際上是一個虛方法。是留給子類來重寫的。
我們就看一下stringrequest的這個方法吧
1 //可以看到這request的子類 StringRequest 的deliverResponse 方法裡面是一個回撥 2 //並沒有真正的實現它 3 @Override 4 protected void deliverResponse(String response) { 5 mListener.onResponse(response); 6 }
1 private final Listener<String> mListener;
這個地方實際上就是回撥了,留給我們自己寫的。
比如
new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { // Display the first 500 characters of the response string. } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } } );
到此,快取處理執行緒的流程就介紹完畢了,我們最後再看看實際上的工作執行緒NetWorkDispatcher
1 @Override 2 public void run() { 3 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 4 while (true) { 5 long startTimeMs = SystemClock.elapsedRealtime(); 6 Request<?> request; 7 try { 8 // Take a request from the queue. 9 request = mQueue.take(); 10 } catch (InterruptedException e) { 11 // We may have been interrupted because it was time to quit. 12 if (mQuit) { 13 return; 14 } 15 continue; 16 } 17 18 try { 19 request.addMarker("network-queue-take"); 20 21 // If the request was cancelled already, do not perform the 22 // network request. 23 if (request.isCanceled()) { 24 request.finish("network-discard-cancelled"); 25 continue; 26 } 27 28 addTrafficStatsTag(request); 29 30 // Perform the network request. 31 //這個地方就是實際發請求的地方 32 NetworkResponse networkResponse = mNetwork.performRequest(request); 33 request.addMarker("network-http-complete"); 34 35 // If the server returned 304 AND we delivered a response already, 36 // we're done -- don't deliver a second identical response. 37 if (networkResponse.notModified && request.hasHadResponseDelivered()) { 38 request.finish("not-modified"); 39 continue; 40 } 41 42 // Parse the response here on the worker thread. 43 //這個地方要注意 networkResponse是交給request來解析的 但是我們的request會有很多子類 44 //parseNetworkResponse的方法重寫以後在這裡最終解析資料 45 Response<?> response = request.parseNetworkResponse(networkResponse); 46 request.addMarker("network-parse-complete"); 47 48 // Write to cache if applicable. 49 // TODO: Only update cache metadata instead of entire record for 304s. 50 if (request.shouldCache() && response.cacheEntry != null) { 51 mCache.put(request.getCacheKey(), response.cacheEntry); 52 request.addMarker("network-cache-written"); 53 } 54 55 // Post the response back. 56 request.markDelivered(); 57 //回撥傳遞資料 58 mDelivery.postResponse(request, response); 59 } catch (VolleyError volleyError) { 60 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); 61 parseAndDeliverNetworkError(request, volleyError); 62 } catch (Exception e) { 63 VolleyLog.e(e, "Unhandled exception %s", e.toString()); 64 VolleyError volleyError = new VolleyError(e); 65 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); 66 mDelivery.postError(request, volleyError); 67 } 68 } 69 }
這個地方也非常簡單,主要是看32行 最終發請求,這個地方是交給basicnetwork來傳送的,我們來看
1 @Override 2 public NetworkResponse performRequest(Request<?> request) throws VolleyError { 3 long requestStart = SystemClock.elapsedRealtime(); 4 while (true) { 5 HttpResponse httpResponse = null; 6 byte[] responseContents = null; 7 Map<String, String> responseHeaders = Collections.emptyMap(); 8 try { 9 // Gather headers. 10 Map<String, String> headers = new HashMap<String, String>(); 11 addCacheHeaders(headers, request.getCacheEntry()); 12 httpResponse = mHttpStack.performRequest(request, headers); 13 StatusLine statusLine = httpResponse.getStatusLine(); 14 int statusCode = statusLine.getStatusCode(); 15 16 responseHeaders = convertHeaders(httpResponse.getAllHeaders()); 17 // Handle cache validation. 18 //自動上次請求後 請求的網頁沒有修改過 伺服器返回網頁時 就不會返回實際內容 19 if (statusCode == HttpStatus.SC_NOT_MODIFIED) { 20 21 Entry entry = request.getCacheEntry(); 22 if (entry == null) { 23 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, 24 responseHeaders, true, 25 SystemClock.elapsedRealtime() - requestStart); 26 } 27 28 // A HTTP 304 response does not have all header fields. We 29 // have to use the header fields from the cache entry plus 30 // the new ones from the response. 31 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 32 entry.responseHeaders.putAll(responseHeaders); 33 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, 34 entry.responseHeaders, true, 35 SystemClock.elapsedRealtime() - requestStart); 36 } 37 38 // Some responses such as 204s do not have content. We must check. 39 if (httpResponse.getEntity() != null) { 40 responseContents = entityToBytes(httpResponse.getEntity()); 41 } else { 42 // Add 0 byte response as a way of honestly representing a 43 // no-content request. 44 responseContents = new byte[0]; 45 } 46 47 // if the request is slow, log it. 48 long requestLifetime = SystemClock.elapsedRealtime() - requestStart; 49 logSlowRequests(requestLifetime, request, responseContents, statusLine); 50 51 if (statusCode < 200 || statusCode > 299) { 52 throw new IOException(); 53 } 54 return new NetworkResponse(statusCode, responseContents, responseHeaders, false, 55 SystemClock.elapsedRealtime() - requestStart); 56 } catch (SocketTimeoutException e) { 57 attemptRetryOnException("socket", request, new TimeoutError()); 58 } catch (ConnectTimeoutException e) { 59 attemptRetryOnException("connection", request, new TimeoutError()); 60 } catch (MalformedURLException e) { 61 throw new RuntimeException("Bad URL " + request.getUrl(), e); 62 } catch (IOException e) { 63 int statusCode = 0; 64 NetworkResponse networkResponse = null; 65 if (httpResponse != null) { 66 statusCode = httpResponse.getStatusLine().getStatusCode(); 67 } else { 68 throw new NoConnectionError(e); 69 } 70 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); 71 if (responseContents != null) { 72 networkResponse = new NetworkResponse(statusCode, responseContents, 73 responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); 74 if (statusCode == HttpStatus.SC_UNAUTHORIZED || 75 statusCode == HttpStatus.SC_FORBIDDEN) { 76 attemptRetryOnException("auth", 77 request, new AuthFailureError(networkResponse)); 78 } else { 79 // TODO: Only throw ServerError for 5xx status codes. 80 throw new ServerError(networkResponse); 81 } 82 } else { 83 throw new NetworkError(networkResponse); 84 } 85 } 86 } 87 }
所以這個地方 我們要注意2點。
第一點,這個函式是最終我們發起請求的地方也是解析出response的地方,但是要注意他返回的是NetworkResponse。你回過頭看45行發現通常是由子類的parseNetworkResponse
來把我們basicnetwork得到的networkresponse 解析成 Response<?> response 這個泛型的! 這個順序要理清楚。比如你看strinrequst的這個方法
1 @Override 2 protected Response<String> parseNetworkResponse(NetworkResponse response) { 3 String parsed; 4 try { 5 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 6 } catch (UnsupportedEncodingException e) { 7 parsed = new String(response.data); 8 } 9 Log.v("burning", "parsed=" + parsed); 10 return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); 11 }
再看看JSONrequest這個方法
1 @Override 2 protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { 3 try { 4 String jsonString = new String(response.data, 5 HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET)); 6 return Response.success(new JSONObject(jsonString), 7 HttpHeaderParser.parseCacheHeaders(response)); 8 } catch (UnsupportedEncodingException e) { 9 return Response.error(new ParseError(e)); 10 } catch (JSONException je) { 11 return Response.error(new ParseError(je)); 12 } 13 }
就能明白這個呼叫過程的先後順序了。
50-52行 就是操作硬碟快取的。實際上你看他呼叫鏈最終就是由networkresponse來解析出快取所需要的entry 這個地方就是對http標準協議裡 頭部許多欄位的解析了,
根據解析出來的值判斷快取是否需要重新整理 是否過期等,在你自定義volley的時候 需要根據伺服器的實際情況來重寫這一部分
1 public class HttpHeaderParser { 2 3 /** 4 * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. 5 * 6 * @param response The network response to parse headers from 7 * @return a cache entry for the given response, or null if the response is not cacheable. 8 */ 9 public static Cache.Entry parseCacheHeaders(NetworkResponse response) { 10 long now = System.currentTimeMillis(); 11 12 Map<String, String> headers = response.headers; 13 14 long serverDate = 0; 15 long lastModified = 0; 16 long serverExpires = 0; 17 long softExpire = 0; 18 long finalExpire = 0; 19 long maxAge = 0; 20 long staleWhileRevalidate = 0; 21 boolean hasCacheControl = false; 22 boolean mustRevalidate = false; 23 24 String serverEtag = null; 25 String headerValue; 26 27 headerValue = headers.get("Date"); 28 if (headerValue != null) { 29 serverDate = parseDateAsEpoch(headerValue); 30 } 31 32 headerValue = headers.get("Cache-Control"); 33 if (headerValue != null) { 34 hasCacheControl = true; 35 String[] tokens = headerValue.split(","); 36 for (int i = 0; i < tokens.length; i++) { 37 String token = tokens[i].trim(); 38 if (token.equals("no-cache") || token.equals("no-store")) { 39 return null; 40 } else if (token.startsWith("max-age=")) { 41 try { 42 maxAge = Long.parseLong(token.substring(8)); 43 } catch (Exception e) { 44 } 45 } else if (token.startsWith("stale-while-revalidate=")) { 46 try { 47 staleWhileRevalidate = Long.parseLong(token.substring(23)); 48 } catch (Exception e) { 49 } 50 } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { 51 mustRevalidate = true; 52 } 53 } 54 } 55 56 headerValue = headers.get("Expires"); 57 if (headerValue != null) { 58 serverExpires = parseDateAsEpoch(headerValue); 59 } 60 61 headerValue = headers.get("Last-Modified"); 62 if (headerValue != null) { 63 lastModified = parseDateAsEpoch(headerValue); 64 } 65 66 serverEtag = headers.get("ETag"); 67 68 // Cache-Control takes precedence over an Expires header, even if both exist and Expires 69 // is more restrictive. 70 if (hasCacheControl) { 71 softExpire = now + maxAge * 1000; 72 finalExpire = mustRevalidate 73 ? softExpire 74 : softExpire + staleWhileRevalidate * 1000; 75 } else if (serverDate > 0 && serverExpires >= serverDate) { 76 // Default semantic for Expire header in HTTP specification is softExpire. 77 softExpire = now + (serverExpires - serverDate); 78 finalExpire = softExpire; 79 } 80 81 Cache.Entry entry = new Cache.Entry(); 82 entry.data = response.data; 83 entry.etag = serverEtag; 84 entry.softTtl = softExpire; 85 entry.ttl = finalExpire; 86 entry.serverDate = serverDate; 87 entry.lastModified = lastModified; 88 entry.responseHeaders = headers; 89 90 return entry; 91 }
第二點也是額外我想多講的一點 對於basicnetwork的performrequest方法來說:
他們的超時策略是很重要的東西,這個對於我們app的定製化 和效能體驗來說非常重要,
大家一定要弄明白。在volley中預設是會使用 預設的超時策略的。程式碼如下:
你看requst 這個類的構造方法
public Request(int method, String url, Response.ErrorListener listener) { mMethod = method; mUrl = url; mErrorListener = listener; setRetryPolicy(new DefaultRetryPolicy()); mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url); }
我們就去看看volley 給我們提供的這個預設超時策略
1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley; 18 19 /** 20 * Default retry policy for requests. 21 */ 22 public class DefaultRetryPolicy implements RetryPolicy { 23 /** The current timeout in milliseconds. */ 24 private int mCurrentTimeoutMs; 25 26 /** The current retry count. */ 27 private int mCurrentRetryCount; 28 29 /** The maximum number of attempts. */ 30 private final int mMaxNumRetries; 31 32 /** The backoff multiplier for the policy. */ 33 private final float mBackoffMultiplier; 34 35 /** The default socket timeout in milliseconds */ 36 public static final int DEFAULT_TIMEOUT_MS = 2500; 37 38 /** The default number of retries */ 39 public static final int DEFAULT_MAX_RETRIES = 1; 40 41 /** The default backoff multiplier */ 42 public static final float DEFAULT_BACKOFF_MULT = 1f; 43 44 /** 45 * Constructs a new retry policy using the default timeouts. 46 */ 47 public DefaultRetryPolicy() { 48 this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); 49 } 50 51 /** 52 * Constructs a new retry policy. 53 * @param initialTimeoutMs The initial timeout for the policy. 54 * @param maxNumRetries The maximum number of retries. 55 * @param backoffMultiplier Backoff multiplier for the policy. 56 */ 57 public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { 58 mCurrentTimeoutMs = initialTimeoutMs; 59 mMaxNumRetries = maxNumRetries; 60 mBackoffMultiplier = backoffMultiplier; 61 } 62 63 /** 64 * Returns the current timeout. 65 */ 66 @Override 67 public int getCurrentTimeout() { 68 return mCurrentTimeoutMs; 69 } 70 71 /** 72 * Returns the current retry count. 73 */ 74 @Override 75 public int getCurrentRetryCount() { 76 return mCurrentRetryCount; 77 } 78 79 /** 80 * Returns the backoff multiplier for the policy. 81 */ 82 public float getBackoffMultiplier() { 83 return mBackoffMultiplier; 84 } 85 86 /** 87 * Prepares for the next retry by applying a backoff to the timeout. 88 * @param error The error code of the last attempt. 89 */ 90 @Override 91 public void retry(VolleyError error) throws VolleyError { 92 mCurrentRetryCount++; 93 mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); 94 if (!hasAttemptRemaining()) { 95 throw error; 96 } 97 } 98 99 /** 100 * Returns true if this policy has attempts remaining, false otherwise. 101 */ 102 protected boolean hasAttemptRemaining() { 103 return mCurrentRetryCount <= mMaxNumRetries; 104 } 105 }
他規定了超時時間 超時以後嘗試重連的次數等。我就隨便分析一下 在超時策略裡定義的超時時間 是怎麼影響最終http請求的。
超時策略類裡面 的這個函式
1 /** 2 * Returns the current timeout. 3 */ 4 @Override 5 public int getCurrentTimeout() { 6 return mCurrentTimeoutMs; 7 }
往下走反映到request裡的這個函式
1 public final int getTimeoutMs() { 2 return mRetryPolicy.getCurrentTimeout(); 3 }
是final函式,我們來看看他被哪些類呼叫了
然後我們隨便開啟1個
1 private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { 2 HttpURLConnection connection = createConnection(url); 3 4 int timeoutMs = request.getTimeoutMs(); 5 connection.setConnectTimeout(timeoutMs); 6 connection.setReadTimeout(timeoutMs); 7 connection.setUseCaches(false); 8 connection.setDoInput(true);
到這呼叫鏈就分析完畢。
之所以要額外分析超時策略 是因為谷歌自帶的超時策略 並不是很好用 他預設的超時時間是2.5s 如果你網路情況比較差 又在上傳圖片的話
這個2.5s超時策略是完全不夠的,此時就會引發很多bug了。所以這個地方要單獨出來講一下。
至此,volley 的 大部分原始碼都分析完畢了。建議讀者在看的時候 要多寫demo 打日誌 來驗證想法,除此之外,要真正讀懂volley原始碼
還需要在java 併發程式設計那邊下點功夫~~