volley源碼解析(六)--HurlStack與HttpClientStack之爭,volley完全解析
Volley中網路載入有兩種方式,分別是HurlStack與HttpClientStack,我們來看Volley.java中的一段代碼
if (stack == null) {//如果沒有限定stack if (Build.VERSION.SDK_INT >= 9) {//adk版本在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)); } }
由此可見,如果沒有設定stack,則根據當前adk版本自動選擇。在Android 2.2版本之前,HttpClient擁有較少的bug,因此使用它是最好的選擇。
而在Android 2.3版本及以後,HttpURLConnection則是最佳的選擇。它的API簡單,體積較小,因而非常適用於Android項目。壓縮和緩衝機制可以有效地減少網路訪問的流量,在提升速度和省電方面也起到了較大的作用。對於新的應用程式應該更加偏向於使用HttpURLConnection,因為在以後的工作當中我們也會將更多的時間放在最佳化HttpURLConnection上面。
為此,我們需要分別來看這兩個類,在看這兩個之前,我們先來看它們一個簡單的父類HttpStack
/** * An HTTP stack abstraction. * 抽象的http棧 */public interface HttpStack { /** * Performs an HTTP request with the given parameters. * 根據參數,執行http請求 * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, * and the Content-Type header is set to request.getPostBodyContentType().</p> * * @param request the request to perform * @param additionalHeaders additional headers to be sent together with * {@link Request#getHeaders()} * @return the HTTP response */ public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;}
該父類主要規定了,子類必須有一個根據request請求資料,並且返回HttpResponse類的方法
OK,接下來我們先看HurlStack,這個類使用的是HttpURLConnection作為串連方式,在adk較高版本推薦使用(其實目前市場上2.3的系統已經很少見了)
我們直接看這個類的核心方法performRequest()
@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);//http協議 int responseCode = connection.getResponseCode();//擷取響應狀態 if (responseCode == -1) {//-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當中
HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); map.putAll(additionalHeaders);
然後是開啟url串連
URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request);//開啟串連
來看openConnection()方法
/** * Opens an {@link HttpURLConnection} with parameters. * 開啟網路連接 * @param url * @return an open connection * @throws IOException */ private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { HttpURLConnection connection = createConnection(url); int timeoutMs = request.getTimeoutMs(); connection.setConnectTimeout(timeoutMs); connection.setReadTimeout(timeoutMs); connection.setUseCaches(false); connection.setDoInput(true); // use caller-provided custom SslSocketFactory, if any, for HTTPS if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {//https ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); } return connection; } /** * Create an {@link HttpURLConnection} for the specified {@code url}. */ protected HttpURLConnection createConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); }這個方法主要就是調用url.openConnevtion()從而返回一個HttpURLConnection對象,其中的一些逾時設定,是由request本身提供的
另外還根據url是否帶有https,為HttpURLConnection設定setSSLSocketFactory(mSslSocketFactory對象是在構造方法中傳入的)
得到HttpURLConnection,就佈建要求參數
for (String headerName : map.keySet()) {//添加請求參數 connection.addRequestProperty(headerName, map.get(headerName)); }然後是確定請求方式(GET,POST還是別的)
setConnectionParametersForRequest(connection, request);//佈建要求方式
setConnectionParametersForRequest方法:
@SuppressWarnings("deprecation") /** * 佈建要求方式 * @param connection * @param request * @throws IOException * @throws AuthFailureError */ /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { // Prepare output. There is no need to set Content-Length explicitly, // since this is handled by HttpURLConnection using the size of the prepared // output stream. connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(postBody); out.close(); } break; case Method.GET: // Not necessary to set the request method because connection defaults to GET but // being explicit here. connection.setRequestMethod("GET"); break; case Method.DELETE: connection.setRequestMethod("DELETE"); break; case Method.POST: connection.setRequestMethod("POST"); addBodyIfExists(connection, request); break; case Method.PUT: connection.setRequestMethod("PUT"); addBodyIfExists(connection, request); break; case Method.HEAD: connection.setRequestMethod("HEAD"); break; case Method.OPTIONS: connection.setRequestMethod("OPTIONS"); break; case Method.TRACE: connection.setRequestMethod("TRACE"); break; case Method.PATCH: connection.setRequestMethod("PATCH"); addBodyIfExists(connection, request); break; default: throw new IllegalStateException("Unknown method type."); } }
最後擷取響應,將回應標頭資訊封裝成StatusLine對象,再封裝成BasicHttpResponse對象
StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage());//響應狀態類 BasicHttpResponse response = new BasicHttpResponse(responseStatus);
然後為BasicHttpResponse加入響應內容
response.setEntity(entityFromConnection(connection));//解析響應實體
entityFromConnection(HttpURLConnection connection)方法:
/** * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}. * <br>解析出響應實體 * @param connection * @return an HttpEntity populated with data from <code>connection</code>. */ private static HttpEntity entityFromConnection(HttpURLConnection connection) { BasicHttpEntity entity = new BasicHttpEntity(); InputStream inputStream; try { inputStream = connection.getInputStream(); } catch (IOException ioe) { inputStream = connection.getErrorStream(); } entity.setContent(inputStream); entity.setContentLength(connection.getContentLength()); entity.setContentEncoding(connection.getContentEncoding()); entity.setContentType(connection.getContentType()); return entity; }最後,加入回應標頭部內容
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); } }OK,這樣就返回了一個具有完整資訊的HttpResponse對象。整個過程比較簡單,是常規的網路請求內容。
接下來我們看HttpClientStack的實現
同樣,直接來看performRequest()方法
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders); addHeaders(httpRequest, additionalHeaders);//添加緩衝頭 addHeaders(httpRequest, request.getHeaders());//添加要求標頭 onPrepareRequest(httpRequest);//請求預先處理 HttpParams httpParams = httpRequest.getParams(); int timeoutMs = request.getTimeoutMs(); // TODO: Reevaluate this connection timeout based on more wide-scale // data collection and possibly different for wifi vs. 3G. HttpConnectionParams.setConnectionTimeout(httpParams, 5000); HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); return mClient.execute(httpRequest); }
請求步驟,首先是根據請求方式,構造HttpUriRequest對象,並且佈建要求參數
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
createHttpRequest()方法:
/** * Creates the appropriate subclass of HttpUriRequest for passed in request. * 根據請求方式返回對應HttpUriRequest的子類 */ @SuppressWarnings("deprecation") /* protected */ static HttpUriRequest createHttpRequest(Request<?> request, Map<String, String> additionalHeaders) throws AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: { // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); HttpEntity entity; entity = new ByteArrayEntity(postBody); postRequest.setEntity(entity); return postRequest; } else { return new HttpGet(request.getUrl()); } } case Method.GET: return new HttpGet(request.getUrl()); case Method.DELETE: return new HttpDelete(request.getUrl()); case Method.POST: { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(postRequest, request);//佈建要求參數 return postRequest; } case Method.PUT: { HttpPut putRequest = new HttpPut(request.getUrl()); putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(putRequest, request); return putRequest; } case Method.HEAD: return new HttpHead(request.getUrl()); case Method.OPTIONS: return new HttpOptions(request.getUrl()); case Method.TRACE: return new HttpTrace(request.getUrl()); case Method.PATCH: { HttpPatch patchRequest = new HttpPatch(request.getUrl()); patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(patchRequest, request); return patchRequest; } default: throw new IllegalStateException("Unknown request method."); } }從createHttpRequest()方法可以看出,在HttpClient中,只要根據請求方式,new一個HttpGet/HttpPost/....對象就可以了(而urlstack這一步是真的connnection而言的)
接著是為HttpUriRequest對象佈建要求頭部
addHeaders(httpRequest, additionalHeaders);//添加緩衝頭 addHeaders(httpRequest, request.getHeaders());//添加要求標頭
addHeaders方法:
/** * 添加回應標頭 * @param httpRequest * @param headers */ private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) { for (String key : headers.keySet()) { httpRequest.setHeader(key, headers.get(key)); } }最後,將HttpUriRequest對象交給httpClient執行
return mClient.execute(httpRequest);
OK,HttpClientStack比我們想象的還要簡單,起碼比HurlStack簡單,這是當然的,因為使用httpClient方式,其本質就是對urlConnection的封裝,然而這個封裝並不是很完美,所以造成了版本之間的差異。
到此為止,給大家介紹了HurlStack與HttpClientStack這兩個類,同時也說明了真正的網路請求在哪裡執行。
下一篇文章,將會來瞭解Response<T>的使用,Response<T>是Volley整個過程中,輾轉獲得的最終目的,作為響應實體,我們來看一下Response<T>是怎麼設計的。