(1)采用單例模式(重用HttpClient實例)
對於一個通信單元甚至是整個應用程序,Apache強烈推薦只使用一個HttpClient的實例。例如:
private static HttpClient httpClient = null;
private static synchronized HttpClient getHttpClient() {
if(httpClient == null) {
final HttpParams httpParams = new BasicHttpParams();
httpClient = new DefaultHttpClient(httpParams);
}
return httpClient;
}
(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進行管理)
例如:
ClientConnectionManager manager = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
httpClient = new DefaultHttpClient(manager, httpParams);
(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);
在Apache的HttpClient包中,有三個設置超時的地方:
/ 從連接池中取連接的超時時間/
ConnManagerParams.setTimeout(params, 1000);
/連接超時/
HttpConnectionParams.setConnectionTimeout(params, 2000);
/請求超時/
HttpConnectionParams.setSoTimeout(params, 4000);
第一行設置ConnectionPoolTimeout:
這定義了從ConnectionManager管理的連接池中取出連接的超時時間,此處設置為1秒。
第二行設置ConnectionTimeout:
這定義了通過網絡與服務器建立連接的超時時間。Httpclient包中通過一個異步線程去創建與服務器的socket連接,這就是該socket連接的超時時間,此處設置為2秒。
第三行設置SocketTimeout:
這定義了Socket讀數據的超時時間,即從服務器獲取響應數據需要等待的時間,此處設置為4秒。
connectionTimeout:指的是連接一個url的連接等待時間。
soTimeout:指的是連接上一個url,獲取response的返回等待時間
(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);
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;
}