天天看點

Java Android HTTP實作總結

Android實作HTTP,采用Apache和JDK的兩種方式,做了一些簡單的說明和比較。

Java Android HTTP實作總結

  Http(Hypertext Transfer Protocol)超文本傳輸協定,是一個基于請求/響應模式的無狀态的協定,Http1.1版給出了持續連接配接的機制,用戶端建立連接配接之後,可以發送多次請求,當不會再發送時再關閉連接配接。

  Android使用Java,對于Http協定的基本功能有兩種實作方案:

  1.使用JDK的java.net包下的HttpURLConnection.

  2.使用Apache的HttpClient。

  關于二者的比較可以看一下:

  http://www.cnblogs.com/devinzhang/archive/2012/01/17/2325092.html

  Android SDK中內建了Apache的HttpClient子產品,也即說Android上兩種方法都能用。

  之前看一個Android開發者部落格(原文連結先空缺,需要FQ)對此的讨論,大意總結如下:

  1.HttpClient的功能比較全,更加強大;而HttpURLConnection的功能較簡單和原始,但是性能更好。

  2.在Android 2.x的版本中使用HttpURLConnection有bug,但是後來進階版本的Android已經将帶來的bug修複,并且做了一些進一步優化的工作,是以建議在進階版本的Android系統(Android 2.3之後)使用HttpURLConnection,低版本的系統仍使用HttpClient。

程式實作

  下面來讨論一下實作,首先,需要确認Manifest中有權限:

<uses-permission android:name="android.permission.INTERNET" />      

使用JDK的HttpURLConnection類

  HttpURLConnection參考:

  http://developer.android.com/reference/java/net/HttpURLConnection.html

  使用這個類的一般步驟:

  1.通過

URL.openConnection()

 方法擷取一個HttpURLConnection對象,并且将結果強制轉化為HttpURLConnection類型。

  2.準備請求(prepare the request),包括URI,headers中的各種屬性等

  (Request headers may also include metadata such as credentials, preferred content types, and session cookies.)

  3.請求體(optionally)。如果有請求體那麼setDoOutput(true)必須為true,然後把輸入放在getOutputStream()流中。

  4.讀取響應。響應的headers一般包括了一些metadata比如響應體的内容類型和長度,修改日期以及session cookies。響應體可以從 

getInputStream()

流中讀出。

  5.斷開連接配接。響應體被讀出之後,應該調用 

disconnect()

方法來斷開連接配接。

  例子代碼:

Java Android HTTP實作總結
Java Android HTTP實作總結
package com.example.helloandroidhttp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

import android.util.Log;

public class HttpUtilsJDK {
    private static final String LOG_TAG = "Http->JDK";
    private static final int CONNECT_TIME_OUT = 3000;
    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private static final String HEADER_CONTENT_LENGTH = "Content-Length";
    /**
     * Default encoding for POST or PUT parameters. See
     * {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";

    public static String getParamsEncoding() {
        return DEFAULT_PARAMS_ENCODING;
    }

    public static String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset="
                + getParamsEncoding();
    }

    public static String performGetRequest(String baseUrl) {
        String result = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(baseUrl);
            if (null != url) {

                // 擷取HttpURLConnection類型的對象
                connection = (HttpURLConnection) url.openConnection();
                // 設定連接配接的最大等待時間
                connection.setConnectTimeout(CONNECT_TIME_OUT);

                // Sets the maximum time to wait for an input stream read to
                // complete before giving up.
                connection.setReadTimeout(3000);
                // 設定為GET方法
                connection.setRequestMethod("GET");
                connection.setDoInput(true);

                if (200 == connection.getResponseCode()) {
                    InputStream inputStream = connection.getInputStream();

                    result = getResultString(inputStream, getParamsEncoding());
                }
                else {
                    Log.e(LOG_TAG,
                            "Connection failed: "
                                    + connection.getResponseCode());
                }

            }
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            connection.disconnect();
        }

        return result;
    }

    public static String performPostRequest(String baseUrl,
            Map<String, String> params) {
        String result = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(baseUrl);
            if (null != url) {
                // 擷取HttpURLConnection類型的對象
                connection = (HttpURLConnection) url.openConnection();
                // 設定響應逾時限制
                connection.setConnectTimeout(CONNECT_TIME_OUT);
                // 設定為POST方法
                connection.setRequestMethod("POST");
                connection.setDoInput(true);
                // 有請求體則setDoOutput(true)必須設定
                connection.setDoOutput(true);

                // 為了性能考慮,如果包含請求體,那麼最好調用 setFixedLengthStreamingMode(int)或者
                // setChunkedStreamingMode(int)
                // connection.setChunkedStreamingMode(0);// 參數為0時使用預設值

                byte[] data = getParamsData(params);

                connection.setRequestProperty(HEADER_CONTENT_TYPE,
                        getBodyContentType());
                if (null != data) {
                    connection.setFixedLengthStreamingMode(data.length);
                    connection.setRequestProperty(HEADER_CONTENT_LENGTH,
                            String.valueOf(data.length));
                    OutputStream outputStream = connection.getOutputStream();
                    outputStream.write(data);
                }

                // 得到傳回值
                int responseCode = connection.getResponseCode();
                if (200 == responseCode) {
                    result = getResultString(connection.getInputStream(),
                            getParamsEncoding());

                }
                else {
                    Log.e(LOG_TAG,
                            "Connection failed: "
                                    + connection.getResponseCode());
                }

            }
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            connection.disconnect();
        }

        return result;
    }

    private static byte[] getParamsData(Map<String, String> params) {
        byte[] data = null;

        try {
            if (null != params && !params.isEmpty()) {
                StringBuffer buffer = new StringBuffer();

                for (Map.Entry<String, String> entry : params.entrySet()) {

                    buffer.append(entry.getKey())
                            .append("=")
                            .append(URLEncoder.encode(entry.getValue(),
                                    getParamsEncoding())).append("&");// 請求的參數之間使用&分割。

                }
                // 最後一個&要去掉
                buffer.deleteCharAt(buffer.length() - 1);

                data = buffer.toString().getBytes(getParamsEncoding());
            }
        }
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();

        }

        return data;
    }

    private static String getResultString(InputStream inputStream, String encode) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        int len = 0;
        String result = "";
        if (inputStream != null) {
            try {
                while ((len = inputStream.read(data)) != -1) {
                    outputStream.write(data, 0, len);
                }
                result = new String(outputStream.toByteArray(), encode);

            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}      

HttpUtilsJDK

使用Apache的HttpClient

  可以檢視官方的Tutorial:

  http://hc.apache.org/httpcomponents-client-ga/tutorial/html/index.html

  Android有一個實作類AndroidHttpClient,實作了HttpClient:

  http://developer.android.com/reference/android/net/http/AndroidHttpClient.html

  包裝了一些預設的設定。

關于HTTP entity

  HTTP消息中可以包含内容實體(content entity),可以看做消息的封包,包含在請求或者響應中。

  HTTP規範規定兩種請求方法可以包含内容實體:POST和PUT。

  響應則通常是包含内容實體的。

  HttpClient會根據内容來源區分三種實體:

  1.streamed:内容來源是流,這類裡包含了從HTTP響應中獲得的實體,流式實體不可重複。

  2.self-contained:内容是從記憶體或者其他方式獲得的,即和連接配接無關,這類實體是可以重複的,多數是用來放在HTTP請求中的實體。

  3.wrapping:這類實體是從其他實體獲得的。

  對于用HttpClient建立的請求實體來說,streamed和self-contained類型的差別其實不太重要,建議把不可重複的實體看作是streamed的,可重複的看作是self-contained的。

創造實體内容

  為了發送HTTP的POST請求(當然還有PUT請求也有實體),需要把一些參數放在實體中,創造實體内容,有四個類型的類可選用:

  StringEntity, ByteArrayEntity, InputStreamEntity, FileEntity

  注意其中的InputStreamEntity是不可重複的。

  UrlEncodedFormEntity這個類是用來把輸入資料編碼成合适的内容,比如下面這段:

List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);      

  兩個鍵值對,被UrlEncodedFormEntity執行個體編碼後變為如下内容:

param1=value1&param2=value2      

  

  使用Apache的HttpClient發送HTTP請求的輔助類,例子代碼:

Java Android HTTP實作總結
Java Android HTTP實作總結
package com.example.helloandroidhttp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import android.util.Log;

public class HttpUtilsApache {

    private static final String LOG_TAG = "Http->Apache";
    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    /**
     * Default encoding for POST or PUT parameters. See
     * {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";

    /**
     * Returns which encoding should be used when converting POST or PUT
     * parameters returned by {@link #getParams()} into a raw POST or PUT body.
     *
     * <p>
     * This controls both encodings:
     * <ol>
     * <li>The string encoding used when converting parameter names and values
     * into bytes prior to URL encoding them.</li>
     * <li>The string encoding used when converting the URL encoded parameters
     * into a raw byte array.</li>
     * </ol>
     */
    public static String getParamsEncoding() {
        return DEFAULT_PARAMS_ENCODING;
    }

    public static String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset="
                + getParamsEncoding();
    }

    public static String performGetRequest(String url) {

        String result = null;
        // 生成一個請求對象
        HttpGet httpGet = new HttpGet(url);

        // 1.生成一個Http用戶端對象(帶參數的)
        HttpParams httpParameters = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParameters, 10 * 1000);// 設定請求逾時10秒
        HttpConnectionParams.setSoTimeout(httpParameters, 10 * 1000); // 設定等待資料逾時10秒
        HttpConnectionParams.setSocketBufferSize(httpParameters, 8192);

        HttpClient httpClient = new DefaultHttpClient(httpParameters); // 此時構造DefaultHttpClient時将參數傳入
        // 2.預設實作:
        // HttpClient httpClient = new DefaultHttpClient();
        httpGet.addHeader(HEADER_CONTENT_TYPE, getBodyContentType());

        // 下面使用Http用戶端發送請求,并擷取響應内容

        HttpResponse httpResponse = null;

        try {
            // 發送請求并獲得響應對象
            httpResponse = httpClient.execute(httpGet);

            final int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (200 == statusCode) {
                result = getResponseString(httpResponse);
            }
            else {
                Log.e(LOG_TAG, "Connection failed: " + statusCode);
            }

        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {

        }

        return result;
    }

    public static String performPostRequest(String baseURL, String postData) {
        String result = "";
        HttpResponse response = null;
        try {

            // URL使用基本URL即可,其中不需要加參數
            HttpPost httpPost = new HttpPost(baseURL);
            // 設定ContentType
            httpPost.addHeader(HEADER_CONTENT_TYPE, getBodyContentType());

            // 将請求體内容加入請求中
            HttpEntity requestHttpEntity = prepareHttpEntity(postData);

            if (null != requestHttpEntity) {
                httpPost.setEntity(requestHttpEntity);
            }

            // 需要用戶端對象來發送請求
            HttpClient httpClient = new DefaultHttpClient();
            // 發送請求
            response = httpClient.execute(httpPost);

            final int statusCode = response.getStatusLine().getStatusCode();
            if (200 == statusCode) {
                // 顯示響應
                result = getResponseString(response);
            }
            else {
                Log.e(LOG_TAG, "Connection failed: " + statusCode);
            }

        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {

        }

        return result;

    }

    /**
     * 直接利用String生成HttpEntity,String應該已經是key=value&key2=value2的形式
     *
     * @param postData
     * @return
     */
    private static HttpEntity prepareHttpEntity(String postData) {

        HttpEntity requestHttpEntity = null;

        try {

            if (null != postData) {
                // 去掉所有的換行
                postData = postData.replace("\n", "");
                // one way
                // requestHttpEntity = new ByteArrayEntity(
                // postData.getBytes(getParamsEncoding()));

                // another way
                requestHttpEntity = new StringEntity(postData,
                        getParamsEncoding());
                ((StringEntity) requestHttpEntity)
                        .setContentEncoding(getParamsEncoding());
                ((StringEntity) requestHttpEntity)
                        .setContentType(getBodyContentType());

            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return requestHttpEntity;
    }

    /**
     * 利用Map結構的參數生成HttpEntity,使用UrlEncodedFormEntity對參數對進行編碼
     *
     * @param params
     * @return
     */
    private static HttpEntity prepareHttpEntity1(Map<String, String> params) {
        // 需要将String裡面的key value拆分出來

        HttpEntity requestHttpEntity = null;
        try {

            if (null != params) {
                List<NameValuePair> pairList = new ArrayList<NameValuePair>(
                        params.size());
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    NameValuePair pair = new BasicNameValuePair(entry.getKey(),
                            entry.getValue());
                    pairList.add(pair);
                }
                requestHttpEntity = new UrlEncodedFormEntity(pairList,
                        getParamsEncoding());

            }

        }
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return requestHttpEntity;
    }

    /**
     * 利用Map結構的參數生成HttpEntity,使用自己的方法對參數進行編碼合成字元串
     *
     * @param params
     * @return
     */
    private static HttpEntity prepareHttpEntity2(Map<String, String> params) {
        // 需要将String裡面的key value拆分出來

        HttpEntity requestHttpEntity = null;
        byte[] body = encodeParameters(params, getParamsEncoding());
        requestHttpEntity = new ByteArrayEntity(body);

        return requestHttpEntity;
    }

    /**
     * Converts <code>params</code> into an application/x-www-form-urlencoded
     * encoded string.
     */
    private static byte[] encodeParameters(Map<String, String> params,
            String paramsEncoding) {
        StringBuilder encodedParams = new StringBuilder();
        try {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                encodedParams.append(URLEncoder.encode(entry.getKey(),
                        paramsEncoding));
                encodedParams.append('=');
                encodedParams.append(URLEncoder.encode(entry.getValue(),
                        paramsEncoding));
                encodedParams.append('&');
            }
            return encodedParams.toString().getBytes(paramsEncoding);
        }
        catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: "
                    + paramsEncoding, uee);
        }
    }

    public static String getResponseString(HttpResponse response) {
        String result = null;
        if (null == response) {
            return result;
        }

        HttpEntity httpEntity = response.getEntity();
        InputStream inputStream = null;
        try {
            inputStream = httpEntity.getContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    inputStream));
            result = "";
            String line = "";
            while (null != (line = reader.readLine())) {
                result += line;
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;

    }
}      

HttpUtilsApache

參考資料

  本部落格HTTP标簽下相關文章,比如這個:

  http://www.cnblogs.com/mengdd/p/3144599.html

  Apache HttpClient:

  http://hc.apache.org/httpcomponents-client-ga/

  http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html#d5e49

  Android之網絡程式設計 系列博文:

  http://www.cnblogs.com/devinzhang/category/349642.html

  Http Header詳解:

  http://kb.cnblogs.com/page/92320/

  Android--Apache HttpClient:

  http://www.cnblogs.com/plokmju/p/Android_apacheHttpClient.html

推薦項目

  Android網絡通信架構Volley:

  https://github.com/mengdd/android-volley

  Android Asynchronous Http Client:A Callback-Based Http Client Library for Android

  https://github.com/mengdd/android-async-http

  也即:http://loopj.com/android-async-http/

  本文項目位址(目前還是個挺簡陋的Demo,有待完善):

  https://github.com/mengdd/HelloAndroidHttpUtils

作者: 聖騎士Wind

出處: 部落格園: 聖騎士Wind

Github: https://github.com/mengdd

微信公衆号: 聖騎士Wind

Java Android HTTP實作總結