Android HttpURLConnection詳解

最近有一個項目須要重構網絡部分代碼,因爲以前的網絡部分都已經封裝好,直接調用接口就行,重構的時候才發現,好多東西已經忘了,如今給你們總結出來,有須要的朋友能夠拿走,文章的最後會有demo工程。javascript

HttpURLConnection

早些時候其實咱們都習慣性使用HttpClient,可是後來Android6.0以後再也不支持HttpClient,須要添加Apache的jar才行,因此,就有不少開發者放棄使用HttpClient了,HttpURLConnection畢竟是標準Java接口(java.net) ,適配性仍是很強的。java

準備工做

在開始使用以前,咱們須要知道網絡請求都須要一些什麼參數。這裏羅列一些經常使用的參數:git

  • url 請求的地址,這個不用說了,確定是必須的
  • 請求方式:GET POST還有DELETE,最經常使用的仍是GET和POST
  • 加密規則,這個固然是根據須要無關緊要的
  • header 請求頭
  • 參數 須要傳遞的參數
  • 文件 你可能須要經過網絡上傳一個文件

知道了這些,咱們能夠本身定義一個接口:github

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是:chrome

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

構建這個類,是爲了上傳文件的時候使用方便。
有了這個接口,咱們進行網絡請求只須要傳遞這個接口便可,若是有新的參數,只須要增長接口中的方法便可,不須要改變網絡核心的代碼。數據庫

GET請求

get是用於信息獲取的,就是說,它僅僅是獲取資源信息,就像數據庫查詢同樣,不會修改,增長數據,不會影響資源的狀態。
他的請求方式是將參數拼接在url中的,好比你請求的地址是http://xxx,參數是name = aa,那麼拼接後應該是http://xxx?name=aa
因此咱們能夠這樣處理:json

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 "";
    }複製代碼

首先須要根據參數拼接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;
    }複製代碼

這裏能夠看出能夠根據encrypt進行加密,encrypt是實現的加密和解密接口:服務器

public interface IEncrypt {
    public String encrypt(String src);
    public String dencrypt(String src);
}複製代碼

加密以後,經過HttpURLConnection進行請求便可。網絡

若是不須要加密,能夠將這個參數設置爲空,或者直接實現,返回原字符串便可。

httpURLConnection.getResponseCode()是返回的響應碼,當爲200時是標誌請求成功了,這裏須要注意的是若是返回301,或者是302,是因爲連接重定向的問題形成的,咱們能夠經過String location =httpURLConnection.getHeaderField("Location");獲取重定向的網址進行從新請求。其中有個normalSetting,這個咱們放在後面說明。

POST

POST表示可能修改變服務器上的資源的請求,好比咱們發一個帖子到服務器,這時候就用到了post請求,他會改變服務器中的存儲資源。
POST 提交的數據必須放在消息主體(entity-body)中,但協議並無規定數據必須使用什麼編碼方式。實際上,開發者徹底能夠本身決定消息主體的格式,只要最後發送的 HTTP 請求知足上面的格式就能夠。 因此咱們必須告訴服務端你是用的什麼編碼方式。服務端一般是根據請求頭(headers)中的 Content-Type 字段來獲知請求中的消息主體是用何種方式編碼,再對主體進行解析。

application/x-www-form-urlencoded

這應該是最多見的 POST 提交數據的方式了。瀏覽器的原生 form 表單,若是不設置 enctype 屬性,那麼最終就會以 application/x-www-form-urlencoded 方式提交數據。請求相似於下面這樣(無關的請求頭在本文中都省略掉了):

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
其次,提交的數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。代碼以下:

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

這又是一個常見的 POST 數據提交的方式。咱們使用表單上傳文件時,必須讓 form 的 enctyped 等於這個值。直接來看一個請求示例:

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--複製代碼

首先生成了一個 boundary 用於分割不一樣的字段,爲了不與正文內容重複,boundary 很長很複雜。而後 Content-Type 裏指明瞭數據是以 mutipart/form-data 來編碼,本次請求的 boundary 是什麼內容。消息主體裏按照字段個數又分爲多個結構相似的部分,每部分都是以 --boundary 開始,緊接着內容描述信息,而後是回車,最後是字段具體內容(文本或二進制)。若是傳輸的是文件,還要包含文件名和文件類型信息。消息主體最後以 --boundary-- 標示結束
看下代碼:

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

其中寫入數據的方法較爲繁瑣:

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());
    }複製代碼

其它

除了上面提到過的兩種方式,還有application/json 以及text/xml ,這兩種在移動端開發不多使用,再也不過多介紹。

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:設置鏈接主機超時(單位:毫秒)
  • setReadTimeout:設置從主機讀取數據超時(單位:毫秒)
  • Accept-Encoding HTTP Header中Accept-Encoding 是瀏覽器發給服務器,聲明瀏覽器支持的編碼類型
  • setDoOutput(false);之後就可使用 httpURLConnection.getOutputStream().write()
  • setDoInput(true);之後就可使用 httpURLConnection.getInputStream().read();

參考demo

這個demo是我根據本身項目中用到的進行整理的,可能有些狀況考慮的不是很全面,可是基本思路就是這個樣子,用到的同窗能夠參考:
DEMO

個人公衆號:

相關文章
相關標籤/搜索