前面分析了Volley初始化的基本流程,下面咱們來看一看Volley發送請求的過程。 緩存
StringRequest translateRequset = new StringRequest(Dict_Url + word.toLowerCase(), mResponListener, mErrorListener); mQueue.add(translateRequset);
這是最簡單的發請求過程。服務器
咱們看一下StringRequest的實現。 app
public class StringRequest extends Request<String> { private final Listener<String> mListener; /** * Creates a new request with the given method. * * @param method the request {@link Method} to use * @param url URL to fetch the string at * @param listener Listener to receive the String response * @param errorListener Error listener, or null to ignore errors */ public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } /** * Creates a new GET request. * * @param url URL to fetch the string at * @param listener Listener to receive the String response * @param errorListener Error listener, or null to ignore errors */ public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } }
這個類,主要是一個構造方法,兩個實現方法。咱們一個一個閱讀:socket
構造方法:ide
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } /** * Creates a new GET request. * * @param url URL to fetch the string at * @param listener Listener to receive the String response * @param errorListener Error listener, or null to ignore errors */ public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); }
能夠看到,這個方法,主要保存了最後請求完成的監聽,其他的直接使用父類的。所以,咱們順藤摸瓜看一看父類的構造方法。post
/** * Creates a new request with the given method (one of the values from {@link Method}), * URL, and error listener. Note that the normal response listener is not provided here as * delivery of responses is provided by subclasses, who have a better idea of how to deliver * an already-parsed response. */ public Request(int method, String url, Response.ErrorListener listener) { mMethod = method; mUrl = url; mErrorListener = listener; setRetryPolicy(new DefaultRetryPolicy()); mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url); }
咱們先來看findDefaultTrafficStatesTag方法,由於它比較簡單。fetch
/** * @return The hashcode of the URL's host component, or 0 if there is none. */ private static int findDefaultTrafficStatsTag(String url) { if (!TextUtils.isEmpty(url)) { Uri uri = Uri.parse(url); if (uri != null) { String host = uri.getHost(); if (host != null) { return host.hashCode(); } } } return 0; }
咱們能夠看到,這個方法,主要是將url解析爲Uri,並取出它的host的hashCode。this
而後,咱們看看DefaultRetryPolicy這個類,看看默認的重發策略是什麼樣的。 編碼
/** The default socket timeout in milliseconds */ public static final int DEFAULT_TIMEOUT_MS = 2500; /** The default number of retries */ public static final int DEFAULT_MAX_RETRIES = 1; /** The default backoff multiplier */ public static final float DEFAULT_BACKOFF_MULT = 1f; /** * Constructs a new retry policy using the default timeouts. */ public DefaultRetryPolicy() { this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); } /** * Constructs a new retry policy. * @param initialTimeoutMs The initial timeout for the policy. * @param maxNumRetries The maximum number of retries. * @param backoffMultiplier Backoff multiplier for the policy. */ public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { mCurrentTimeoutMs = initialTimeoutMs; mMaxNumRetries = maxNumRetries; mBackoffMultiplier = backoffMultiplier; } /** * Returns the current timeout. */ @Override public int getCurrentTimeout() { return mCurrentTimeoutMs; } /** * Returns the current retry count. */ @Override public int getCurrentRetryCount() { return mCurrentRetryCount; } /** * Returns the backoff multiplier for the policy. */ public float getBackoffMultiplier() { return mBackoffMultiplier; }
這一段中不難發現,咱們默認的重發策略是2500ms定義爲超時,重發次數爲1次,DEFAULT_BACKOFF_MULT=1.0f。DEFAULT_BACKOFF_MULT稱爲超時因子,每次重發,超時都會乘上這個因子:url
/** * Prepares for the next retry by applying a backoff to the timeout. * @param error The error code of the last attempt. */ @Override public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); if (!hasAttemptRemaining()) { throw error; } }
至此,咱們對於StringRequest的構造方法閱讀完成。
接下來就來看兩個核心方法:deliverResponse和parseNetworkResponse。先不看內容,咱們先來看這兩個方法在哪裏調用的,其實在以前的文章中提到過,可是可能不少人,包括我本身也忘記了,因此此處來回顧一下。
在RequestQueue中,咱們有以下代碼:
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(); } }
其中networkDispatcher是Thread的子類,networkDispatcher的run方法中:
…… // Parse the response here on the worker thread. Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); ……
而另外一個方法,則是在ExecutorDelivery中,咱們在networkDispatcher中也能夠找到它的蹤影:
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); }
mDelivery的類型就是ExecutorDelivery。ExecutorDelivery類咱們尚未讀過,這個放在後面。下面咱們來看看deliverResponse和parseNetworkResponse的實現。
@Override protected void deliverResponse(String response) { mListener.onResponse(response); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); }
deliverResponse不用多說,parseNetworkResponse中,直接用byte[]類型的response中的data,生成字符串。而字符串的編碼方式,則須要從請求的響應中進行解析。
咱們來看一看解析的方法:
public static String parseCharset(Map<String, String> headers, String defaultCharset) { String contentType = headers.get(HTTP.CONTENT_TYPE); if (contentType != null) { String[] params = contentType.split(";"); for (int i = 1; i < params.length; i++) { String[] pair = params[i].trim().split("="); if (pair.length == 2) { if (pair[0].equals("charset")) { return pair[1]; } } } } return defaultCharset; }
在headers的Content-Type字段中,咱們能夠讀出不少信息,它們以分號彼此區分。咱們找到其中關於charset的設置,將其返回。
最後咱們來看一看Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
/** * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. * * @param response The network response to parse headers from * @return a cache entry for the given response, or null if the response is not cacheable. */ 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; 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; } /** * Parse date in RFC1123 format, and return its value as epoch */ public static long parseDateAsEpoch(String dateStr) { try { // Parse date in RFC1123 format if this header contains one return DateUtils.parseDate(dateStr).getTime(); } catch (DateParseException e) { // Date in invalid format, fallback to 0 return 0; } }
這一段是解析http的Response中的緩存機制。
咱們從Response中取出瞭如下一些屬性:Cache-Control;Expires;Last-Modified;Etag
Cache-Control——緩存控制:
no-cache 指示請求或響應消息不能緩存(HTTP/1.0用Pragma的no-cache替換) 根據什麼能被緩存 no-store 用於防止重要的信息被無心的發佈。在請求消息中發送將使得請求和響應消息都不使用緩存。 根據緩存超時 max-age 指示客戶機能夠接收生存期不大於指定時間(以秒爲單位)的響應。 min-fresh 指示客戶機能夠接收響應時間小於當前時間加上指定時間的響應。 max-stale 指示客戶機能夠接收超出超時期間的響應消息。若是指定max-stale消息的值,那麼客戶機能夠 接收超出超時期指定值以內的響應消息。
Expires:
表示存在時間,容許客戶端在這個時間以前不去檢查(發請求),等同max-age的
效果。可是若是同時存在,則被Cache-Control的max-age覆蓋。
Last-Modified:服務器上文件的最後修改時間
Etag:
Etag 主要爲了解決 Last-Modified 沒法解決的一些問題。 1、 一些文件也許會週期性的更改,可是他的內容並不改變(僅僅改變的修改時間),這個時候咱們並不但願客戶端認爲這個文件被修改了,而從新GET; 二、某些文件修改很是頻繁,好比在秒如下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改沒法判斷(或者說UNIX記錄MTIME只能精確到秒) 3、某些服務器不能精確的獲得文件的最後修改時間; 爲此,HTTP/1.1 引入了 Etag(Entity Tags).Etag僅僅是一個和文件相關的標記,能夠是一個版本標記,好比說v1.0.0或者說"2e681a-6-5d044840"這麼一串看起來很神祕的編碼。可是HTTP/1.1標準並無規定Etag的內容是什麼或者說要怎麼實現,惟一規定的是Etag須要放在""內。
最後:
/** Returns a successful response containing the parsed result. */ public static <T> Response<T> success(T result, Cache.Entry cacheEntry) { return new Response<T>(result, cacheEntry); } private Response(T result, Cache.Entry cacheEntry) { this.result = result; this.cacheEntry = cacheEntry; this.error = null; }
Done~~