最新发布的android 6.0将Apache的HttpClient删除掉了,为了适应这一变化,有下面两种解决方法
编译时加入HttpClient的依赖以继续使用HttpClient
可参考我翻译的这篇文章android 6.0开发时的行为改变
抛弃HttpClient,全面使用HttpURLConnection
在决定使用哪种Http API之前,充分了解它们之间的差别很有必要。
我使用了理论与实践相结合的方式来对比了一下两者的区别。
理论
国内外关于两者对比的文章很多,但是我觉得最好的还是android官方出的一篇文章 - Android’s HTTP Clients,作者是Dalvik团队的Jesse Wilson,发表于2011年9月29日。
在这里,我简要地把这篇文章的关键点列一下。
以下内容主要是我的翻译。
两者主要都支持HTTPS,数据流的上传与下载,可设置的超时,IPv6和连接池。
HttpClient
DefaultHttpClient
和AndroidHttpClient
都是可扩展的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只用来维护一些比较老的代码。具体的差别见上面的总结。