Home > Uncategorized > Best way to use HttpClient in Android

Best way to use HttpClient in Android

Many Android applications access the Internet resources over HTTP (and my projects are no exception). There are 2 common ways to do that: use Apache HttpClient 4.x (which is included in Android) or use HttpURLConnection (from Java). Google stated in a September 29, 2011 blog post that they prefer you use HttpURLConnection, but many apps and a large number of Java libraries already use HttpClient and won’t be changing soon (if ever). So HttpClient is here to stay.

With that in mind, the performance and footprint of HttpClient can vary widely based on how its set up. Here are my recommendations:

  • Always use one HttpClient instance for your entire application. HttpClient is not free to instantiate – each additional instance takes time to create and uses more memory. However, more importantly, using one instance allows HttpClient to pool and reuse connections along with other optimizations that can make big differences in how your application performs.
  • Use a thread safe connection manager. If you’re using one global HttpClient, it will be accessed by multiple threads concurrently – so if you don’t use a thread safe connection manager, Bad Things will happen.
  • Use Android’s android.net.SSLCertificateSocketFactory and android.net.SSLSessionCache if they’re available. Using these instead of the base HttpClient SSLSocketFactorywill reduce round trips when connecting to the same https site multiple times, making your application feel faster.
  • Set the user agent to something useful. That way, the server’s logs will be far more useful, which may save you (or someone else) a lot of time later if (when) a problem occurs.

With all that said, here’s how I get my global HttpClient instance. This code should work on all Android versions (it should even work all the way back to 1.0 – if anyone cares). I use Google Guice‘s Provider interface and injection to get the application, but you can easily adopt this to a form that doesn’t use Guice.

import java.lang.reflect.Method;
import java.util.Locale;
 
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
 
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;
 
import com.google.inject.Inject;
import com.google.inject.Provider;
 
public class HttpClientProvider implements Provider {
@Inject
Application application;
 
// Wait this many milliseconds max for the TCP connection to be established
private static final int CONNECTION_TIMEOUT = 60 * 1000;
 
// Wait this many milliseconds max for the server to send us data once the connection has been established
private static final int SO_TIMEOUT = 5 * 60 * 1000;
 
private String getUserAgent(String defaultHttpClientUserAgent){
String versionName;
try {
versionName = application.getPackageManager().getPackageInfo(
application.getPackageName(), 0).versionName;
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
StringBuilder ret = new StringBuilder();
ret.append(application.getPackageName());
ret.append("/");
ret.append(versionName);
ret.append(" (");
ret.append("Linux; U; Android ");
ret.append(Build.VERSION.RELEASE);
ret.append("; ");
ret.append(Locale.getDefault());
ret.append("; ");
ret.append(Build.PRODUCT);
ret.append(")");
if(defaultHttpClientUserAgent!=null){
ret.append(" ");
ret.append(defaultHttpClientUserAgent);
}
return ret.toString();
}
 
@Override
public HttpClient get() {
AbstractHttpClient client = new DefaultHttpClient(){
@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(
new Scheme("https", getHttpsSocketFactory(), 443));
HttpParams params = getParams();
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, SO_TIMEOUT);
HttpProtocolParams.setUserAgent(params, getUserAgent(HttpProtocolParams.getUserAgent(params)));
return new ThreadSafeClientConnManager(params, registry);
}
 
/** Gets an HTTPS socket factory with SSL Session Caching if such support is available, otherwise falls back to a non-caching factory
* @return
*/
protected SocketFactory getHttpsSocketFactory(){
try {
Class sslSessionCacheClass = Class.forName("android.net.SSLSessionCache");
Object sslSessionCache = sslSessionCacheClass.getConstructor(Context.class).newInstance(application);
Method getHttpSocketFactory = Class.forName("android.net.SSLCertificateSocketFactory").getMethod("getHttpSocketFactory", new Class[]{int.class, sslSessionCacheClass});
return (SocketFactory) getHttpSocketFactory.invoke(null, CONNECTION_TIMEOUT, sslSessionCache);
}catch(Exception e){
Log.e("HttpClientProvider", "Unable to use android.net.SSLCertificateSocketFactory to get a SSL session caching socket factory, falling back to a non-caching socket factory",e);
return SSLSocketFactory.getSocketFactory();
}
 
}
};
return client;
}
 
}

I use this approach in my CallerID app (source) and an upcoming app (that I cannot yet talk about). I’ve also submitted patches (which have been accepted) to reddit is fun, so it will use this approach in its next version.

Creative Commons License
Best way to use HttpClient in Android by Craig Andrews, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

Categories: Uncategorized Tags:
  1. February 3rd, 2012 at 08:45 | #1
    Hi,

    Getting the following error when the httpclient is being called on the second attempt

    Connection pool shut down

    I’ve created the the following module

    bind(HttpClient.class).toProvider(HttpClientProvider.class).in(Scopes.SINGLETON);

    And I then inject this into my asycntask

    @Inject
    private HttpClient iHttpClient;

    Any ideas?

    John

    • February 17th, 2012 at 15:14 | #2
      I’m not really sure. If you provide me the full stack trace, that would be helpful. Also, if the source is freely available, if you refer me to it, I could look it over.
  2. Chuck Doucette
    June 7th, 2012 at 16:56 | #3
    Thanks for posting! Reusing an HttpClient as you demonstrated above appears to have made a significant difference in our application’s performance. Now I just need to figure out how to add caching (I’m attempting to use apache’s httpclient-cache as you suggested in a related post).

    FYI:
    a) If you don’t know how to make injection work properly (as I don’t yet), you get NullPointerExceptions. Instead – I changed application to be a Context and made it static and then set it to Context.getApplicationContext() when I had a reference to a context object.
    b) I didn’t see the same code or similar code to what you have above in either your Caller ID application or in reddit is fun (I was at least hoping to see a complete working example of injection).

  3. June 13th, 2012 at 11:12 | #4
    @Chuck Doucette
    Since HttpClient use in Android is discouraged, I made the HttpResponseCache library (see my other post on that topic) which doesn’t use HttpClient. Once I did that, I changed my CallerID application to use HttpUrlConnection and HttpResponseCache instead of HttpClient and HttpClient-Cache.

    However, all the CallerID history is available. Here’s a link to the tree just before I changed the application to no longer use HttpClient: https://gitorious.org/callerid-for-android/mainline/trees/e2bb54cd2d7a5fe5539a61255f534ed895519a9e/

  1. September 30th, 2011 at 12:20 | #1
  2. February 21st, 2012 at 08:56 | #2
  3. February 21st, 2012 at 09:01 | #3