上一節,介紹了HurlStack的實現,根據咱們外層的代碼:html
/** * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. * * @param context A {@link Context} to use for creating the cache dir. * @param stack An {@link HttpStack} to use for the network, or null for default. * @return A started {@link RequestQueue} instance. */ 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; }
這一節,我將閱讀並記錄BasicNetwork的實現。android
/** * @param httpStack HTTP stack to be used */ public BasicNetwork(HttpStack httpStack) { // If a pool isn't passed in, then build a small default pool that will give us a lot of // benefit and not use too much memory. this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); } /** * @param httpStack HTTP stack to be used * @param pool a buffer pool that improves GC performance in copy operations */ public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { mHttpStack = httpStack; mPool = pool; }
先看BasicNetwork的構造方法。咱們在此方法中,傳入了HttpStack,這個上一篇已經分析過了。而後咱們新建了一個ByteArrayPool傳入。咱們能夠閱讀一下ByteArrayPool這個類。數組
public class ByteArrayPool { /** The buffer pool, arranged both by last use and by buffer size */ private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>(); private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64); /** The total size of the buffers in the pool */ private int mCurrentSize = 0; /** * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay * under this limit. */ private final int mSizeLimit; /** Compares buffers by size */ protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() { @Override public int compare(byte[] lhs, byte[] rhs) { return lhs.length - rhs.length; } }; /** * @param sizeLimit the maximum size of the pool, in bytes */ public ByteArrayPool(int sizeLimit) { mSizeLimit = sizeLimit; } /** * Returns a buffer from the pool if one is available in the requested size, or allocates a new * one if a pooled one is not available. * * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be * larger. * @return a byte[] buffer is always returned. */ public synchronized byte[] getBuf(int len) { for (int i = 0; i < mBuffersBySize.size(); i++) { byte[] buf = mBuffersBySize.get(i); if (buf.length >= len) { mCurrentSize -= buf.length; mBuffersBySize.remove(i); mBuffersByLastUse.remove(buf); return buf; } } return new byte[len]; } /** * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted * size. * * @param buf the buffer to return to the pool. */ public synchronized void returnBuf(byte[] buf) { if (buf == null || buf.length > mSizeLimit) { return; } mBuffersByLastUse.add(buf); int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); if (pos < 0) { pos = -pos - 1; } mBuffersBySize.add(pos, buf); mCurrentSize += buf.length; trim(); } /** * Removes buffers from the pool until it is under its size limit. */ private synchronized void trim() { while (mCurrentSize > mSizeLimit) { byte[] buf = mBuffersByLastUse.remove(0); mBuffersBySize.remove(buf); mCurrentSize -= buf.length; } } }
總體上來講,它是一個ByteArray的緩衝類,它提供了存、取和清理三個功能。目的是讓ByteArray保持在合適的長度,這個設計理念很是相似LruCache。 接着就是閱讀BasicNetwork中的performRequest方法。爲何是這個方法,在Volley的第一篇分析中,咱們曾閱讀過RequestQueue的源碼,中間有這樣一段: 緩存
for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); }
而後在NetworkDispatcher這個Thread的子類中的run方法中,又有這樣一段:app
// Perform the network request. NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete");
當時咱們一筆帶過了。BasicNetwork的performRequest方法實現代碼,比較長,咱們只能分段閱讀。socket
@Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; Map<String, String> responseHeaders = Collections.emptyMap(); try { // Gather headers. Map<String, String> headers = new HashMap<String, String>(); addCacheHeaders(headers, request.getCacheEntry()); httpResponse = mHttpStack.performRequest(request, headers);
咱們很容易發現,BasicNetwork的performRequest總體是一個無限循環中。咱們來看看循環裏面的內容:ide
首先建立了HttpResponse、一個存放response內容的byte數組和一個存放response頭的Map。ui
而後,調用addCacheHeader方法,咱們能夠看一下這個方法的實現,比較簡單。 this
private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) { // If there's no cache entry, we're done. if (entry == null) { return; } if (entry.etag != null) { headers.put("If-None-Match", entry.etag); } if (entry.lastModified > 0) { Date refTime = new Date(entry.lastModified); headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); } }
就是將request中緩存的etag和lastModified屬性加入到response的header中。url
最後就是執行HurlStack中的performRequest方法,這個在第二節中已經詳細記錄過,再也不贅述。
接着是下面一段:
httpResponse = mHttpStack.performRequest(request, headers); StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); responseHeaders = convertHeaders(httpResponse.getAllHeaders());
這一段的關鍵方法是converHeaders,咱們來看看它的實現。
/** * Converts Headers[] to Map<String, String>. */ protected static Map<String, String> convertHeaders(Header[] headers) { Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); for (int i = 0; i < headers.length; i++) { result.put(headers[i].getName(), headers[i].getValue()); } return result; }
這個方法其實比較簡單,就是將httpResponse中的headers取出來,放入responseHaeader的map中。通過兩上面段代碼,咱們將緩存中的header和HurlStack返回的httpResponse的header作了合併,最終都存入responseHeaders這個Map中。
// Handle cache validation. if (statusCode == HttpStatus.SC_NOT_MODIFIED) { Entry entry = request.getCacheEntry(); if (entry == null) { return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, responseHeaders, true, SystemClock.elapsedRealtime() - requestStart); } // A HTTP 304 response does not have all header fields. We // have to use the header fields from the cache entry plus // the new ones from the response. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 entry.responseHeaders.putAll(responseHeaders); return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, entry.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart); }
接下來的一段進行了緩存校驗。若是請求返回的狀態碼是304(即SC_NOT_MODIFIED),表示自上次請求以後,所請求的內容沒有任何改變。這種狀況我就能夠直接在緩存中取數據。 不論緩存是否爲空,咱們都會新建一個NetworkResponse對象返回。區別只在於,若是有緩存,咱們會將剛剛合併好的httpResponse的header加入其中。
// 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]; }
校驗完304的狀況後,開始對204進行校驗。便是否返回無內容。若是返回無內容則新建一個空byte數組,若是有內容則須要一個轉化。咱們來看看這個轉化方法entityToBytes:
/** Reads the contents of HttpEntity into a byte[]. */ private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError { PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); byte[] buffer = null; try { InputStream in = entity.getContent(); if (in == null) { throw new ServerError(); } buffer = mPool.getBuf(1024); int count; while ((count = in.read(buffer)) != -1) { bytes.write(buffer, 0, count); } return bytes.toByteArray(); } finally { try { // Close the InputStream and release the resources by "consuming the content". entity.consumeContent(); } catch (IOException e) { // This can happen if there was an exception above that left the entity in // an invalid state. VolleyLog.v("Error occured when calling consumingContent"); } mPool.returnBuf(buffer); bytes.close(); } }
一個比較基礎的數據讀取,運用了以前提到的ByteArrayPool這個類。一次只讀取1024位。值得注意的是,bytes.write進行了重寫。
@Override public synchronized void write(byte[] buffer, int offset, int len) { expand(len); super.write(buffer, offset, len); } /** * Ensures there is enough space in the buffer for the given number of additional bytes. */ private void expand(int i) { /* Can the buffer handle @i more bytes, if not expand it */ if (count + i <= buf.length) { return; } byte[] newbuf = mPool.getBuf((count + i) * 2); System.arraycopy(buf, 0, newbuf, 0, count); mPool.returnBuf(buf); buf = newbuf; }
若是須要讀取的流,長度超過了1024會進行擴展。讀取完成後,會將數據再放回mPool,進行緩存。
接下來,就是performRequest方法的try塊兒中的最後一段:
logSlowRequests(requestLifetime, request, responseContents, statusLine); if (statusCode < 200 || statusCode > 299) { throw new IOException(); } return new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
這一段分爲三步,第一步,檢測慢速請求,第二步,檢測2xx錯誤,第三步,返回NetworkResponse。
咱們先來看看檢測慢速請求的代碼:
/** * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ private void logSlowRequests(long requestLifetime, Request<?> request, byte[] responseContents, StatusLine statusLine) { if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + "[rc=%d], [retryCount=%s]", request, requestLifetime, responseContents != null ? responseContents.length : "null", statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); } }
這一段代碼,只作了一個簡單的判斷,當請求用時,大於SLOW_REQUEST_THRESHOLD_MS時,即爲慢速請求,報出log。
至此,performRequest方法的try塊兒,就閱讀完了。下面閱讀catch部分,這裏涉及了一些錯誤處理方法和重發機制。
} 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); } VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); if (responseContents != null) { networkResponse = new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) { attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } else { // TODO: Only throw ServerError for 5xx status codes. throw new ServerError(networkResponse); } } else { throw new NetworkError(networkResponse); } }
咱們先來看一看attemptRetryOnException方法,在超時的異常中,它首先會被調用。
/** * Attempts to prepare the request for a retry. If there are no more attempts remaining in the * request's retry policy, a timeout exception is thrown. * @param request The request to use. */ private static void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { RetryPolicy retryPolicy = request.getRetryPolicy(); int oldTimeout = request.getTimeoutMs(); try { retryPolicy.retry(exception); } catch (VolleyError e) { request.addMarker( String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); throw e; } request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); }
代碼會取出請求中的重發策略,進行重發。RetryPolicy是個接口,咱們須要在本身實現。RetryPolicy:
/** * Retry policy for a request. */ public interface RetryPolicy { /** * Returns the current timeout (used for logging). */ public int getCurrentTimeout(); /** * Returns the current retry count (used for logging). */ public int getCurrentRetryCount(); /** * Prepares for the next retry by applying a backoff to the timeout. * @param error The error code of the last attempt. * @throws VolleyError In the event that the retry could not be performed (for example if we * ran out of attempts), the passed in error is thrown. */ public void retry(VolleyError error) throws VolleyError; }
RetryPolicy接口包含了三個方法,獲取超時,獲取重發次數和重發。
至此,BasicNetwork的基本實現,概讀了一遍。接下來,會閱讀Volley中的Request。
Done~