HttpClient与HttpURLConnection的区别(理论与实践结合)

本文对比了Android中HttpClient与HttpURLConnection的区别,分析了各自的适用场景,并提供了代码示例。对于新开发的应用,推荐使用HttpURLConnection。

最新发布的android 6.0将Apache的HttpClient删除掉了,为了适应这一变化,有下面两种解决方法

  1. 编译时加入HttpClient的依赖以继续使用HttpClient

    可参考我翻译的这篇文章android 6.0开发时的行为改变

  2. 抛弃HttpClient,全面使用HttpURLConnection

在决定使用哪种Http API之前,充分了解它们之间的差别很有必要。
我使用了理论与实践相结合的方式来对比了一下两者的区别。

理论

国内外关于两者对比的文章很多,但是我觉得最好的还是android官方出的一篇文章 - Android’s HTTP Clients,作者是Dalvik团队的Jesse Wilson,发表于2011年9月29日。

在这里,我简要地把这篇文章的关键点列一下。

以下内容主要是我的翻译。

两者主要都支持HTTPS,数据流的上传与下载,可设置的超时,IPv6和连接池。

HttpClient

DefaultHttpClientAndroidHttpClient都是可扩展的HTTP客户端,适合web浏览器使用。它们的实现稳定并且bug较少。

但是大量的API使得android团队在不打破兼容性的前提下来改进它。所以android团队已经不再维护了。

HttpURLConnection

HttpURLConnection是通用的、轻量的HTTP客户端,它适合大部分应用程序。这个类的起点很低,但是它的专注的API使得可以比较容易地稳步改进它。

在2.2版本之前,HttpURLConnection有一些基础性的bug。尤其是当在一个可读的InputStream上调用close()方法时,会破坏连接池。通过禁用连接池可以解决这个问题:

private void disableConnectionReuseIfNecessary() {
    // HTTP connection reuse which was buggy pre-froyo
    if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
        System.setProperty("http.keepAlive", "false");
    }
}

从2.3版本开始,android团队加入了透明的响应压缩。HttpURLConnection会自动地将下面的Header加入到发出去的请求中:

Accept-Encoding: gzip

接下来的内容是一些具体的细节,这里就不翻译了,有兴趣的可以查看原文。

哪个更好

HttpClient在<=2.2的版本里bug较少,对这些版本它是最好的选择。

对于>=2.3的版本,HttpURLConnection无疑是最好的选择。它的API简单并且小巧使得它非常适合android。透明压缩和响应缓存降低了网络流量的使用,提升了速度并且节电。新应用应该使用HttpURLConnection;android团队也会全力开发改进这个API。

实践

我分别使用HttpClient和HttpClientConnection实现了同样的功能,通过代码可以理解上面说的为什么HttpClientConnection简单小巧。

代码部分

下面附上我的代码

HttpClient版本

    private String getSpecifiedPageViaHttpClient(String url) {
        HttpClient client = null;
        InputStream inputStream = null;
        try {
            client = new DefaultHttpClient();

            // 设置参数
            HttpParams params = client.getParams();
            params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 3000);
            params.setParameter(CoreConnectionPNames.SO_TIMEOUT, 2000);

            // 构建请求
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader("Accept-Encoding", "gzip");   // 客户端可以处理gzip格式的数据

            // 发送请求并获取响应
            HttpResponse httpResponse = client.execute(httpGet);
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = httpResponse.getEntity();
                if (entity != null) {
                    // 是否经过gzip压缩
                    Header header = entity.getContentEncoding();
                    if (header != null && header.getValue().equalsIgnoreCase("gzip")) {
                        inputStream = new GZIPInputStream(entity.getContent());
                    } else {
                        inputStream = entity.getContent();
                    }

                    return IOHelper.inputStream2String(inputStream);
                }
            } else {
                return "请求失败," + statusCode;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                    Log.d(null, "inputstream has been closed");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (client != null) {
                client.getConnectionManager().shutdown();   // 直接关闭connection以释放服务器端的连接
            }
        }

        return "未知错误";
    }

HttpURLConnection版本

    private String getSpecifiedPageViaHurl(String url) {
        HttpURLConnection urlConnection = null;
        InputStream inputStream = null;
        try {
            urlConnection = (HttpURLConnection) new URL(url).openConnection();

            urlConnection.setConnectTimeout(3000);
            urlConnection.setReadTimeout(2000);
            urlConnection.setRequestMethod("GET");

            urlConnection.connect();

            int responseCode = urlConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // HURL会自动透明地加入gzip的支持
                inputStream = urlConnection.getInputStream();
                return IOHelper.inputStream2String(inputStream);
            } else {
                return "请求失败," + responseCode;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }

        return "未知错误";
    }

上面两个方法都用到的方法IOHelper.inputStream2String()

    /**
     * 从输入流中读取数据到String对象中。<br/>
     * 注意:使用完InputStream请显式关闭。
     * 
     * @param is InputStream对象。
     * @return 输入流的数据
     */
    public static String inputStream2String(InputStream is) {
        String data = null;

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int byteRead;
        try {
            while ((ByteRead = is.read(buf, 0, buf.length)) != -1) {
                os.write(buf, 0, byteRead);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                data = os.toString("UTF-8");
            } catch (UnsupportedEncodingException e1) {
                e1.printStackTrace();
            }
            try {
                os.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }

        return data;
    }

分析部分

首先先来看HttpClient版本的代码,为了实现功能,它引入了一系列的API

  • 实际的HttpClient实现:DefaultHttpClient/AndroidHttpClient
  • 设置参数:HttpParams
  • 参数名:CoreConnectionPNames的常量
  • 请求:HttpGet/HttpPost/…,请求header也在这些类里边配置
  • 响应:HttpResponse
  • 响应码:HttpStatusLine.getStatusCode()
  • 响应码常量:HttpStatus
  • header:Header
  • 响应的数据流(InputStream):HttpEntity.getContent()
  • 关闭Connection:ClientConnectionManager.shutdown()

上面的这些还只是比较基本的使用场景,如果要发送post请求或者使用proxy,则代码会更加复杂(引入的API会更多)。另外,gzip压缩也要手动处理。正如上面所说的,HttpClient适合web浏览器,因为它面向对象应用地非常好,基本上每一块儿都专门封装成一个类,但是对于app进行http请求来说,它又显得相对复杂很多。

而HttpURLConnection则非常简洁,所有的设置调用只通过这个API就可以了,并且它会自动处理gzip(虽然手动处理的代码也不多)。

结论

对于新开发的应用,肯定首选HttpURLConnection,HttpClient只用来维护一些比较老的代码。具体的差别见上面的总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值