Android網絡訪問Volley源碼解析
來自: http://blog.csdn.net/u014486880/article/details/50703666
很早之前就想寫下關于Volley的源碼解析。一開始學android網絡訪問都是使用HttpClient,剛接觸么Volley的時候就瞬間愛不釋手,雖說現在項目中使用OkHttp多些(Volley更新慢),但是作為google自家推出的網絡框架,Volley還是有很多值得學習的地方。這篇博客是我對Volley源碼分析后的一個總結。
Volley的使用
Volley的使用非常簡單,相信大家都很熟悉。首先需要獲取到一個RequestQueue對象。
RequestQueue mQueue = Volley.newRequestQueue(context);
如果想通過網絡獲取json,如下:
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); } });
只要在onResponse中處理返回的response即可。如果訪問出錯,則會調用onErrorResonse方法。 注意Volley是異步,是在子線程中進行網絡訪問,而onResponse里的代碼是在主線程中執行。所以使用Volley的地方切記不要把它當成單線程,這是初學者經常犯錯的地方。最后,將這個StringRequest對象添加到RequestQueue里面就可以了。
mQueue.add(stringRequest);
如果要加載圖片,則首先要定義一個ImageCache,用于定義圖片的緩存。通過ImageLoader來加載圖片,ImageListener則用于指定ImageView以及加載失敗和加載過程中默認圖片 :
private final LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>( (int) (Runtime.getRuntime().maxMemory() / 10)) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } };@Override public void putBitmap(String url, Bitmap bitmap) { mLruCache.put(url, bitmap); } @Override public Bitmap getBitmap(String url) { return mLruCache.get(url); } });
ImageListener listener = ImageLoader.getImageListener(imageView,
R.drawable.default, R.drawable.failed); imageLoader.get(imageurl, listener); </pre>介紹完簡單用法之后,就來分析源代碼了。
Volley源碼分析
先看下官網給出的介紹圖:
![]()
這里我們先有個大概的介紹,藍色是主線程,綠色是CacheDispatcher(硬盤緩存)線程,紅色是NetworkDispatcher(網絡請求線程)。我們在主線程中調用RequestQueue的add()方法來添加一條網絡請求,這條請求會先被加入到緩存隊列當中,如果發現可以找到相應的緩存結果就直接讀取緩存并解析,然后回調給主線程。如果在緩存中沒有找到結果,則將這條請求加入到網絡請求隊列中,然后處理發送HTTP請求,解析響應結果,寫入緩存,并回調主線程。接下來詳細的進行分析。
不用說,入口肯定是Volley.newRequestQueue(context)。先看下newRequestQueue的代碼: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) { 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; }</pre> <p>首先封裝得到userAgent,User-Agent 字段設置為 App 的packageName/{versionCode},如果異常則使用 “volley/0”。上面代碼主要是實例化stack ,如果SDK版本大于9,使用HurlStack,否則使用HttpClientStack。實際上HurlStack的內部就是使用HttpURLConnection進行網絡通訊的,而HttpClientStack的內部則是使用HttpClient進行網絡通訊的。也就是說android2.2以上的都是使用HttpURLConnection,否則使用HttpClient。接著new了一個RequestQueue,并調用它的start方法。來看下它的RequestQueue構造方法:</p>
/* Number of network request dispatcher threads to start. / private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;/** Cache interface for retrieving and storing responses. */ private final Cache mCache; /** Network interface for performing requests. */ private final Network mNetwork; /** Response delivery mechanism. */ private final ResponseDelivery mDelivery; /** The network dispatchers. */ private NetworkDispatcher[] mDispatchers;
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }</pre>
初始化主要就是4個參數:mCache、mNetwork、mDispatchers、mDelivery。第一個是硬盤緩存;第二個主要用于Http相關操作;第三個用于轉發請求的;第四個參數用于把結果轉發到UI線程,通過它來對外聲明接口。接下來看下start方法。
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;/** Cache interface for retrieving and storing responses. */ private final Cache mCache; /** Network interface for performing requests. */ private final Network mNetwork; /** Response delivery mechanism. */ private final ResponseDelivery mDelivery; /** The network dispatchers. */ private NetworkDispatcher[] mDispatchers;
/* 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); 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(); } } /** * Stops the cache and network dispatchers. */ public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } } }</pre> <p>首先調用stop()方法,確保此時所有轉發器都處于停止狀態。接下來就new了一個CacheDispatcher轉發器,它其實就是一個線程,用于硬盤緩存。再new了四個NetworkDispatcher轉發器,用于網絡請求。并分別調用這些線程的start()方法。如果是加載圖片,我們還需定義一個imageLoader,來看看Volley中為我們定義的ImageLoader,主要看它的get方法:</p>
public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) { // only fulfill requests that were initiated from the main thread. throwIfNotOnMainThread();final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); // Try to look up the request in the cache of remote images. Bitmap cachedBitmap = mCache.getBitmap(cacheKey); if (cachedBitmap != null) { // Return the cached bitmap. ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); imageListener.onResponse(container, true); return container; } // The bitmap did not exist in the cache, fetch it! ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener); // Update the caller to let them know that they should use the default bitmap. imageListener.onResponse(imageContainer, true); // Check to see if a request is already in-flight. BatchedImageRequest request = mInFlightRequests.get(cacheKey); if (request != null) { // If it is, add this request to the list of listeners. request.addContainer(imageContainer); return imageContainer; } // The request is not already in flight. Send the new request to the network and // track it. Request<?> newRequest = new ImageRequest(requestUrl, new Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { onGetImageSuccess(cacheKey, response); } }, maxWidth, maxHeight, Config.RGB_565, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { onGetImageError(cacheKey, error); } }); mRequestQueue.add(newRequest); mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer)); return imageContainer; }</pre> <p>上面代碼具體流程是這樣,首先通過throwIfNotOnMainThread()方法限制必須在UI線程調用;然后根據傳入的參數計算cacheKey,獲取緩存;如果存在cache,直接將返回結果封裝為一個ImageContainer,然后直接回調imageListener.onResponse(container, true);這時我們就可以設置圖片了。如果不存在,那就初始化一個ImageContainer,然后直接回調imageListener.onResponse(imageContainer, true),這里是為了讓我們設置默認圖片。所以,在實現listener的時候,要先判斷resp.getBitmap()是否為null;接下來檢查該url是否早已加入了請求對了,如果已加入,則將剛初始化的ImageContainer加入BatchedImageRequest。這就是加載圖片時的內存緩存。 <br />
然后調用RequestQueue的add()方法將Request傳入就可以完成網絡請求操作了,讓我們來看看add方法中到底做了什么事。</p>
private final Map<String, Queue< Request<?>>> mWaitingRequests = new HashMap<String, Queue< Request<?>>>();/** * The set of all requests currently being processed by this RequestQueue. A Request * will be in this set if it is waiting in any queue or currently being processed by * any dispatcher. */ private final Set<Request <?>> mCurrentRequests = new HashSet<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<?>>();
/* Adds a Request to the dispatch queue. @param request The request to service @return The passed -in request */ 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; } }</pre> <p>這里首先將請求加入mCurrentRequests,這個mCurrentRequests是一個HashSet,它保存了所有需要處理的Request,主要為了提供cancel的入口。如果該請求不應該被緩存則直接加入mNetworkQueue,然后返回。request.shouldCache()在默認情況下,每條請求都是可以緩存的,當然我們也可以調用Request的setShouldCache(false)方法來改變這一默認行為。 <br />
接下來判斷該請求是否有相同的請求正在被處理,如果有則加入mWaitingRequests;如果沒有,則加入mWaitingRequests.put(cacheKey, null),并將request加入到CacheQueue中。
有了隊列,我們就來看看線程是如何執行的。先看CacheDispatcher。</p>public class CacheDispatcher extends Thread {private static final boolean DEBUG = VolleyLog.DEBUG; /** The queue of requests coming in for triage. */ private final BlockingQueue<Request<?>> mCacheQueue; /** The queue of requests going out to the network. */ private final BlockingQueue<Request<?>> mNetworkQueue; /** The cache to read from. */ private final Cache mCache; /** For posting responses. */ private final ResponseDelivery mDelivery; /** Used for telling us to die. */ private volatile boolean mQuit = false; /** * Creates a new cache triage dispatcher thread. You must call {@link #start()} * in order to begin processing. * * @param cacheQueue Queue of incoming requests for triage * @param networkQueue Queue to post requests that require network to * @param cache Cache interface to use for resolution * @param delivery Delivery interface to use for posting responses */ public CacheDispatcher( BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue, Cache cache, ResponseDelivery delivery) { mCacheQueue = cacheQueue; mNetworkQueue = networkQueue; mCache = cache; mDelivery = delivery; } /** * Forces this dispatcher to quit immediately. If any requests are still in * the queue, they are not guaranteed to be processed. */ public void quit() { mQuit = true; interrupt(); } @Override 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(); while (true) { try { // Get a request from the cache triage queue, blocking until // at least one is available. final Request<?> request = mCacheQueue.take(); 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. 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; } } }
}</pre>
我們要知道CacheDispatcher是硬盤緩存,到此可知Volley也是有二級緩存的。重點看它的run方法。看到while(true)時,我們就知道,它是在不斷的執行的。首先從mCacheQueue中取出緩存,如果沒有取到,就把它加入mNetworkQueue中,再判斷緩存是否過期,如果過期,也放入mNetworkQueue中。否則就取到了可用的緩存了,再調用request.parseNetworkResponse解析從緩存中取出的data和responseHeaders通過mDelivery.postResponse轉發,然后回調到UI線程;我們看下mDelivery.postResponse方法:
@Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); }主要看ResponseDeliveryRunnable。
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { mRequest = request; mResponse = response; mRunnable = runnable; }@SuppressWarnings("unchecked") @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(); } }</pre> <p>在它的run方法中,如果訪問成功會調用mRequest.deliverResponse(mResponse.result)方法,到這里就很明了了,因為每個request子類中都要重寫deliverResponse,最后我們再在這個方法中將響應的數據回調到Response.Listener的onResponse()方法中就可以了。以StringRequest為例:</p>
@Override protected void deliverResponse(String response) { mListener.onResponse (response); }分析完緩存,我們來看下網絡加載。它是在NetworkDispatcher線程中實現的。
public class NetworkDispatcher extends Thread { / The queue of requests to service. */ private final BlockingQueue<Request<?>> mQueue; / The network interface for processing requests. / private final Network mNetwork; /** The cache to write to. / private final Cache mCache; / For posting responses and errors. */ private final ResponseDelivery mDelivery; / Used for telling us to die. */ private volatile boolean mQuit = false;/** * Creates a new network dispatcher thread. You must call {@link #start()} * in order to begin processing. * * @param queue Queue of incoming requests for triage * @param network Network interface to use for performing requests * @param cache Cache interface to use for writing responses to cache * @param delivery Delivery interface to use for posting responses */ public NetworkDispatcher(BlockingQueue<Request<?>> queue, Network network, Cache cache, ResponseDelivery delivery) { mQueue = queue; mNetwork = network; mCache = cache; mDelivery = delivery; }
@Override public void run() { Process.setThreadPriority(Process. THREAD_PRIORITY_BACKGROUND); Request<?> request; while ( true) { 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) { parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog. e(e, "Unhandled exception %s", e.toString()); mDelivery.postError(request, new VolleyError(e)); } } }
}</pre>
首先取出請求;然后通過mNetwork.performRequest(request)處理我們的請求,拿到NetworkResponse。看下performRequest方法:
public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; Map<String, String> responseHeaders = new HashMap<String, String>(); try { // Gather headers. Map<String, String> headers = new HashMap<String, String>(); addCacheHeaders(headers, request.getCacheEntry()); httpResponse = mHttpStack.performRequest(request, headers); StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // Handle cache validation. if (statusCode == HttpStatus.SC_NOT_MODIFIED) { return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, request.getCacheEntry() == null ? null : request.getCacheEntry().data, responseHeaders, true); } // Some responses such as 204s do not have content. We must check. if (httpResponse.getEntity() != null) { responseContents = entityToBytes(httpResponse.getEntity()); } else { // Add 0 byte response as a way of honestly representing a // no-content request. responseContents = new byte[0]; } // if the request is slow, log it. long requestLifetime = SystemClock.elapsedRealtime() - requestStart; logSlowRequests(requestLifetime, request, responseContents, statusLine); if (statusCode < 200 || statusCode > 299) { throw new IOException(); } return new NetworkResponse(statusCode, responseContents, responseHeaders, false); } catch (Exception e) { …… } } }上面方法主要是網絡請求的一些細節,所以如果要修改請求的細節就要到此處修改(后面會講到)。
在這里服務器會返回的數據組裝成一個NetworkResponse對象進行返回。在NetworkDispatcher中收到了NetworkResponse這個返回值后又會調用Request的parseNetworkResponse()方法來解析NetworkResponse中的數據,再將數據寫入到緩存。parseNetworkResponse的實現是交給Request的子類來完成的,不同種類的Request解析的方式不同。如json與gson就有區別。最后與CacheDispatcher一樣調用mDelivery.postResponse(request, response)返回回調,這里就不再分析了。
到這里volley的源碼就分析完了,總結一下:
- 首先初始化RequestQueue,主要就是開啟CacheDispatcher和NetworkDispatcher線程,線程會不斷讀取請求,沒有消息則阻塞。
- 當我們發出請求以后,會根據url,ImageView屬性等,構造出一個cacheKey,然后首先從LruCache中獲取,這個緩存我們自己構建的,這就是內存緩存;如果沒有取到,則判斷是否存在硬盤緩存,這一步是從getCacheDir里面獲取(默認5M);如果沒有取到,則從網絡請求; </ul>
Volley的擴展
添加cookie頭
volley跟httpClient不一樣,它是不會自動添加cookie頭的。但是cookie在應用中卻很重要,它會保證登陸后的操作都處于一個會話中,有效的增加了安全性。那么如何在volley中自動添加cookie呢。
首先在新建Appliaction,當成全局的Application,然后在里面編寫在http頭參數中識別出cookie和添加cookie到Http頭代碼。
/* Checks the response headers for session cookie and saves it if it finds it. @param headers Response Headers. */ public static final void checkSessionCookie(Map<String, String> headers) { Log.e("TAG", "checkSessionCookie->headers:" + headers);if (headers.containsKey(GlobalParams.SET_COOKIE_KEY) && headers.get(GlobalParams.SET_COOKIE_KEY).startsWith(GlobalParams.SESSION_COOKIE)) { String cookie = headers.get(GlobalParams.SET_COOKIE_KEY); if (cookie.length() > 0) { //形如Set-Cookie:JSESSIONID=18D6BCC01453C6EB39BB0C4208F389EE; Path=/smdb //進行解析,取出JSESSIONID的value String[] splitCookie = cookie.split(";"); String[] splitSessionId = splitCookie[0].split("="); cookie = splitSessionId[1]; Editor prefEditor = preferences.edit(); prefEditor.putString(GlobalParams.SESSION_COOKIE, cookie); prefEditor.commit(); } }else { if (null != httpclient.getCookieStore()) { List<Cookie> cookies = httpclient.getCookieStore().getCookies(); for (Cookie cookie : cookies) { if ("JSESSIONID".equals(cookie.getName())) {//取得session的value String sessionId = cookie.getValue(); Editor prefEditor = preferences.edit(); prefEditor.putString(GlobalParams.SESSION_COOKIE, sessionId); prefEditor.commit(); break; } } if (!cookies.isEmpty()) { for (int i = 0; i < cookies.size(); i++) { cookie = cookies.get(i);//保存cookie的信息使得HttpClient和WebView共享同一個cookie } } } } }</pre> <p>接著就要在Request的子類中合適地方添加頭信息,哪個地方合適。我們來看下HurlStack的performRequest方法。</p>
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); map.putAll(additionalHeaders); if (mUrlRewriter != null) { String rewritten = mUrlRewriter.rewriteUrl(url); if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request); for (String headerName : map.keySet()) { connection.addRequestProperty(headerName, map.get(headerName)); } setConnectionParametersForRequest(connection, request); // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromConnection(connection)); for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; }重點看到map.putAll(request.getHeaders());所以我們考慮到如果要給它添加頭信息可以在request的getHeaders()方法中添加。至此我們以StringRequest為例,重寫一個類叫MyStringRequest:
public class MyStringRequest extends StringRequest {private final Map<String, String> mParams; /** * @param method * @param url * @param params * A {@link HashMap} to post with the request. Null is allowed * and indicates no parameters will be posted along with request. * @param listener * @param errorListener */ public MyStringRequest(int method, String url, Map<String, String> params, Listener<String> listener, ErrorListener errorListener) { super(method, url, listener, errorListener); mParams = params; } @Override protected Map<String, String> getParams() { return mParams; } /* (non-Javadoc) * @see com.android.volley.toolbox.StringRequest#parseNetworkResponse(com.android.volley.NetworkResponse) */ @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { // since we don't know which of the two underlying network vehicles // will Volley use, we have to handle and store session cookies manually Log.e("TAG", "parseNetworkResponse->response.headers:" + response.headers); GlobalApplication.checkSessionCookie(response.headers); return super.parseNetworkResponse(response); } /* (non-Javadoc) * @see com.android.volley.Request#getHeaders() */ @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = super.getHeaders(); if (headers == null || headers.equals(Collections.emptyMap())) { headers = new HashMap<String, String>(); } GlobalApplication.addSessionCookie(headers); return headers; }
}</pre>
在parseNetworkResponse中調用checkSessionCookie解析頭信息中的cookie,然后重寫getHeaders方法,調用addSessionCookie添加cookie。
添加重定向功能
網絡訪問經常要用到重定向,雖說在客戶端中用得比較少。那Volley能不能進行自動重定向,答案是可以的,重要修改下源碼。既然要重定向,那就要在請求返回的進行判斷,毫無疑問要在BasicNetwork的performRequest中修改,先看下修改后的代碼:
@Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; Map<String, String> responseHeaders = new HashMap<String, String>(); try { // Gather headers. Map<String, String> headers = new HashMap<String, String>(); addCacheHeaders(headers, request.getCacheEntry()); httpResponse = mHttpStack.performRequest(request, headers); StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode();responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // Handle cache validation. if (statusCode == HttpStatus.SC_NOT_MODIFIED) { return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, request.getCacheEntry() == null ? null : request.getCacheEntry().data, responseHeaders, true); } // Handle moved resources //Line143-148為解決301/302重定向問題增加的代碼。 //參考見https://github.com/elbuild/volley-plus/commit/4a65a4099d2b1d942f4d51a6df8734cf272564eb#diff-b4935f77d9f815bb7e0dba85e55dc707R150 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { String newUrl = responseHeaders.get("Location"); request.setRedirectUrl(newUrl); } // Some responses such as 204s do not have content. We must check. if (httpResponse.getEntity() != null) { responseContents = entityToBytes(httpResponse.getEntity()); } else { // Add 0 byte response as a way of honestly representing a // no-content request. responseContents = new byte[0]; } // if the request is slow, log it. long requestLifetime = SystemClock.elapsedRealtime() - requestStart; logSlowRequests(requestLifetime, request, responseContents, statusLine); if (statusCode < 200 || statusCode > 299) { throw new IOException(); } return new NetworkResponse(statusCode, responseContents, responseHeaders, false); } catch (SocketTimeoutException e) { attemptRetryOnException("socket", request, new TimeoutError()); } catch (ConnectTimeoutException e) { attemptRetryOnException("connection", request, new TimeoutError()); } catch (MalformedURLException e) { throw new RuntimeException("Bad URL " + request.getUrl(), e); } catch (IOException e) { int statusCode = 0; NetworkResponse networkResponse = null; if (httpResponse != null) { statusCode = httpResponse.getStatusLine().getStatusCode(); } else { throw new NoConnectionError(e); } //Line143-148為解決301/302重定向問題增加的代碼。 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl()); } else { VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); } if (responseContents != null) { networkResponse = new NetworkResponse(statusCode, responseContents, responseHeaders, false); if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) { attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { //else if語句為解決301/302重定向問題增加的代碼。設置重連請求。 attemptRetryOnException("redirect", request, new AuthFailureError(networkResponse)); } else { // TODO: Only throw ServerError for 5xx status codes. throw new ServerError(networkResponse); } } else { throw new NetworkError(networkResponse); } } } }</pre> <p>其實重點添加了以下的代碼:</p>
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) { String newUrl = responseHeaders.get("Location"); request.setRedirectUrl(newUrl); }上面的代碼就是判斷返回code是否是301或302,如果是就獲取重定向的Url,再設置重定向,很簡單。到此Volley常見的擴展功能就講完了。
</div>
源碼解析的文章都會有點長,寫完也要有耐心。繼續堅持。