尽管Android官网推荐在2.3及后续版本中使用HttpURLConnection做为网络开发首选类,但在链接管理和线程安全方面,HttpClient仍是具备很大优点。就目前而言,HttpClient还是一个值得考虑的选择。对于HttpClient的优化,能够从如下几个方面着手:html
(1)采用单例模式(重用HttpClient实例)
对于一个通讯单元甚至是整个应用程序,Apache强烈推荐只使用一个HttpClient的实例。例如:java
private static HttpClient httpClient = null;
private static synchronized HttpClient getHttpClient() {
if(httpClient == null) {
final HttpParams httpParams = new BasicHttpParams();
httpClient = new DefaultHttpClient(httpParams);
}
return httpClient;
}android
(2)保持链接(重用链接)
对于已经和服务端创建了链接的应用来讲,再次调用HttpClient进行网络数据传输时,就没必要从新创建新链接了,而能够重用已经创建的链接。这样无疑能够减小开销,提高速度。
在这个方面,Apache已经作了“链接管理”,默认状况下,就会尽量的重用已有链接,所以,不须要客户端程序员作任何配置。只是须要注意,Apache的链接管理并不会主动释放创建的链接,须要程序员在不用的时候手动关闭链接。程序员
(3)多线程安全管理的配置
若是应用程序采用了多线程进行网络访问,则应该使用Apache封装好的线程安全管理类ThreadSafeClientConnManager来进行管理,这样可以更有效且更安全的管理多线程和链接池中的链接。
(在网上也看到有人用MultiThreadedHttpConnectionManager进行线程安全管理的,后查了下Apache的API,发现MultiThreadedHttpConnectionManager是API 2.0中的类,而ThreadSafeClientConnManager是API 4.0中的类,比前者更新,因此选择使用ThreadSafeClientConnManager。另外,还看到有PoolingClientConnectionManager这个类,是API 4.2中的类,比ThreadSafeClientConnManager更新,但Android SDK中找不到该类。因此目前仍是选择了ThreadSafeClientConnManager进行管理)
例如:apache
ClientConnectionManager manager = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
httpClient = new DefaultHttpClient(manager, httpParams);api
(4)大量传输数据时,使用“请求流/响应流”的方式
当须要传输大量数据时,不该使用字符串(strings)或者字节数组(byte arrays),由于它们会将数据缓存至内存。当数据过多,尤为在多线程状况下,很容易形成内存溢出(out of memory,OOM)。
而HttpClient可以有效处理“实体流(stream)”。这些“流”不会缓存至内存、而会直接进行数据传输。采用“请求流/响应流”的方式进行传输,能够减小内存占用,下降内存溢出的风险。
例如:数组
// Get method: getResponseBodyAsStream()
// not use getResponseBody(), or getResponseBodyAsString()
GetMethod httpGet = new GetMethod(url);
InputStream inputStream = httpGet.getResponseBodyAsStream();缓存
// Post method: getResponseBodyAsStream()
PostMethod httpPost = new PostMethod(url);
InputStream inputStream = httpPost.getResponseBodyAsStream(); 安全
(5)持续握手(Expect-continue handshake)
在认证系统或其余可能遭到服务器拒绝应答的状况下(如:登录失败),若是发送整个请求体,则会大大下降效率。此时,能够先发送部分请求(如:只发送请求头)进行试探,若是服务器愿意接收,则继续发送请求体。此项优化主要进行如下配置:服务器
// use expect-continue handshake
HttpProtocolParams.setUseExpectContinue(httpParams, true);
(6)“旧链接”检查(Stale connection check)
HttpClient为了提高性能,默认采用了“重用链接”机制,即在有传输数据需求时,会首先检查链接池中是否有可供重用的链接,若是有,则会重用链接。同时,为了确保该“被重用”的链接确实有效,会在重用以前对其进行有效性检查。这个检查大概会花费15-30毫秒。关闭该检查举措,会稍微提高传输速度,但也可能出现“旧链接”太久而被服务器端关闭、从而出现I/O异常。
关闭旧链接检查的配置为:
// disable stale check
HttpConnectionParams.setStaleCheckingEnabled(httpParams, false);
(7)超时设置
进行超时设置,让链接在超过期间后自动失效,释放占用资源。
// timeout: get connections from connection pool
ConnManagerParams.setTimeout(httpParams, 1000);
// timeout: connect to the server
HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
// timeout: transfer data from server
HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
(8)链接数限制
配置每台主机最多链接数和链接池中的最多链接总数,对链接数量进行限制。其中,DEFAULT_HOST_CONNECTIONS和DEFAULT_MAX_CONNECTIONS是由客户端程序员根据须要而设置的。
// set max connections per host
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(DEFAULT_HOST_CONNECTIONS));
// set max total connections
ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
通过优化后,上一篇日志中的getHttpClient()方法代码以下:
[java] view plaincopy
private static synchronized HttpClient getHttpClient() {
if(httpClient == null) {
final HttpParams httpParams = new BasicHttpParams();
// timeout: get connections from connection pool
ConnManagerParams.setTimeout(httpParams, 1000);
// timeout: connect to the server
HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
// timeout: transfer data from server
HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
// set max connections per host
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(DEFAULT_HOST_CONNECTIONS));
// set max total connections
ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
// use expect-continue handshake
HttpProtocolParams.setUseExpectContinue(httpParams, true);
// disable stale check
HttpConnectionParams.setStaleCheckingEnabled(httpParams, false);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8);
HttpClientParams.setRedirecting(httpParams, false);
// set user agent
String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2) Gecko/20100115 Firefox/3.6";
HttpProtocolParams.setUserAgent(httpParams, userAgent);
// disable Nagle algorithm
HttpConnectionParams.setTcpNoDelay(httpParams, true);
HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
// scheme: http and https
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager manager = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
httpClient = new DefaultHttpClient(manager, httpParams);
}
return httpClient;
}
附录:关于HttpURLConnection的优化,网上资料很少。从Android官网上看到一点,整理以下:
(1)上传数据至服务器时(即:向服务器发送请求),若是知道上传数据的大小,应该显式使用setFixedLengthStreamingMode(int)来设置上传数据的精确值;若是不知道上传数据的大小,则应使用setChunkedStreamingMode(int)——一般使用默认值“0”做为实际参数传入。若是两个函数都未设置,则系统会强制将“请求体”中的全部内容都缓存至内存中(在经过网络进行传输以前),这样会浪费“堆”内存(甚至可能耗尽),并加剧隐患。
(2)若是经过流(stream)输入或输出少许数据,则须要使用带缓冲区的流(如BufferedInputStream);大量读取或输出数据时,可忽略缓冲流(不使用缓冲流会增长磁盘I/O,默认的流操做是直接进行磁盘I/O的);
(3)当须要传输(输入或输出)大量数据时,使用“流”来限制内存中的数据量——即:将数据直接放在“流”中,而不是存储在字节数组或字符串中(这些都存储在内存中)。
参考文章:
http://hc.apache.org/httpclient-3.x/performance.html
http://blog.csdn.net/androidzhaoxiaogang/article/details/8198400
http://guowww.diandian.com/post/2011-11-07/15351973
http://blog.csdn.net/ken831001/article/details/7925309
http://www.iteye.com/topic/1117362
分享到: