[TUT] Networking off the UI thread – SOLID Architecture

Hi guys, this is a Tutorial Request to show how to architect an app in a S.O.L.I.D design to complete networking tasks.

Now I am not an expert at this, but I believe the example does show the signs of a SOLID design. I can also see places where I could have used more interfaces and perhaps reduced responsibility. The one thing I noticed is, it is hard to design an application without some end goal to complete. What I mean is, having a scenario to code to I find is much easier than trying to make something generic that most people can grasp and mould to their needs. Yes I am actually saying I need requirements. With that said lets crack on!

In this tutorial we are going to create an application that runs a service spawning background threads. These background threads can run tasks that talk to the internet / parse output / perform complex tasks. Once these are complete they can send a response back to the calling Activity allowing it to display data, update UI components or just acknowledge the task is complete.

This will involve:

  • Creating singular tasks that can be run on a separate thread (internet stuff)
  • Create a service that will run and manage these threads
  • Have an interface so each activity can request a unit of work to be done
  • Create a ‘super’ activity so all task code is in one place
  • Have an Activity example that can call the tasks and receive the outcome

Ok .. here … we .. go.

Please check the source code for the full compilable project, it also heavily commented (which isn’t recommended in a SOLID design).
This code is a bit circular so bear with it.

First off we need to create our tasks, these implement runnable signalling they are capable of running as a single unit of work. We will create a hierarchy of Tasks, each task will implement from an abstract task class. This allows us to put the communication between the task and service in once place and the actual task just does it work then says “I’ve finished”.

Each task has a Message class field, this is used to hold any data the task wishes to send back, it also holds a reference to where the task came from (i.e. our service).

android.os.Message.java

Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.

While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

Here is the super class for each Task:

Task.java

package com.blundell.solidnetworking.service.factory;

import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;

/**
 * A single unit of work that is executed as a task
 * @author paul.blundell
 *
 */
public abstract class Task implements Runnable {

	protected final Message msg;

	public Task(Message msg) {
		this.msg = Message.obtain(msg);
	}

	@Override
	public abstract void run();

	/**
	 * Call this if you want to add data to your message before it is sent back up to your Activity
	 * @param data attach any data to the bundle here
	 */
	protected void setResponseData(Bundle data){
		msg.setData(data);
	}

	/**
	 * Replies to our Service saying this task is completed
	 * @param response The response message to send
	 */
	protected void sendReply() {
		Messenger replyTo = msg.replyTo;
		if (replyTo != null) {
			try {
				replyTo.send(msg);
			} catch (Exception e) { // Mostly RemoteException
				try {
					msg.what = 666; // Some error code
					replyTo.send(msg);
				} catch (RemoteException e1) { /** Nothing we can do */ }
			}
		}
	}
}

An example implementation is the DoSomethingTask below. As you can see it is possible to give each task some input and send back output. In the DoSomethingTask we just send a String and send back another String, you can use more complex objects just make them implement serializable. Anything you can send in a Bundle can be sent and received by the Task.

DoSomethingTask.java

package com.blundell.solidnetworking.service.factory.task;

import com.blundell.solidnetworking.service.factory.Task;
import com.blundell.solidnetworking.util.Log;

import android.os.Bundle;
import android.os.Message;

/**
 * @author paul.blundell
 * Here you can do networking or other long running 'tasks' that do not belong on the main thread
 */
public class DoSomethingTask extends Task {
	public static final String SOME_STRING = "someString";
	public static final String INPUT_VAR = "someInputVar";

	public DoSomethingTask(Message msg) {
		super(msg);
	}

	@Override
	public void run() {
		// Do some work
		Log.i("Hi, I'm on another thread");

		Log.i("I received the variable: " + msg.getData().getString(INPUT_VAR));

		// Set the response - according to the work done
		Bundle b = new Bundle();
		b.putString(SOME_STRING, "test string in bundle, created in task - sent back to activity (responselistener)");
		setResponseData(b);

		// Finished so send reply
		sendReply();
	}

}

Now we have our tasks setup we need someway of calling them from our Activity. However just having the tasks implement Runnable does not make them run on there own thread. We need a TaskExecutor, android has a nice one right here (ThreadPoolExecutor). I recommend reading the API documentation on that class. Alright we can’t just dump a ThreadPoolExecutor in ever Activity thats just nasty. So we wrap the executor in an Android Service, that way we can bind to it from each Activity and get our tasks running yay!

SolidService.java

package com.blundell.solidnetworking.service;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.blundell.solidnetworking.service.factory.Task;
import com.blundell.solidnetworking.service.factory.TaskFactory;
import com.blundell.solidnetworking.util.Log;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Messenger;

/**
 * Our service is used to manage the running of tasks. We have a queue of tasks that need to be ran
 * currently this runs the tasks in a linear fashion. Therefore tasks that send replys to the UI could be queued behind fire and forget tasks *hint (no comparison)*
 * @author paul.blundell
 *
 */
public class SolidService extends Service {
	private static final int QUEUE_CAPACITY = 10;
	// A handler for incoming messages from our client
	private Messenger messenger;
	// A pool of threads for the android system to manage our tasks
	private static ThreadPoolExecutor executor;
	// A factory to help us retrieve our tasks
	private static TaskFactory factory;

	@Override
	public void onCreate() {
		super.onCreate();
		messenger = new Messenger(messageHandler);
		executor = new ThreadPoolExecutor(1, 3, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(QUEUE_CAPACITY));
		factory = new TaskFactory();
	}

	@Override
	public IBinder onBind(Intent intent) {
		Log.d("Service was bound");
		return messenger.getBinder();
	}

	protected static Handler messageHandler = new Handler() {
		@Override
		public void handleMessage(android.os.Message msg) {
			Task task = factory.getTask(msg);
			if(task != null)
				executor.execute(task);
		};
	};

	@Override
	public void onDestroy() {
		waitForTasksToFinishThenStop();
		messenger = null;
		factory = null;
		super.onDestroy();
	}

	private static void waitForTasksToFinishThenStop() {
		executor.shutdown();
		try {
			executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
		} catch (InterruptedException e) {
			Log.e("Unfinished tasks", e);
		}
		executor = null;
	}
}

It’s important to notice how the service shutsdown the executor when it is finishing. This stops nasty bugs with connections being open, callbacks when you don’t expect it or even open databases ew. We also use a TaskFactory here so that we can easily add new tasks as necessary.

TaskFactory.java

package com.blundell.solidnetworking.service.factory;

import com.blundell.solidnetworking.R;
import com.blundell.solidnetworking.service.factory.task.DoSomethingTask;
import com.blundell.solidnetworking.service.factory.task.FireAndForgetTask;
import com.blundell.solidnetworking.util.Log;

import android.os.Message;

/**
 * This is used by our service to return the appropriate task that was asked for
 * @author paul.blundell
 *
 */
public class TaskFactory {

	public Task getTask(Message msg) {
		switch (msg.what) {
		case R.id.task_do_something:
			return new DoSomethingTask(msg);
		case R.id.task_do_fire_and_forget:
			return new FireAndForgetTask(msg);
		default:
			Log.d("No task found");
			return null;
		}
	}

}

Now we need to bind to the service to get it started. This is done using a ServiceConnectionManager, it does what it says on the tin, manages the connection to the service. We talk to the service using Messages and in the connection manager we hold a queue sending messages as they are received from the Activity’s (i.e. as we request to do things on the internet).

ServiceConnectionManager.java

package com.blundell.solidnetworking.service;

import java.lang.ref.WeakReference;
import java.util.LinkedList;

import com.blundell.solidnetworking.util.Log;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.*;

/**
 * We deal with calls to the service in a FIFO manner
 *
 * @author Blundell
 */
public abstract class ServiceConnectionManager implements ServiceConnection {
	// The context the service is running in
	private Context context;
	// If the service has been bound yet or not
	private boolean bound;
	// A handler to the service to send messages
	private Messenger service;
	// A queue of tasks that the app is wanting the service to do
	private final LinkedList<Message> msgQueue;
	// This is the listener if the Activity wants to get a response from the task ran by the service
	private final ReplyHandler replyTo;

	public ServiceConnectionManager(Context context, ResponseListener listener) {
		this.context = context;
		replyTo = new ReplyHandler(listener);
		msgQueue = new LinkedList<Message>();
		context.bindService(new Intent(context, SolidService.class), this, Context.BIND_AUTO_CREATE);
	}

	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		this.service = new Messenger(service);
        bound = true;

        // If there is a msg waiting to be sent, send after binding
        sendServiceMessage();
	}

	/**
	 * Get a message with the id of the task we want to run
	 * @param taskId
	 * @return
	 */
	protected Message obtainMessage(int taskId){
		Message msg = Message.obtain();
		msg.replyTo = new Messenger(replyTo);
		msg.what 	= taskId;
		return msg;
	}

	protected void addToQueue(Message msg) {
		msgQueue.add(msg);
		if(bound) {
			sendServiceMessage();
		} else {
			Log.d("The service is not bound.");
        	Log.d("Message ID was:" + Log.identifyMessage(context.getResources(), msg));
        	Log.d("Message was added to the queue and service call will be done on next call (when we're bound)");
		}
	}

	private void sendServiceMessage() {
		try {
			while(!msgQueue.isEmpty()){
				Message msg = msgQueue.removeFirst();
				service.send(msg);
				Log.d("Sent the service the msg: "+Log.identifyMessage(context.getResources(), msg));
			}
		} catch (RemoteException e) {
			Log.d("Can't do much about remote exceptions.");
		}
	}

	private static class ReplyHandler extends Handler {
		private final WeakReference<ResponseListener> mListener;

		public ReplyHandler(ResponseListener listener) {
			mListener = new WeakReference<ResponseListener>(listener);
		}

		@Override
		public void handleMessage(Message msg) {
			// We now pass the message onto the Activity if it set a listener (which we are enforcing it does with the BlundellActivity, using the ResponseListener interface)
			if(mListener.get() != null)
				mListener.get().handleServiceResponse(msg);
		}
	}

	@Override
	public void onServiceDisconnected(ComponentName name) {
		service = null;
        bound = false;
	}

	public void stop(){
		msgQueue.clear();
		context.unbindService(this);
		bound = false;
		context = null;
	}
}

Attempting to ensure the single responsibility principle we have a secondary class called the ServiceClient this is where the Activity talks to the service, it holds a nice interface so the Activity has no idea how we implement the service, it then calls the ServiceConnectionManager adding to its queue.

If you wanted to create a new task, this is where you would start, create an identifier for your task, create a new message and send it to the service. Then create your task and add it to the TaskFactory. Simple?

ServiceClient.java

package com.blundell.solidnetworking.service;

import com.blundell.solidnetworking.R;
import com.blundell.solidnetworking.service.factory.task.DoSomethingTask;

import android.content.Context;
import android.os.Message;

/**
 * A client for our service, here we create the 'interface' that our activity's can talk to.
 * @author paul.blundell
 *
 */
public class ServiceClient extends ServiceConnectionManager {

	public ServiceClient(Context context, ResponseListener listener) {
		super(context, listener);
	}

	public void doSomething(String input){
		Message msg = obtainMessage(R.id.task_do_something);
		msg.getData().putString(DoSomethingTask.INPUT_VAR, input);
		addToQueue(msg);
	}

	public void doFireAndForgetTask() {
		Message msg = obtainMessage(R.id.task_do_fire_and_forget);
		addToQueue(msg);
	}

}

Alright sorry for going backwards but recall the ServiceConnectionManager, this needs to send messages back to the activities when the tasks are complete. We do this using an interface called ResponseListener. You could implement this on any object and it will receive callbacks when your task has completed.

ResponseListener.java

package com.blundell.solidnetworking.service;

import android.os.Message;

/**
 *
 * @author paul.blundell
 *
 * This is the interface stating we are listening to our tasks for a reply
 *
 */
public interface ResponseListener {

	public abstract void handleServiceResponse(Message msg);

}

Thats it you could create an Activity to use this architecture, however we are going to take it a step further and use a super activity to handle connecting and talking to our service. This means each activity will bind to the service and allow it to call our ServiceClient asking for tasks to be complete. I would always advice creating a super activity. It is handy for grouping duplicate code, obviously you won’t have any code when you create a new application but better off putting it in now than having to retro fit it, you will need it.

BlundellActivity.java

package com.blundell.solidnetworking.ui;

import com.blundell.solidnetworking.service.ResponseListener;
import com.blundell.solidnetworking.service.ServiceClient;
import com.blundell.solidnetworking.util.Log;

import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.widget.Toast;

/**
 * Our 'super' activity, here we can put common code. It is also used to bind to our service,
 * therefore any class that subclasses this class can talk to the service and perform tasks off the UI thread.
 * @author paul.blundell
 *
 */
public abstract class BlundellActivity extends Activity implements ResponseListener {

	private ServiceClient serviceClient;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		lazyInitService();
	}

	@Override
	protected void onResume() {
		super.onResume();
		lazyInitService();
	}

    protected ServiceClient getServiceClient() {
    	lazyInitService();
        Log.d("Returning service from super activity");
        return serviceClient;
    }

	private void lazyInitService() {
		if(serviceClient == null) {
			Log.d("Created new service");
			serviceClient = new ServiceClient(this, this);
		}
	}

	public void popToast(String msg){
		Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
	}

	public void popBurntToast(String msg){
		Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
	}

	/** Does nothing here */
	@Override
	public void handleServiceResponse(Message msg){
		// Implemented in sub classes
		// If you wanted you could check for errors here and shown a generic dialog error message
	}

	@Override
	protected void onStop() {
		stopService();
		super.onStop();
	}

	private void stopService() {
		if(serviceClient != null){
			Log.d("super activity onStop - stopping service");
			serviceClient.stop();
			serviceClient = null;
		}
	}
}

Finally here is an example activity that runs the tasks. Don’t forget you are binding to a service in the super onCreate, so any task request in the onCreate will be slightly delayed (because it will be added to the queue but not ran until the service is bound).
This example doesn’t do much, but you can imagine doing a network task, parsing the result and sending back some object to your Activity. When this activity receives the reply you can update your UI, perhaps adding text or filling a list. I do have examples of such tasks in some of my other tutorials (which use a similar architecture) so take a look around.

MainActivity.java

package com.blundell.solidnetworking.ui.phone;

import com.blundell.solidnetworking.FromXML;
import com.blundell.solidnetworking.R;
import com.blundell.solidnetworking.service.factory.task.DoSomethingTask;
import com.blundell.solidnetworking.ui.BlundellActivity;
import com.blundell.solidnetworking.util.Log;

import android.os.Bundle;
import android.os.Message;
import android.view.View;

/**
 *
 * @author paul.blundell
 *
 */
public class MainActivity extends BlundellActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @FromXML
    public void doFireAndForgetTask(View button){
    	getServiceClient().doFireAndForgetTask();
    }

    @FromXML
    public void doSomethingTask(View button){
    	getServiceClient().doSomething("I sent this");
    }

    @Override
    public void handleServiceResponse(Message msg) {
    	dealWithResponse(msg.what, msg.getData());
    }

	private void dealWithResponse(int responseId, Bundle data) {
		switch(responseId){
    	case R.id.task_do_something:
			dealWithDoSomethingResult(data);
    		break;
    	default:
    		Log.v("Received service reply that wasn't handled");
    	}
	}

	private void dealWithDoSomethingResult(Bundle data) {
		String string = data.getString(DoSomethingTask.SOME_STRING);

		Log.d("Received reply from the task. The var was: "+ string);

		popToast("Do Something Task Complete");
	}
}

I hope you’ve find this useful, please comment with any questions. One thing I would say is don’t take this as the only way to do this. There are many architectures you could use, also using other Android framework classes like Loaders or ASyncTasks. This architecture can also be improved by adding comparability to the tasks, then you could give priority to your UI changing tasks and de-prioritize menial tasks like analytics etc.

Source Code:

Solid Networking Tut Eclipse Source

Git Hub Repo – SOLIDNetworkingTut