Android HttpURLConnection及HttpClient选择

本文比较了Android中HttpURLConnection与HttpClient的优缺点,并提供了详细的优化方案,包括连接管理、线程安全配置、数据传输方式等。

Android HttpURLConnection及HttpClient选择

介绍Android中Http请求方式的选择、区别及几个常用框架对API的选择

1. 两种请求方式对比
Android Http请求API主要分两种:
第一种是Java的HttpURLConnection,默认带gzip压缩
第二种Apache的HttpClient,默认不带gzip压缩
两种方式请求connection都是keep alive,默认User-Agent不同。

关于两种方式发出去的请求头对比图如下:
HttpURLConnection
HttpClient

上图是通过抓包抓包的,具体如何抓包可参考:Android利用Fiddler进行网络数据抓包
测试代码见:android-http-api-compare@github, 测试APK见:http-api-compare.apk

 

2. 常用Http框架对Http API的选择

在 Froyo(2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。

 

另外在 Gingerbread(2.3) HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了请求结果缓存。
再加上 HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。

 

Retrofit及Volley框架默认在Android Gingerbread(API 9)及以上都是用HttpURLConnection,9以下用HttpClient。

Volley 源码解析以及更多对比可以参考:Volley 源码解析

 

3. GZip压缩
一般对于API请求需带上GZip压缩,因为API返回数据大都是JSon串之类字符串,GZip压缩后内容大小大幅降低,下面是这两个网页GZip压缩前后对比,都是第一条表示GZip压缩后,第二条为压缩前
GZip
更详细关于HttpURLConnection及HttpClient对比可见:Android’s HTTP Clients
更多关于网络请求优化请参考:Android性能优化第四部分网络的介绍


 尽管Android官网推荐在2.3及后续版本中使用HttpURLConnection作为网络开发首选类,但在连接管理线程安全方面,HttpClient还是具有很大优势。就目前而言,HttpClient仍是一个值得考虑的选择。对于HttpClient的优化,可以从以下几个方面着手:

    (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);

    (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 plain copy
  1. private static synchronized HttpClient getHttpClient() {  
  2.     if(httpClient == null) {  
  3.         final HttpParams httpParams = new BasicHttpParams();    
  4.           
  5.         // timeout: get connections from connection pool  
  6.         ConnManagerParams.setTimeout(httpParams, 1000);    
  7.         // timeout: connect to the server  
  8.         HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);  
  9.         // timeout: transfer data from server  
  10.         HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);   
  11.           
  12.         // set max connections per host  
  13.         ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(DEFAULT_HOST_CONNECTIONS));    
  14.         // set max total connections  
  15.         ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);  
  16.           
  17.         // use expect-continue handshake  
  18.         HttpProtocolParams.setUseExpectContinue(httpParams, true);  
  19.         // disable stale check  
  20.         HttpConnectionParams.setStaleCheckingEnabled(httpParams, false);  
  21.           
  22.         HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);    
  23.         HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8);   
  24.             
  25.         HttpClientParams.setRedirecting(httpParams, false);  
  26.           
  27.         // set user agent  
  28.         String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2) Gecko/20100115 Firefox/3.6";  
  29.         HttpProtocolParams.setUserAgent(httpParams, userAgent);       
  30.           
  31.         // disable Nagle algorithm  
  32.         HttpConnectionParams.setTcpNoDelay(httpParams, true);   
  33.           
  34.         HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);    
  35.           
  36.         // scheme: http and https  
  37.         SchemeRegistry schemeRegistry = new SchemeRegistry();    
  38.         schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));    
  39.         schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));  
  40.   
  41.         ClientConnectionManager manager = new ThreadSafeClientConnManager(httpParams, schemeRegistry);    
  42.         httpClient = new DefaultHttpClient(manager, httpParams);   
  43.     }         
  44.       
  45.     return httpClient;  
  46. }  


    附录:关于HttpURLConnection的优化,网上资料不多。从Android官网上看到一点,整理如下:

    (1)上传数据至服务器时(即:向服务器发送请求),如果知道上传数据的大小,应该显式使用setFixedLengthStreamingMode(int)来设置上传数据的精确值;如果不知道上传数据的大小,则应使用setChunkedStreamingMode(int)——通常使用默认值“0”作为实际参数传入。如果两个函数都未设置,则系统会强制将“请求体”中的所有内容都缓存至内存中(在通过网络进行传输之前),这样会浪费“堆”内存(甚至可能耗尽),并加重隐患。

    (2)如果通过流(stream)输入或输出少量数据,则需要使用带缓冲区的流(如BufferedInputStream);大量读取或输出数据时,可忽略缓冲流(不使用缓冲流会增加磁盘I/O,默认的流操作是直接进行磁盘I/O的);

    (3)当需要传输(输入或输出)大量数据时,使用“流”来限制内存中的数据量——即:将数据直接放在“流”中,而不是存储在字节数组或字符串中(这些都存储在内存中)。



利用volley进行http设置请求头、超时及请求参数设置(post)

2013年07月31日  ⁄ 综合 ⁄ 共 1047字 ⁄ 字号  小 中 大  ⁄ 评论关闭
id="iframeu1788635_0" src="http://pos.baidu.com/ncpm?rdid=1788635&dc=2&di=u1788635&dri=0&dis=0&dai=2&ps=235x772&dcb=BAIDU_SSP_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1464855367806&ti=%E5%88%A9%E7%94%A8volley%E8%BF%9B%E8%A1%8Chttp%E8%AE%BE%E7%BD%AE%E8%AF%B7%E6%B1%82%E5%A4%B4%E3%80%81%E8%B6%85%E6%97%B6%E5%8F%8A%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE(post)%20%7C%20%E5%AD%A6%E6%AD%A5%E5%9B%AD&ari=1&dbv=2&drs=1&pcs=1092x528&pss=1093x255&cfv=0&cpl=24&chi=1&cce=true&cec=UTF-8&tlm=1464855367&ltu=http%3A%2F%2Fwww.xuebuyuan.com%2F1674579.html&ltr=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3D98K8XRYIkTunzFRL374Leai5lIQIYky111IbDi56fi5nzHbVIXPJtqWFVJUyyhlC%26wd%3D%26eqid%3Dcdb465f20001088400000002574feb3c&ecd=1&psr=1366x768&par=1366x728&pis=-1x-1&ccd=24&cja=true&cmi=35&col=zh-CN&cdo=-1&tcn=1464855368&qn=03c7cc969ec0768a&tt=1464855367739.105.201.204" width="336" height="280" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="margin: 0px; padding: 0px; border-width: 0px; border-style: initial; vertical-align: bottom; background: transparent;">

这里以post请求说明,get请求相似设置请求头及超时。

1.自定义request,继承com.android.volley.Request

2.构造方法实现(basecallback,为自定义的监听,实现Response.Listener,ErrorListener接口)--post请求

 public BaseRequest(String url,String params, BaseCallback<T> callback)
    {
	super(Method.POST, url, callback);
	this.callback = callback;
	this.params = params;
	Log.e(TAG, "request:" + params);
	setShouldCache(false);
    }

3.请求头设置:重写getHeaders方法

 @Override
    public Map<String, String> getHeaders() throws AuthFailureError
    {
	Map<String, String> headers = new HashMap<String, String>();
	headers.put("Charset", "UTF-8");
	headers.put("Content-Type", "application/x-javascript");
	headers.put("Accept-Encoding", "gzip,deflate");
	return headers;
    }

设置字符集为UTF-8,并采用gzip压缩传输

4.超时设置:重写getRetryPolicy方法

 @Override
    public RetryPolicy getRetryPolicy()
    {
	RetryPolicy retryPolicy = new DefaultRetryPolicy(SOCKET_TIMEOUT, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
	return retryPolicy;
    }

5.请求参数组装:重写getBody方法

 @Override
    public byte[] getBody() throws AuthFailureError
    {
	return params == null ? super.getBody() : params.getBytes();
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值