儘管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
分享到: