Thursday, January 27, 2011

Follow 302 redirects with AndroidHttpClient

Recently I had to download images in Android mobile application. I found a fantastic tutorial by Tim Bray in android developers blog - Multithreading For Performance. You are downloading images using AndroidHttpClient and AsyncTask-s.

All was perfect before I realize that some of my images doesn't load because of HTTP code 302 - moved temporary or "The data requested actually resides under a different URL". So this means my request is redirected with "Location" header. I found that Android do not support any redirect following - with RedirectHandler or anything else (in SDK v2.3).

I solved my problem with simply reading headers, finding Location header and recursively call downloading method again. Warning: This technique does not prevent circular redirects, so you have to check for them on your own. Here is my method:

private static Bitmap downloadBitmap(String url) {
    final AndroidHttpClient client = 
        AndroidHttpClient.newInstance("Android");
    final HttpGet request = new HttpGet(url);
    
    try {
        HttpResponse response = client.execute(request);
        final int statusCode = 
            response.getStatusLine().getStatusCode();
        
        if (statusCode != HttpStatus.SC_OK) {
            Header[] headers = response.getHeaders("Location");
            
            if (headers != null && headers.length != 0) {
                String newUrl = 
                    headers[headers.length - 1].getValue();
                // call again with new URL
                return downloadBitmap(newUrl);
            } else {
                return null;
            }
        }
        
        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                inputStream = entity.getContent();
                
                // do your work here
                return BitmapFactory.decodeStream(inputStream);
            } finally {
                if (inputStream != null) {
                    inputStream.close();  
                }
                entity.consumeContent();
            }
        }
    } catch (Exception e) {
        request.abort();
    } finally {
        if (client != null) {
            client.close();
        }
    }
    
    return null;
}

Lets all hope Google engineers will fix following redirects in future versions of Android SDK

Edit

I have found that you shouldn't use AndroidHttpClient but instead use HttpUrlConnection. This class follows up to 5 redirects and supports cache. Here is the improved code snippet:
private static Bitmap downloadBitmap(String stringUrl) {
    URL url = null;
    HttpURLConnection connection = null;
    InputStream inputStream = null;
    
    try {
        url = new URL(stringUrl);
        connection = (HttpURLConnection) url.openConnection();
        connection.setUseCaches(true);
        inputStream = connection.getInputStream();
        
        return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
    } catch (Exception e) {
        Log.w(TAG, "Error while retrieving bitmap from " + stringUrl, e);
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }
    
    return null;
}