For a pleasant Android user experience it is vital for applications to actively provide feedback. When downloading images and data from the web, delays are expected. Developers should implement asynchronous connections and provide active feedback on their progress.

Throttling connections is another important technique. Android phones are mainly on 3G and Edge connections and are prone to slow connection speeds. Imagine downloading 100 images from the web simultaneously at 1 KB/second each. That certainly doesn’t make for the greatest user experience. Executing connections in smaller chunks is a much better option. For example, downloading those 100 images I mentioned earlier in chunks of 5 connections would allow for a comparable 20 KB/second rate.

Below is an example of my asynchronous HTTP connection implementation using the classes HttpConnection and ConnectionManager. It uses Apache’s HttpClient methods and allows for GET, POST, PUT and DELETE requests. A built in Bitmap decoder is also included.

To receive status updates from an HttpConnection a Handler is used (but not required). The HttpConnection will dispatch a message to the Handler at the occurrence of the following:

  • HttpConnection.DID_START – The connection is removed from the ConnectionManager’s queue and started
  • HttpConnection.DID_SUCCEED – The connection was successful and the response is stored in the Message instance obj field
  • HttpConnection.DID_ERROR – The connection failed and the exception is stored in the Message instance obj field


Here is an implementation of a Handler:

Handler handler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case HttpConnection.DID_START:
text.setText("Starting connection...");
break;
case HttpConnection.DID_SUCCEED:
String response = (String) message.obj;
text.setText(response);
break;
case HttpConnection.DID_ERROR:
Exception e = (Exception) message.obj;
e.printStackTrace();
text.setText("Connection failed.");
break;
}
}
};

Now that we have a Handler we can create an HttpConnection. Here is the one line of code doing so:

new HttpConnection(handler).get("http://twitter.com/statuses/user_timeline/69177017.rss");

Download this sample project

Sample Implementation

public void downloadTwitterIcon() {
Handler handler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case HttpConnection.DID_START: {
Log.d("Twitter Icon", "Starting Connection");
break;
}
case HttpConnection.DID_SUCCEED: {
Bitmap response = (Bitmap) message.obj;
icon.setImageBitmap(response);
break;
}
case HttpConnection.DID_ERROR: {
Exception e = (Exception) message.obj;
e.printStackTrace();
break;
}
}
}
};
new HttpConnection(handler)
.bitmap("http://a1.twimg.com/profile_images/398455304/gvsu-cis-avatar_bigger.jpg");
}

public void downloadTwitterStream() {
Handler handler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case HttpConnection.DID_START: {
text.setText("Starting connection...");
break;
}
case HttpConnection.DID_SUCCEED: {
String response = (String) message.obj;
text.setText(response);
break;
}
case HttpConnection.DID_ERROR: {
Exception e = (Exception) message.obj;
e.printStackTrace();
text.setText("Connection failed.");
break;
}
}
}
};
new HttpConnection(handler)
.get("http://twitter.com/statuses/user_timeline/69177017.rss");
}

HttpConnection.java [download]

package edu.gvsu.masl.asynchttp;

import java.io.*;

import org.apache.http.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.entity.*;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;

import android.graphics.*;
import android.os.*;

/**
* Asynchronous HTTP connections
*
* @author Greg Zavitz & Joseph Roth
*/
public class HttpConnection implements Runnable {

public static final int DID_START = 0;
public static final int DID_ERROR = 1;
public static final int DID_SUCCEED = 2;

private static final int GET = 0;
private static final int POST = 1;
private static final int PUT = 2;
private static final int DELETE = 3;
private static final int BITMAP = 4;

private String url;
private int method;
private Handler handler;
private String data;

private HttpClient httpClient;

public HttpConnection() {
this(new Handler());
}

public HttpConnection(Handler _handler) {
handler = _handler;
}

public void create(int method, String url, String data) {
this.method = method;
this.url = url;
this.data = data;
ConnectionManager.getInstance().push(this);
}

public void get(String url) {
create(GET, url, null);
}

public void post(String url, String data) {
create(POST, url, data);
}

public void put(String url, String data) {
create(PUT, url, data);
}

public void delete(String url) {
create(DELETE, url, null);
}

public void bitmap(String url) {
create(BITMAP, url, null);
}

public void run() {
handler.sendMessage(Message.obtain(handler, HttpConnection.DID_START));
httpClient = new DefaultHttpClient();
HttpConnectionParams.setSoTimeout(httpClient.getParams(), 25000);
try {
HttpResponse response = null;
switch (method) {
case GET:
response = httpClient.execute(new HttpGet(url));
break;
case POST:
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(data));
response = httpClient.execute(httpPost);
break;
case PUT:
HttpPut httpPut = new HttpPut(url);
httpPut.setEntity(new StringEntity(data));
response = httpClient.execute(httpPut);
break;
case DELETE:
response = httpClient.execute(new HttpDelete(url));
break;
case BITMAP:
response = httpClient.execute(new HttpGet(url));
processBitmapEntity(response.getEntity());
break;
}
if (method < BITMAP)
processEntity(response.getEntity());
} catch (Exception e) {
handler.sendMessage(Message.obtain(handler,
HttpConnection.DID_ERROR, e));
}
ConnectionManager.getInstance().didComplete(this);
}

private void processEntity(HttpEntity entity) throws IllegalStateException,
IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(entity
.getContent()));
String line, result = "";
while ((line = br.readLine()) != null)
result += line;
Message message = Message.obtain(handler, DID_SUCCEED, result);
handler.sendMessage(message);
}

private void processBitmapEntity(HttpEntity entity) throws IOException {
BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity);
Bitmap bm = BitmapFactory.decodeStream(bufHttpEntity.getContent());
handler.sendMessage(Message.obtain(handler, DID_SUCCEED, bm));
}

}

ConnectionManager.java [download]

package edu.gvsu.masl.asynchttp;

import java.util.ArrayList;

/**
* Simple connection manager to throttle connections
*
* @author Greg Zavitz
*/
public class ConnectionManager {

public static final int MAX_CONNECTIONS = 5;

private ArrayList<Runnable> active = new ArrayList<Runnable>();
private ArrayList<Runnable> queue = new ArrayList<Runnable>();

private static ConnectionManager instance;

public static ConnectionManager getInstance() {
if (instance == null)
instance = new ConnectionManager();
return instance;
}

public void push(Runnable runnable) {
queue.add(runnable);
if (active.size() < MAX_CONNECTIONS)
startNext();
}

private void startNext() {
if (!queue.isEmpty()) {
Runnable next = queue.get(0);
queue.remove(0);
active.add(next);

Thread thread = new Thread(next);
thread.start();
}
}

public void didComplete(Runnable runnable) {
active.remove(runnable);
startNext();
}

}