volley源碼解析(六)--HurlStack與HttpClientStack之爭,volley完全解析

來源:互聯網
上載者:User

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>是怎麼設計的。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.