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

来源:互联网 发布:评论盖楼的数据库设计 编辑:程序博客网 时间:2024/06/06 11:42

最新发布的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只用来维护一些比较老的代码。具体的差别见上面的总结。

0 0