Volley源碼完全解析之HttpStack

HttpURLConnection和HttpClient

  • 第一篇系列文章中我們說(shuō)過(guò),volley的底層是使用HttpURLConnection和HttpClient來(lái)實(shí)現(xiàn)的,所以在對(duì)HttpStack進(jìn)行分析時(shí),我們需要對(duì)HttpURLConnection和HttpClient進(jìn)行必要的了解。

HttpURLConnection

  • HttpURLConnection是Java的標(biāo)準(zhǔn)類,它繼承自URLConnection,可用于向指定網(wǎng)站發(fā)送GET請(qǐng)求、POST請(qǐng)求。需要注意的是HttpURLConnection是一個(gè)抽象類,所以不能直接創(chuàng)建它的實(shí)例,所以一般我們通過(guò)URL類的openConnection()方法獲得它的實(shí)例。
  • 其用法可歸納如下:
    • 獲取HttpURLConnection的實(shí)例
    URL url = new URL("http://www.baidu.com");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    
    • 設(shè)置HTTP請(qǐng)求所使用的方法:GET和POST。GET標(biāo)識(shí)希望從服務(wù)器得到數(shù)據(jù),而POST表示希望提交數(shù)據(jù)到服務(wù)器
    connection.setRequestMethod("GET");
    connection.setRequestMethod("POST");
    
    • 設(shè)置連接超時(shí),讀取超時(shí)的毫秒數(shù)等
    connection.setConnectTimeout(8000);
    connection.setReadTimeout(8000);
    
    • 獲取服務(wù)器返回的輸入流(若HTTP請(qǐng)求為GET)
    InputStream in = connection.getInputStream();
    
    • 向服務(wù)器提交數(shù)據(jù)(若HTTP請(qǐng)求為POST)
    DataOutputStream out = new DataOutputStream(connection.getOutputStresm());
    out.writeBytes("username=admin&password=123456");
    
    • 將HTTP連接關(guān)閉
    connection.disconnect();
    
  • 注意事項(xiàng)
    • HttpURLConnection的connect()函數(shù),實(shí)際上只是建立了一個(gè)與服務(wù)器的tcp連接,并沒(méi)有實(shí)際發(fā)送http請(qǐng)求。無(wú)論是post還是get,http請(qǐng)求實(shí)際上直到HttpURLConnection的getInputStream()這個(gè)函數(shù)里面才正式發(fā)送出去。
    • 在用POST方式發(fā)送URL請(qǐng)求時(shí),URL請(qǐng)求參數(shù)的設(shè)定順序是重中之重,對(duì)connection對(duì)象的一切配置(那一堆set函數(shù)),都必須要在connect()函數(shù)執(zhí)行之前完成。而對(duì)outputStream的寫(xiě)操作,又必須要在inputStream的讀操作之前。 這些順序?qū)嶋H上是由http請(qǐng)求的格式?jīng)Q定的。 如果inputStream讀操作在outputStream的寫(xiě)操作之前,會(huì)拋出異常: java.net.ProtocolException: Cannot write output after reading input……
    • http請(qǐng)求實(shí)際上由兩部分組成,一個(gè)是http頭,所有關(guān)于此次http請(qǐng)求的配置都在http頭里面定義,一個(gè)是正文content。 connect()函數(shù)會(huì)根據(jù)HttpURLConnection對(duì)象的配置值生成http頭部信息,因此在調(diào)用connect函數(shù)之前,就必須把所有的配置準(zhǔn)備好。
    • 在http頭后面緊跟著的是http請(qǐng)求的正文,正文的內(nèi)容是通過(guò)outputStream流寫(xiě)入的, 實(shí)際上outputStream不是一個(gè)網(wǎng)絡(luò)流,充其量是個(gè)字符串流,往里面寫(xiě)入的東西不會(huì)立即發(fā)送到網(wǎng)絡(luò),而是存在于內(nèi)存緩沖區(qū)中,待outputStream流關(guān)閉時(shí),根據(jù)輸入的內(nèi)容生成http正文。 至此,http請(qǐng)求的東西已經(jīng)全部準(zhǔn)備就緒。在getInputStream()函數(shù)調(diào)用的時(shí)候,就會(huì)把準(zhǔn)備好的http請(qǐng)求 正式發(fā)送到服務(wù)器了,然后返回一個(gè)輸入流,用于讀取服務(wù)器對(duì)于此次http請(qǐng)求的返回信息。由于http請(qǐng)求在getInputStream的時(shí)候已經(jīng)發(fā)送出去了(包括http頭和正文),因此在getInputStream()函數(shù) 之后對(duì)connection對(duì)象進(jìn)行設(shè)置(對(duì)http頭的信息進(jìn)行修改)或者寫(xiě)入outputStream(對(duì)正文進(jìn)行修改) 都是沒(méi)有意義的了,執(zhí)行這些操作會(huì)導(dǎo)致異常的發(fā)生。

HttpClient

  • 它是一個(gè)簡(jiǎn)單的HTTP客戶端(并不是瀏覽器),可以用于發(fā)送HTTP請(qǐng)求,接收HTTP響應(yīng)。但不會(huì)緩存服務(wù)器的響應(yīng),不能執(zhí)行HTML頁(yè)面中嵌入的Javascript代碼;也不會(huì)對(duì)頁(yè)面內(nèi)容進(jìn)行任何解析、處理。
  • 簡(jiǎn)單來(lái)說(shuō),HttpClient就是一個(gè)增強(qiáng)版的HttpURLConnection,HttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection沒(méi)有提供的有些功能,HttpClient也提供了,但它只是關(guān)注于如何發(fā)送請(qǐng)求、接收響應(yīng),以及管理HTTP連接。
  • 其用法可總結(jié)如下:
    • 創(chuàng)建一個(gè)DefaultHttpClient實(shí)例
    HttpClient httpClient = new DefaultHttpClient();
    
    • 如果是進(jìn)行GET請(qǐng)求
    HttpGet httpGet = new HttpGet("http://www.baidu.com");
    HttpResponse httpResponse = httpClient.execute(httpGet);
    
    • 如果是進(jìn)行POST請(qǐng)求
      創(chuàng)建一個(gè)HttpPost對(duì)象
    HttpPost httpPost = new HttpPost("http://www.baidu.com");
    
    通過(guò)一個(gè)NameValuePair集合來(lái)存放待提交的參數(shù),并將這個(gè)參數(shù)集合傳入到一個(gè)UrlEncodedFormEntity中,然后調(diào)用HttpPost的setEntity()方法將構(gòu)建好的UrlEncodedEntity傳入,之后調(diào)用httpClient的execute()方法,并將httpPost對(duì)象傳入,并接受服務(wù)器返回的httpResponse對(duì)象。
    List<NameValuePair> params = new ArrayList<NameValuePair>();
    params.add(new BasicNameValuePair("username", "admin"));
    params.add(new BasicNameValuePair("password", "123456"));
    UrlEncodedFormEntity entity = new   UrlEncodedFormEntity(params, "utf-8");
    httpPost.setEntity(entity);
    HttpResponse httpResponse = httpClient.execute(httpPost);
    
    • 取出服務(wù)器返回的狀態(tài)碼,如果等于200就說(shuō)明請(qǐng)求和響應(yīng)都成功了
    if(httpResponse.getStatusLine().getStatusCode() == 200) {
      HttpEntity entity = httpResponse.getEntity();
      String response = EntityUtils.toString(entity, "utf-8");
    }
    

HttpURLConnection和HttpClient的選擇

  • 在Android 2.2版本之前,HttpClient擁有較少的bug,因此使用它是最好的選擇。
  • 而在Android 2.3版本及以后,HttpURLConnection則是最佳的選擇。它的API簡(jiǎn)單,體積較小,因而非常適用于Android項(xiàng)目。壓縮和緩存機(jī)制可以有效地減少網(wǎng)絡(luò)訪問(wèn)的流量,在提升速度和省電方面也起到了較大的作用。對(duì)于新的應(yīng)用程序應(yīng)該更加偏向于使用HttpURLConnection,因?yàn)樵谝院蟮墓ぷ鳟?dāng)中我們也會(huì)將更多的時(shí)間放在優(yōu)化HttpURLConnection上面。
  • 在前面的Volley解析中我們也提到,它遵從上面的規(guī)律使用的。

HttpStack

  • 在對(duì)HttpURLConnection和HttpClient有了初步的了解后,我們就可以進(jìn)行HttpStack的解析了。
  • HttpStack是一個(gè)接口,它有一個(gè)抽象方法performRequest,也就是進(jìn)行請(qǐng)求的方法。
HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;
  • 該接口分別有HurlStack和HttpClientStack兩實(shí)現(xiàn)類,也就是分別對(duì)應(yīng)HttpURLConnection和HttpClient,兩種不同的HTTP請(qǐng)求方式,接下來(lái)我們分別介紹這兩個(gè)不同的類。

HurlStack

  • 首先看到HurlStack的成員變量
    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private final HurlStack.UrlRewriter mUrlRewriter;
    private final SSLSocketFactory mSslSocketFactory;

HurlStack的成員變量只有三個(gè),一個(gè)是表示“Content-Type”字符串的靜態(tài)常量,一個(gè)URL轉(zhuǎn)換接口,還有一個(gè)是生成SSLSocket的工廠類。
至于SSLSocket,它是擴(kuò)展Socket并提供使用SSL或TLS協(xié)議的安全套接字。這種套接字是正常的流套接字,但是它們?cè)诨A(chǔ)網(wǎng)絡(luò)傳輸協(xié)議(如TCP)上添加了安全保護(hù)層。

  • 既然HurlStack實(shí)現(xiàn)了HttpStack接口的,那么performRequest方法的實(shí)現(xiàn)肯定是重中之重。
    這里先直接貼出performRequest的實(shí)現(xiàn),再對(duì)其進(jìn)行分析。
 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if(this.mUrlRewriter != null) {
            String rewritten = this.mUrlRewriter.rewriteUrl(url);
            if(rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }

            url = rewritten;
        }

        URL parsedUrl = new URL(url);
        HttpURLConnection connection = this.openConnection(parsedUrl, request);
        Iterator var8 = map.keySet().iterator();

        while(var8.hasNext()) {
            String headerName = (String)var8.next();
            connection.addRequestProperty(headerName, (String)map.get(headerName));
        }

        setConnectionParametersForRequest(connection, request);
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();
        if(responseCode == -1) {
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        } else {
            StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage());
            BasicHttpResponse response = new BasicHttpResponse(responseStatus);
            response.setEntity(entityFromConnection(connection));
            Iterator var12 = connection.getHeaderFields().entrySet().iterator();

            while(var12.hasNext()) {
                Entry<String, List<String>> header = (Entry)var12.next();
                if(header.getKey() != null) {
                    Header h = new BasicHeader((String)header.getKey(), (String)((List)header.getValue()).get(0));
                    response.addHeader(h);
                }
            }

            return response;
        }
    }

首先這個(gè)方法將請(qǐng)求的參數(shù)信息全部整合放在一個(gè)map里,然后通過(guò)判斷mUrlRewriter是否為空來(lái)決定是否轉(zhuǎn)換URL,我們可以通過(guò)實(shí)現(xiàn)mUrlRewriter類去擴(kuò)展Volley從而實(shí)現(xiàn)過(guò)濾非法字符之類的操作。
接著,這個(gè)方法通過(guò)openConnection方法打開(kāi)了一個(gè)TCP連接,并配置連接的相關(guān)信息,如超時(shí)時(shí)間,是否采用緩存等,并且如果是采用HTTP協(xié)議,則會(huì)為其設(shè)置默認(rèn)的SSLSocketFactory(當(dāng)然是在SSLSocketFactory不為空的情況下),需要注意的是,現(xiàn)在只是打開(kāi)了一個(gè)TCP連接,并沒(méi)有實(shí)際發(fā)送http請(qǐng)求。
openConnection:

    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
        HttpURLConnection connection = this.createConnection(url);
        int timeoutMs = request.getTimeoutMs();
        connection.setConnectTimeout(timeoutMs);
        connection.setReadTimeout(timeoutMs);
        connection.setUseCaches(false);
        connection.setDoInput(true);
        if("https".equals(url.getProtocol()) && this.mSslSocketFactory != null) {
            ((HttpsURLConnection)connection).setSSLSocketFactory(this.mSslSocketFactory);
        }

        return connection;
    }

再接下來(lái),就是配置http的請(qǐng)求頭信息了,通過(guò)遍歷存放著所有參數(shù)信息的map,再調(diào)用addRequestProperty將信息加入http的請(qǐng)求頭。
而setConnectionParametersForRequest方法的作用則是根據(jù)request的請(qǐng)求類型來(lái)設(shè)置connection的請(qǐng)求類型,如果是POST或者PUT的話還需要將發(fā)送的內(nèi)容輸出到并且設(shè)置相應(yīng)的content-type。
setConnectionParametersForRequest:

static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
        switch(request.getMethod()) {
        case -1:
            byte[] postBody = request.getPostBody();
            if(postBody != null) {
                connection.setDoOutput(true);
                connection.setRequestMethod("POST");
                connection.addRequestProperty("Content-Type", request.getPostBodyContentType());
                DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                out.write(postBody);
                out.close();
            }
            break;
        case 0:
            connection.setRequestMethod("GET");
            break;
          .....

再接著就是設(shè)置協(xié)議的類型以及版本,然后構(gòu)造一個(gè)BasicHttpResponse封裝本次的響應(yīng)的信息。其中包含了響應(yīng)的頭部信息和內(nèi)容,返回給調(diào)用該方法的上級(jí)進(jìn)行進(jìn)一步的解析。(其真正解析成我們需要的數(shù)據(jù)是在Request的子類中,如StringRequest類中)。

HttpClientStack

  • HttpClientStack類與HurlStack類的作用類似,結(jié)構(gòu)也差不多,只不過(guò)HttpClientStack的底層是使用HttpClient實(shí)現(xiàn)的。
  • 看到HttpClientStack的成員變量:
    protected final HttpClient mClient;
    private static final String HEADER_CONTENT_TYPE = "Content-Type";

很簡(jiǎn)單,只有一個(gè)HttpClient對(duì)象和一個(gè)字符串常量。其中HttpClient對(duì)象在初始化時(shí)傳入,也就是在VOLLEY類的newRequestQueue方法中通過(guò)AndroidHttpClient.newInstance(userAgent)方法取得。而AndroidHttpClient則是實(shí)現(xiàn)了HttpClient接口的類,主要是幫我們做了一些缺省的配置,如連接超時(shí)和socket超時(shí)都是設(shè)置為20秒,連接管理器設(shè)置為T(mén)hreadSafeClientConnManager。

  • 在看到它的performRequest()方法:
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
        addHeaders(httpRequest, additionalHeaders);
        addHeaders(httpRequest, request.getHeaders());
        this.onPrepareRequest(httpRequest);
        HttpParams httpParams = httpRequest.getParams();
        int timeoutMs = request.getTimeoutMs();
        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
        return this.mClient.execute(httpRequest);
    }

首先通過(guò)createHttpRequest()方法,根據(jù)請(qǐng)求的類型產(chǎn)生一個(gè)網(wǎng)絡(luò)請(qǐng)求,這里跟HurlStack里的處理類似。接著配置頭部信息,加入請(qǐng)求的參數(shù),設(shè)置超時(shí)時(shí)長(zhǎng),最后再通過(guò)mClient執(zhí)行請(qǐng)求。

  • 本質(zhì)上HttpClientStack和HurlStack都是進(jìn)行同樣的封裝行為,只不過(guò)HttpClientStack的底層是采用HttpClient實(shí)現(xiàn)的。而HttpClientStack和HurlStack最大的不同應(yīng)該是HttpClientStack還擁有一個(gè)onPrepareRequest方法,我們可以繼承HttpClientStack類重寫(xiě)該方法做一些執(zhí)行前的工作,類似相當(dāng)于AsyncTask的onPreExecute()。

總結(jié)

  • 到這里,和HttpStack有關(guān)的就已經(jīng)解析完了,其實(shí)還是挺容易看懂的,但這是在對(duì)HttpURLConnection和HttpClient有一定了解的前提下。
  • 下一篇計(jì)劃分析VOLLEY的緩存機(jī)制。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Volley介紹 Volley 是 Google 在 2013 Google I/O 大會(huì)上發(fā)布推出的 Andro...
    cgzysan閱讀 717評(píng)論 0 4
  • 我們?cè)賮?lái)看看volley是怎么工作的。首先還是要帶著重點(diǎn)去看源碼,我們要關(guān)注的地方除了最核心的工作流程之外,還有一...
    反復(fù)橫跳的龍?zhí)?/span>閱讀 572評(píng)論 0 1
  • 一、WebView view=(WebView) findViewById(R.id.webView1); vie...
    在你左右2018閱讀 605評(píng)論 0 0
  • 注:本文轉(zhuǎn)自http://codekk.com/open-source-project-analysis/deta...
    Ten_Minutes閱讀 1,401評(píng)論 1 16
  • 國(guó)慶做為一個(gè)有比較長(zhǎng)假期的節(jié)日,大部分人自然是會(huì)有大動(dòng)作,那就是出行。除了加班的和沒(méi)有計(jì)劃的應(yīng)該都會(huì)在路上。 你今...
    一只某某7閱讀 126評(píng)論 2 0

友情鏈接更多精彩內(nèi)容