A Generic Android Server Design (using AIDL)

Introduction

This page desribes a generic server which runs on an Android device and uses AIDL. It explains why we use servers – that is, it describes the problem that services attempt to solve. It describes the means by which apps interact with this particular server implementation, and it also describes a general server's overall internal architecture. The server's intended to run on an Android platform and uses the Android SDK, inter-process communication (IPC), the Android Service class, the client/server model, Loopers, Handlers, Threads, Binders, and the Android Interface Definition Language (AIDL).

I'll post example source code that implements this design if there's any interest.

-- Jeff
   cmp@emirac.net

Purpose (what are we trying to accomplish?)

Users see the Android device as a set of apps. Each app presents a user interface which allows the user to search, view, and manipulate various bits of data stored on the device. The user interface (UI) within each app runs on a single thread – the UI thread – one per activity. In order for the UI to remain responsive to user input this thread must refrain from doing things which take “long” amounts of time. (In fact, the Android system will kill any app which takes more than a second or two to respond to user input.) The problem is that many interesting things take too long for Android’s impatient UI thread to tolerate. Often the operations that an app needs to accomplish take a Very Long Time when compared to the response time that the Android UI-thread requires. The consequence of this is that an app which needs to use the internet or needs to do calculations or communicate with lots of other apps or manipulate large amounts of data requires a way to remove the latency involved from its UI thread and put it someplace else. Also an app running on an android device is typically limited in the amount of runtime memory it can can use -- somewhere around 16 to 48 MBytes per app depending upon the device -- so we might need a way of spreading that memory constraint to another process. If every data or network or mathematical operation were instantaneous server design would be very simple, and in many cases we wouldn't need servers at all. This generic server description describes a server that can run in its own Android partition and service multiple apps each of which can be running in a different partition. It uses AIDL to communicate across the local process boundaries.

Consider a mechanical system like the cooling system in an automobile. The cooling system exists because the engine generates more heat than it can physically tolerate. The cooling system absorbs heat from the engine and moves it to the outside air by means of channels within its radiator core – a heat redistribution system. In much the same way this server attempts to solve the UI-thread/data-object latency problem by collecting latency and moving it into in a separate process and dissipating it in a set of threads, each of which contains a queue. Each queue consists of a set of Runnable tasks, and each task is a time-consuming call that does some work, say, database or calculation. The server is a sort of time machine – a latency-distribution device. It accomplishes this by separating a lengthy operation into a request followed at some later time by a reply. An app's service request returns immediately, and the app is free to process events in the UI until the server contacts it sometime later with the result.

The server class is called AidlServer (since it uses AIDL to communicate with client apps), and it has the following properties:

External Interface (how apps communicate with the server)

Overview

An app which wants to communicate with the server creates a Connection object. The application object sends data requests to the server by means of the Connection’s methods. Replies from these requests are returned to some application object which has implemented the server listeners (ConnectionListener, DataListener) and registered them with the Connection object.

The picture below represents an app on the left which communicates with the server on the right in order to accomplish some server-side task. The app exists in a separate process from the server. In order to affect communication with server the app creates a new Connection object and implements the DataListener and ConnectionListener interfaces which it registers with the Connection object. The data object library then calls the Connection’s connect() method which initiates a connection with the server. The server creates a new Client object on the remote side (Client 1 in the picture). The client object then invokes the data object library’s ConnectionListener (via the Connection object) in order to tell the app that the server connection has been established. From this point on requests for service are passed from the app to the server-side client via the connection, and replies from the server-side client are sent back to the app via the DataListener.

Classes

ConnectionListener is the interface through which the Connection notifies the client of changes in the IPC status. After the client calls connect() the Connection will call back into this listener’s onConnect() when the connection is complete and ready to process requests. After the client calls disconnect() or possibly due to external IPC problem the connection will call back this listener’s onDisconnect().

	interface ConnectionListener {
		public void onConnect();
		public void onDisconnect();
	}
	

DataListener is the interface through which the Connection sends data or status about data to the client. After the client calls doSomething() the Connection will reply by call back into this listener’s onSomething() with the result. In each case the returned requestID will correspond to the requestID of the originating request call. The resultObject can be any Android object that implements Parcelable, and we can have any number of doSomething/onSomething pairs in the interface that we'd like to define.

	interface DataListener {
		public void onSomething(int requestID, Parcelable resultObject);
		/* ... more "onSomethings" can go here... */
	}

Connection is the class through which the client (app) makes requests to the AidlServer. After the client calls doQuery() the Connection will reply by calling back into this listener’s onSomething() with the result. In each case the returned requestID will correspond to the requestID of the originating request.

	class Connection {
		public boolean connect();
		public void disconnect();
		public void setConnectionListener(ConnectionListener listener);
		public void setDataListener(DataListener listener);
		
		public void doSomething(int requestID, Parcelable requestObject);
		/* ... more "doSomethings" can go here... */
	} 	

The Connection object is all that an app needs in order to connect to the server and request it to do work. Requests to do work are initiated through the "doSomething" methods which can take any Android Parcelble object (or objects) as a parameter. The app must also implement ConnectionListener and DataListener interfaces in order to receive results from the server. Results are delivered by the onSomething callbacks which can use any Android Parcelable object (or objects) to return the results.

Internal Design

Overview

There are two types of control flow within the AIDL server. The first follows what an application-side program must do within the Connection object in order to connect with the service, send requests to the service, and receive replies. The second type of flow follows what happens within the AidlServer between a specific request and its associated reply (or replies – as example, one request may result in many replies).

When an application calls Connection.connect() the Connection object generates a bind intent and initiates a connection with the remote AidlServer by calling bindService(). Result is that eventually the Connection object’s onServiceConnected() will be called and given the AidlServer’s IBinder interface (DataServiceApi.Stub). This interface consists of one synchronous method called apiRegisterClient(). Connection then calls apiRegisterClient() which calls into the AidlServer. When the AidlServer receives this call it creates a new Client object and returns the new Client object’s IBinder interface to the Connection object. Client objects are server-side representations of remote clients (or applications). The AidlServer creates and manages a list of Client objects – one for each remote Connection object. The Connection object uses the Client IBinder to register its own callbacks for listeners specified in the Client’s IBinder. At this point the Connection holds an IBinder for the AidlServer (DataServiceApi.Stub) and an IBinder for the server-side Client object (DataServiceClientApi.Stub). The server-side Client object for this Connection holds a set of callbacks supplied by the Connection. The Connection then calls the DataListener.onConnected() method of the application which created the Connection. All subsequent remote communication from application to AidlServer will take place through the Client IBinder held by the Connection.

The AidlServer Client class represents the server-side of a remote Connection – one Client per Connection. When a new Client object is created it contains its IBinder (AidlServerClientApi.Stub) for the remote Connection to call into the Client and listeners (AidlServerConnectionListener and AidlServerConnectionListener) to call back into the Connection. The Client also creates two lists of pipeline thread queues called QueueThreads – one list for reading and one list for writing. Each QueueThread is a thread which implements a queue of tasks. A task is any object which implements Runnable, however, in this implementation a task is a subclass of the abstract QueueTask. The QueueThread blocks until it has tasks in its queue and executes them in its own thread in sequence removing them from the queue when they complete. Tasks are added to the QueueThread with QueueThread.enqueueTask(QueueTask). In a non-generic implementation there would be a subclass of QueueTask class associated with each type of operation which a Client can perform. When the Client object creates a new task object it passes the Connection’s associated data listener to the new task object.

When an application makes a request via the Connection the Connection object initiates the request by calling one of the Client IBinder methods and returning immediately. The Client object creates a new QueueTask object for the specific request and enqueues the new task in the shortest of its QueueThreads. Eventually that particular data task completes and calls back into the remote Connection by means of the listener which it’s holding. The Connection object then calls the appropriate method in the application’s registered DataListener. Parcelable objects are used as parameters to pass objects in each direction.

The picture below illustrates a slightly simplified version of the IPC flow and connections. A single app which uses the AidlServer is on the left side, and AidlServer is on the right. These are in two separate processes. The reverse-color boxes represent interfaces (IBinders) and sets of callbacks (listeners) each of which is implemented in the other’s process and sent across the process boundary. The server sends its AidlServerApi.Stub and AidlServerClientApi.Stub to the app, and the app sends its AidlServerConnectionListener and AidlServerDataListener to the service. These reverse-color interfaces are all defined in AIDL files. There can be multiple clients connected to the AidlServer, and that’s why the Client box is illustrated as a stack of boxes – there are an indeterminate number of them. A simplification in the illustration is that within each Client there are multiple QueueThreads, and within each QueueThread there can be an indeterminate number of tasks queued. However, the QueueThread box is omitted, and all the queued tasks in the Client are illustrated in the same stack of grey-shaded QueueTasks. (Remember there is one QueueTask for each app request.) The app creates a new Connection object and registers a ConnectionListener and DataListener.

When the app calls Connection.connect() the server responds by sending back its AidlServerApi.Stub. The Connection object uses this interface to call into the AidlServer and register itself. The AidlServer creates a new Client and sends the Client’s AidlServerClientApi.Stub to the Connection. The Connection uses the AidlServerClientApi.Stub to send its AidlServerConnectionListener and AidlServerDataListener to the Client and then calls the app’s ConnectionListener.onConnect() to tell the app that the connection has been made. The app can now make data requests by calling Connection.doSomething().

When the app makes a do-something request the Connection calls into the Client through its AidlServerClientApi.Stub. The call returns immediately, and the Client creates a new QueueTask and enqueues it. The QueueTask eventually runs, does the "something," and returns results back to the Connection through the AidlServerDataListener. The Connection forwards the result to the app through the app’s DataListener.

Interfaces (IBinder)

There are two remote interfaces (IBinders) that server uses. They are AidlServerApi.Stub and AidlServerClientApi.Stub.

The Server Interface is AidlServerApi.Stub. This is the interface that’s returned to the Connection object’s onServiceConnected() method after it calls bindService(). The Connection object uses this interface to ask the server to create a new server-side Client. The server creates the Client and returns its interface via the returned IBinder. It’s defined in an AIDL file called AidlServerApi.aidl:

	interface AidlServerApi {
		IBinder apiRegisterClient();
	}

It’s implemented as an anonymous class in AidlServer.java.

	private final AidlServerApi.Stub apiEndpoint = new AidlServerApi.Stub() {
		@Override
		public IBinder apiRegisterClient() throws RemoteException {
			IBinder b =  handleRegisterClient();
			return(b);
		}
	};

The Client Interface is AidlServerClientApi.Stub. This is the interface that’s returned to the Connection object’s onServiceConnected() method after it calls apiRegisterClient() on the server’s IBinder. The Connection object uses this interface to send requests and register listeners for replies. It’s defined in an AIDL file called AidlServerClientApi.aidl:

	interface AidlServerClientApi {
		oneway void apiUnregisterClient();
		oneway void apiSetConnectionListener(in AidlServerConnectionListener cListener);
		oneway void apiSetDataListener(in AidlServerDataListener dListener);
		oneway void apiDoSomething(in int requestID, in Parcelable requestData);
		/* ... more "apiDoSomethings" can go here... */
	}

It’s implemented as an anonymous class in Client.java.

	private final AidlServerClientApi.Stub apiEndpoint= new AidlServerClientApi.Stub() {

		@Override
		public void apiUnregisterClient() throws RemoteException {
			handleUnregister();
			return;
		}

		@Override
		public void apiSetConnectionListener(AidlServeConnectionListener cListener)  throws RemoteException {
			connectionListener = cListener;
			return;
		}

		@Override
		public void apiSetDataListener(AidlServerDataListener qListener) throws RemoteException {
			dataListener = dListener;
			return;
		}
		
		@Override
		public void apiDoSomething(int reqID, Parcelable requestData) throws RemoteException {
			handleDoSomething(reqID, requestData);
			return;
		}

	};

In addition to the two IBinder AIDL files there is an AIDL definition for reporting the service status and one for database operations. These are AidlServerConnectionListener.aidl and AidlServerDataListener.aidl.

	interface AidlServerConnectionListener {
		void onDisconnected();
	}

and

	interface AidlServerDataListener {
		void onSomething(int id, in Parcelable resultObject);
	}

The Connection object registers each of these listeners with the Client object at connect time, and the Client object passes them to each QueueTask at enqueue time. The QueueTask then uses the particular listener to send results (in the form of a Parcelable object) back to the Connection which originated the request.

Classes and Algorithms

AidlServer

The AidlServer class is the remote server object. There is one AidlServer object. The AidlServer object manages a set of server-side Client objects – one for each remote connection. The AidlServer object accepts initial connection requests from remote applications and creates a server-side Client object for each remote connection.

Client

The Client class represents the server-side of a single remote connection. There is one Client object within AidlServer for each remote Connection object and typically one remote Connection object for each connected application. The Client object contains its own IBinder object which it uses to receive requests from the remote Connection. Client also contains a set of QueueThreads. Client handles a request typically by creating the appropriate QueueTask and enqueueing the task into its shortest queue as appropriate.

The figure below illustrates the important objects contained in each Client. Each Client contains a number of QueueThreads, and this number is fixed at the time the DataServer creates the Client object. Each QueueThread contains a variable number of QueueTasks, and each QueueTask represents a single request from the remote app.

This arrangement lends itself to symmetric multiprocessing (SMP). It acts as a splitter, and on a multi-core hardware architecture the result will be that if the data store can handle concurrency then the heavier the load the apps place on the service the faster the performance. The set of Clients, each with its own set of blocking QueueThreads, each with its set of QueueTasks is a giant time-sink. (In the automobile cooling-system analogy this is the radiator. It’s a latency-radiator;-)

QueueThread

QueueThread extends Thread and implements a queue of tasks (QueueTask) in a single thread. QueueThread has methods to enqueue a task and to stop itself by enqueueing a special Runnable which tells its Looper to quit at the front of the queue. QueueThread uses Looper and Handler classes to implement its run method:

	@Override
	public void run() {
		try {
			Looper.prepare();
			synchronized(this){
				handler = new Handler();
				notifyAll();
			}
			Looper.loop();			
		} catch (Throwable t) {
			Log.w(TAG, "QueueThread throwable:", t);
		}
	}

	/**
	 * we override Thread's start method because the Looper sometimes
	 *  takes a little too long initializing, and we need to keep
	 *  start() from returning before the handler/looper is
	 *  fully set up
	 */
	@Override
	public void start(){
		super.start();
		synchronized(this){
			while(handler == null){
				try{
					wait();
				} catch (InterruptedException e) {
					Log.w(TAG, "QueueThread.start(): wait interrupted");
				}
			}
		}
		return;
	}

Once started the QueueThread enqueues a new task via its handler by calling handler.post(QueueTask). QueueThread blocks when the queue is empty. It’s also possible to empty QueueThread’s queue and cause it to quit the loop by calling its requestStop() method which posts a special Runnable that calls Looper.quit() at the front of the queue. If the remote Connection object disconnects while the Client is still processing requests in its QueueThread the Client uses requestStop() to clear and stop the queues. QueueThread wraps each of the tasks that it enqueues with an inner class called TaskWrapper. TaskWrapper holds the QueueThreadListener and also takes care of decrementing QueueThread’s task counter when the QueueTask completes. When QueueThread enqueues a QueueTask it increments its task counter.

QueueThread.TaskWrapper

TaskWrapper is implemented as QueueThread’s inner class. QueueThread creates a new TaskWrapper for each QueueTask that it enqueues. The TaskWrapper holds the QueueThreadListener and takes care of running the QueueTask and decrementing the task counter.

QueueTask

There really isn’t any QueueTask. Rather, QueueTask is an abstract class which represents a set of tasks, each one of which needs only to implement Runnable interface. QueueTask holds the Client’s AidlServerDataListener, the request-ID, and keeps track of whether the particular QueueTask is currently enqueued. It works in conjunction with QueueThread.TaskWrapper to keep track of the QueueThread’s task count. QueueTask objects are created by the Client object and enqueued by posting them to one of the Client’s QueueThreads. There is a separate QueueTask for each type of task the server can perform. There is an api-callback (declared in an AIDL file, AidlServerDataListener.aidl) for each of the types, or operations. This api-listener, AidlServerDataListener, is registered by a function call in the Client’s IBinder. The QueueTask contains the remote AidlServerDataListener which in turn contains the specific callback for the particular operation implemented in the QueueTask subclass’ run() method, and the QueueTask subclass’ implementation calls this listener to deliver results to the remote Connection when its run method completes.