Android HttpURLConnection详解

最近有一个项目需要重构网络部分代码,由于之前的网络部分都已经封装好,直接调用接口就行,重构的时候才发现,好多东西已经忘了,现在给大家总结出来,有需要的朋友可以拿走,文章的最后会有demo工程。

HttpURLConnection

早些时候其实我们都习惯性使用HttpClient,但是后来Android6.0之后不再支持HttpClient,需要添加Apache的jar才行,所以,就有很多开发者放弃使用HttpClient了,HttpURLConnection毕竟是标准Java接口(java.net) ,适配性还是很强的。

准备工作

在开始使用之前,我们需要知道网络请求都需要一些什么参数。这里罗列一些常用的参数:

  • url 请求的地址,这个不用说了,肯定是必须的
  • 请求方式:GET POST还有DELETE,最常用的还是GET和POST
  • 加密规则,这个当然是根据需要可有可无的
  • header 请求头
  • 参数 需要传递的参数
  • 文件 你可能需要通过网络上传一个文件

知道了这些,我们可以自己定义一个接口:

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;
        }
    }

构建这个类,是为了上传文件的时候使用方便。
有了这个接口,我们进行网络请求只需要传递这个接口即可,如果有新的参数,只需要增加接口中的方法即可,不需要改变网络核心的代码。

GET请求

get是用于信息获取的,就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。
他的请求方式是将参数拼接在url中的,比如你请求的地址是http://xxx,参数是name = aa,那么拼接后应该是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 "";
    }

首先需要根据参数拼接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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李头阅读 14,848评论 4 39
  • HTTP网络请求 对于android开发来说,http是网络开发中最为重要、使用频率最高的手段。 HTTP请求原理...
    幻灭一只狼阅读 6,729评论 0 11
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 活在当下 管道里水声呜咽 小女儿在熟睡中呼吸均匀 敲出的字与我四目相对 活在当下 风筝在洱海的月光里坠落 阳光被风...
    梦想家佳阅读 232评论 1 1