Android HttpURLConnection詳解

最近有一個(gè)項(xiàng)目需要重構(gòu)網(wǎng)絡(luò)部分代碼,由于之前的網(wǎng)絡(luò)部分都已經(jīng)封裝好,直接調(diào)用接口就行,重構(gòu)的時(shí)候才發(fā)現(xiàn),好多東西已經(jīng)忘了,現(xiàn)在給大家總結(jié)出來(lái),有需要的朋友可以拿走,文章的最后會(huì)有demo工程。

HttpURLConnection

早些時(shí)候其實(shí)我們都習(xí)慣性使用HttpClient,但是后來(lái)Android6.0之后不再支持HttpClient,需要添加Apache的jar才行,所以,就有很多開發(fā)者放棄使用HttpClient了,HttpURLConnection畢竟是標(biāo)準(zhǔn)Java接口(java.net) ,適配性還是很強(qiáng)的。

準(zhǔn)備工作

在開始使用之前,我們需要知道網(wǎng)絡(luò)請(qǐng)求都需要一些什么參數(shù)。這里羅列一些常用的參數(shù):

  • url 請(qǐng)求的地址,這個(gè)不用說(shuō)了,肯定是必須的
  • 請(qǐng)求方式:GET POST還有DELETE,最常用的還是GET和POST
  • 加密規(guī)則,這個(gè)當(dāng)然是根據(jù)需要可有可無(wú)的
  • header 請(qǐng)求頭
  • 參數(shù) 需要傳遞的參數(shù)
  • 文件 你可能需要通過(guò)網(wǎng)絡(luò)上傳一個(gè)文件

知道了這些,我們可以自己定義一個(gè)接口:

public interface IRequest {
    public String getBaseUrl();
    public String getMethod();
    public IEncrypt getEncrypt();
    public HashMap<String, Object> getParam();
    public Map<String, FilePair> getFilePair();
    public Map<String, String> getHeaders();
}

其中FilePair是:

  public  class FilePair{
        String mFileName;
        byte[] mBinaryData;
        public FilePair(String fileName, byte[] data) {
            this.mFileName = fileName;
            this.mBinaryData = data;
        }
    }

構(gòu)建這個(gè)類,是為了上傳文件的時(shí)候使用方便。
有了這個(gè)接口,我們進(jìn)行網(wǎng)絡(luò)請(qǐng)求只需要傳遞這個(gè)接口即可,如果有新的參數(shù),只需要增加接口中的方法即可,不需要改變網(wǎng)絡(luò)核心的代碼。

GET請(qǐng)求

get是用于信息獲取的,就是說(shuō),它僅僅是獲取資源信息,就像數(shù)據(jù)庫(kù)查詢一樣,不會(huì)修改,增加數(shù)據(jù),不會(huì)影響資源的狀態(tài)。
他的請(qǐng)求方式是將參數(shù)拼接在url中的,比如你請(qǐng)求的地址是http://xxx,參數(shù)是name = aa,那么拼接后應(yīng)該是http://xxx?name=aa
所以我們可以這樣處理:

public static String get(IRequest request) {
        InputStream inputStream = null;
        HttpURLConnection httpURLConnection = null;
        try {
            URL url = new URL(buildGetUrl(request.getBaseUrl(), request.getParam(), request.getEncrypt()));
            openUrlConnection(url,httpURLConnection);
            normalSetting(httpURLConnection, Method.GET, request.getHeaders());
            if (httpURLConnection == null) {
                return null;
            }
            int responseCode = httpURLConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                inputStream = httpURLConnection.getInputStream();
                String contentEncoding = httpURLConnection.getContentEncoding();
                InputStream stream = null;
                try {
                    stream = wrapStream(contentEncoding, inputStream);
                    String data = convertStreamToString(stream);
                    return data;
                } catch (IOException e) {
                    return "";
                } finally {
                    closeQuietly(stream);
                }

            }
            return null;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

首先需要根據(jù)參數(shù)拼接url:

private static String buildGetUrl(String urlPath, Map<String, Object> params, IEncrypt encrypt) {
        if (TextUtils.isEmpty(urlPath) || params == null || params.size() == 0) {
            return urlPath;
        }
        if (!urlPath.endsWith("?")) {
            urlPath += "?";
        }

        String paramsStr = buildGetParams(params);
        if (encrypt != null) {
            paramsStr = encrypt.encrypt(urlPath, params);

        }

        StringBuilder sbUrl = new StringBuilder(urlPath);
        sbUrl.append(paramsStr);
        return sbUrl.toString();
    }

    private static String buildGetParams(Map<String, Object> params) {
        StringBuilder sb = new StringBuilder();
        Set<String> keys = params.keySet();
        for (String key : keys) {
            if (params.get(key) == null) {
                continue;
            }
            sb = sb.append(key + "=" + URLEncoder.encode(params.get(key).toString()) + "&");
        }

        String paramsStr = sb.substring(0, sb.length() - 1).toString();
        return paramsStr;
    }

這里可以看出可以根據(jù)encrypt進(jìn)行加密,encrypt是實(shí)現(xiàn)的加密和解密接口:

public interface IEncrypt {
    public String encrypt(String src);
    public String dencrypt(String src);
}

加密之后,通過(guò)HttpURLConnection進(jìn)行請(qǐng)求即可。

如果不需要加密,可以將這個(gè)參數(shù)設(shè)置為空,或者直接實(shí)現(xiàn),返回原字符串即可。

httpURLConnection.getResponseCode()是返回的響應(yīng)碼,當(dāng)為200時(shí)是標(biāo)志請(qǐng)求成功了,這里需要注意的是如果返回301,或者是302,是由于鏈接重定向的問(wèn)題造成的,我們可以通過(guò)String location =httpURLConnection.getHeaderField("Location");獲取重定向的網(wǎng)址進(jìn)行重新請(qǐng)求。其中有個(gè)normalSetting,這個(gè)我們放在后面說(shuō)明。

POST

POST表示可能修改變服務(wù)器上的資源的請(qǐng)求,比如我們發(fā)一個(gè)帖子到服務(wù)器,這時(shí)候就用到了post請(qǐng)求,他會(huì)改變服務(wù)器中的存儲(chǔ)資源。
POST 提交的數(shù)據(jù)必須放在消息主體(entity-body)中,但協(xié)議并沒(méi)有規(guī)定數(shù)據(jù)必須使用什么編碼方式。實(shí)際上,開發(fā)者完全可以自己決定消息主體的格式,只要最后發(fā)送的 HTTP 請(qǐng)求滿足上面的格式就可以。 所以我們必須告訴服務(wù)端你是用的什么編碼方式。服務(wù)端通常是根據(jù)請(qǐng)求頭(headers)中的 Content-Type 字段來(lái)獲知請(qǐng)求中的消息主體是用何種方式編碼,再對(duì)主體進(jìn)行解析。

application/x-www-form-urlencoded

這應(yīng)該是最常見的 POST 提交數(shù)據(jù)的方式了。瀏覽器的原生 form 表單,如果不設(shè)置 enctype 屬性,那么最終就會(huì)以 application/x-www-form-urlencoded 方式提交數(shù)據(jù)。請(qǐng)求類似于下面這樣(無(wú)關(guān)的請(qǐng)求頭在本文中都省略掉了):


POST http://www.example.com HTTP/1.1 
Content-Type: application/x-www-form-urlencoded;charset=utf-8 
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3 

我們需要做的是
Content-Type 被指定為 application/x-www-form-urlencoded
其次,提交的數(shù)據(jù)按照 key1=val1&key2=val2 的方式進(jìn)行編碼,key 和 val 都進(jìn)行了 URL 轉(zhuǎn)碼。代碼如下:

  httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                Uri.Builder builder = new Uri.Builder();
                builder.appendQueryParameter("content", request.getMessage());
                String query = builder.build().getEncodedQuery();
                outputStream = new DataOutputStream(httpURLConnection.getOutputStream());
                outputStream.write(query.getBytes());

multipart/form-data

這又是一個(gè)常見的 POST 數(shù)據(jù)提交的方式。我們使用表單上傳文件時(shí),必須讓 form 的 enctyped 等于這個(gè)值。直接來(lái)看一個(gè)請(qǐng)求示例:

POST http://www.example.com HTTP/1.1 
Content-Type:multipart/form-data; boundary=----xxxxx 

------xxxxx 
Content-Disposition: form-data; name="text" 

title 
------xxxxx 
Content-Disposition: form-data; name="file"; filename="chrome.png" 
Content-Type: image/png 

PNG ... content of chrome.png ... 
------xxxxx--

首先生成了一個(gè) boundary 用于分割不同的字段,為了避免與正文內(nèi)容重復(fù),boundary 很長(zhǎng)很復(fù)雜。然后 Content-Type 里指明了數(shù)據(jù)是以 mutipart/form-data 來(lái)編碼,本次請(qǐng)求的 boundary 是什么內(nèi)容。消息主體里按照字段個(gè)數(shù)又分為多個(gè)結(jié)構(gòu)類似的部分,每部分都是以 --boundary 開始,緊接著內(nèi)容描述信息,然后是回車,最后是字段具體內(nèi)容(文本或二進(jìn)制)。如果傳輸?shù)氖俏募€要包含文件名和文件類型信息。消息主體最后以 --boundary-- 標(biāo)示結(jié)束
看下代碼:

 httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
                outputStream = httpURLConnection.getOutputStream();
                addBodyParams(request.getParam(),request.getFilePair(), outputStream, boundary);

其中寫入數(shù)據(jù)的方法較為繁瑣:

private static void addBodyParams(HashMap<String, Object> map, Map<String, FilePair> filePair, OutputStream outputStream, String boundary) throws IOException {
        boolean didWriteData = false;
        StringBuilder stringBuilder = new StringBuilder();
        Map<String, Object> bodyPair =map;
        Set<String> keys = bodyPair.keySet();
        for (String key : keys) {
            if (bodyPair.get(key) != null) {
                addFormField(stringBuilder, key, bodyPair.get(key).toString(), boundary);
            }
        }

        if (stringBuilder.length() > 0) {
            didWriteData = true;
            outputStream = new DataOutputStream(outputStream);
            outputStream.write(stringBuilder.toString().getBytes());
        }

        // upload files like POST files to server
        if (filePair != null && filePair.size() > 0) {
            Set<String> fileKeys = filePair.keySet();
            for (String key : fileKeys) {
                FilePair pair = filePair.get(key);
                byte[] data = pair.mBinaryData;
                if (data == null || data.length < 1) {
                    continue;
                } else {
                    didWriteData = true;
                    addFilePart(pair.mFileName, data, boundary, outputStream);
                }
            }
        }

        if (didWriteData) {
            finishWrite(outputStream, boundary);
        }
    }
    private static void addFormField(StringBuilder writer, final String name, final String value, String boundary) {
        writer.append("--").append(boundary).append(END)
                .append("Content-Disposition: form-data; name=\"").append(name)
                .append("\"").append(END)
                .append("Content-Type: text/plain; charset=").append("UTF-8")
                .append(END).append(END).append(value).append(END);
    }


    private static void addFilePart(final String fieldName, byte[] data, String boundary, OutputStream outputStream)
            throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("--").append(boundary).append(END)
                .append("Content-Disposition: form-data; name=\"")
                .append("pic").append("\"; filename=\"").append(fieldName)
                .append("\"").append(END).append("Content-Type: ")
                .append("application/octet-stream").append(END)
                .append("Content-Transfer-Encoding: binary").append(END)
                .append(END);
        outputStream.write(stringBuilder.toString().getBytes());
        outputStream.write(data);
        outputStream.write(END.getBytes());
    }

其它

除了上面提到過(guò)的兩種方式,還有application/json 以及text/xml ,這兩種在移動(dòng)端開發(fā)很少使用,不再過(guò)多介紹。

post代碼

 public static String post(IRequest request) {
        String boundary = UUID.randomUUID().toString();
        HttpURLConnection httpURLConnection = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        URL url = null;
        try {
            url = new URL(request.getBaseUrl());
            openUrlConnection(url,httpURLConnection);
            normalSetting(httpURLConnection,Method.POST,request.getHeaders());

            if (request.getParam() != null && request.getParam().size() > 0) {
                httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
                outputStream = httpURLConnection.getOutputStream();
                addBodyParams(request.getParam(),request.getFilePair(), outputStream, boundary);
            } else {

                httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                Uri.Builder builder = new Uri.Builder();
                builder.appendQueryParameter("content", request.getMessage());
                String query = builder.build().getEncodedQuery();
                outputStream = new DataOutputStream(httpURLConnection.getOutputStream());
                outputStream.write(query.getBytes());
            }
            outputStream.flush();
            int responseCode = httpURLConnection.getResponseCode();

            if (responseCode == HttpURLConnection.HTTP_OK) {
                inputStream = httpURLConnection.getInputStream();
                String contentEncoding = httpURLConnection.getContentEncoding();
                InputStream stream = wrapStream(contentEncoding, inputStream);
                String data = convertStreamToString(stream);
               return data;

            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

通用配置介紹

 private static void normalSetting(HttpURLConnection urlConnection, Method method, Map<String, String> mHeaders) throws ProtocolException {

        urlConnection.setConnectTimeout(connectionTimeOut);
        urlConnection.setReadTimeout(readSocketTimeOut);
        urlConnection.setRequestMethod(method.toString());
        if (method == Method.GET) {
            urlConnection.setRequestProperty("Accept-Encoding", "gzip");
            if (mHeaders != null && mHeaders.size() > 0) {
                Set<String> stringKeys = mHeaders.keySet();
                for (String key : stringKeys) {
                    urlConnection.setRequestProperty(key, mHeaders.get(key));
                }
            }
        } else if (method == Method.POST) {
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
        }
    }

其中

  • setConnectTimeout:設(shè)置連接主機(jī)超時(shí)(單位:毫秒)
  • setReadTimeout:設(shè)置從主機(jī)讀取數(shù)據(jù)超時(shí)(單位:毫秒)
  • Accept-Encoding HTTP Header中Accept-Encoding 是瀏覽器發(fā)給服務(wù)器,聲明瀏覽器支持的編碼類型
  • setDoOutput(false);以后就可以使用 httpURLConnection.getOutputStream().write()
  • setDoInput(true);以后就可以使用 httpURLConnection.getInputStream().read();

參考demo

這個(gè)demo是我根據(jù)自己項(xiàng)目中用到的進(jìn)行整理的,可能有些情況考慮的不是很全面,但是基本思路就是這個(gè)樣子,用到的同學(xué)可以參考:
DEMO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,868評(píng)論 18 139
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李頭閱讀 15,145評(píng)論 4 39
  • HTTP網(wǎng)絡(luò)請(qǐng)求 對(duì)于android開發(fā)來(lái)說(shuō),http是網(wǎng)絡(luò)開發(fā)中最為重要、使用頻率最高的手段。 HTTP請(qǐng)求原理...
    幻滅一只狼閱讀 7,212評(píng)論 0 11
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,024評(píng)論 25 708
  • 活在當(dāng)下 管道里水聲嗚咽 小女兒在熟睡中呼吸均勻 敲出的字與我四目相對(duì) 活在當(dāng)下 風(fēng)箏在洱海的月光里墜落 陽(yáng)光被風(fēng)...
    夢(mèng)想家佳閱讀 249評(píng)論 1 1