2010-06-06

AIDL callbacks in Android

There are several blog posts on the internet about doing this stuff so you probably can find better descriptions elsewhere. Probably by people that actually knows Android coding better than I do but anyway, this is my first attempt at hacking Android. You can find the source here: http://code.google.com/p/tretris/
So for our first Android app we (that is me and my pals) wanted to make a multiplayer Tetris-clone (I'll probably incur the wrath of the Tetris Company by even writing this blog). After a while we finally figured out that we wanted the game logic implemented in a Service that could run in the background on one device and push updates to game clients on the network and of course display something nice on the device hosting the service.
So, the use of XMPP and and the Smack library for network communication is food for another blog post but to put it simply, smack zooms.

Anyway, implementing a Service in Android isn't all that hard. You extend the android.app.Service class and do interesting stuff in onCreate like constructing a Timer.
You start the service from your activity by calling (you guessed it) startService.

Now we have a running service and there are a few ways to talk to a running service, using intents and messages, using the producer/consumer but I wanted to try my hand on method invocation. The nice thing about a service is that it is running in it's own Linux process but this makes it impossible to simply call methods on it. There is an RPC-mechanism in Android invented to handle this and it isn't as hard as writing WSDL/SOAP-stuff but there are some hoops to jump through.

First off: The interface description. You have to create a description of your RPC methods in Android Interface Description Language (AIDL). A poorly documented and strange language that looks a lot like java interfaces. To be fair, the documentation is brief but as far as I can tell correct and it gets you started.

Since I was using the android plug-in for eclipse it happily let me place the aidl-file amongst the java source and generated the stub classes automatically. So if you create a MyService.aidl with all the right package and import and wierd 'in' modifiers here and there you end up with a MyService java interface generated.

Second: we have a description and the tools works. Now, how do you get a GUI-activity to find the service? Well, you bind to it using intents (like so much else in Android). This is where the lack of closures in java (eerr, dalvik... whatever) becomes painful. Binding to a service is an asynchronous thing, so android makes a callback when the service is bound and running. This is a good thing but makes for messy code.
This is basically what we do:
bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE);
The serviceIntent is the usual kind of Intent object (in our case naming the implementation class directly). The serviceConnection is an instance of a class implementing the ServiceConnection interface (see it is involved just saying it). The BIND_AUTO_CREATE constant tells Android to create and run the service if it isn't already running.
So we end up with something like:
ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName name) {
...
}

public void onServiceConnected(ComponentName name, IBinder service) {
MyService serviceInstance = MyService.Stub.asInterface(service);
...
}
};
Intent serviceIntent = new Intent(this, XXXService.class);
bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE);

Wait a minute, what is that Stub doing there? Well, this is just one of those conventions the Android developers put there, it reminds me of the javax.rmi.PortableRemoteObject.narrow and you can think of it in those terms, as a strange kind of cast from generated stub code to what you are really after.
This is pretty sweet, some client cruft and we finally get the methods we expose on the service. Or at least a stub object that can call methods on the service.

Third: How to expose methods on the service. The service must implement the public IBinder onBind(Intent intent) and return an IBinder instance but not only that, an IBinder that can be "narrowed" to the generated interface. The place to find such a class is again the Stub-class on the generated MyService interface. What you do is that you create a sub-class of the Stub-class and return an instance of the sub-class (get the 't' right here). Like this in your service:

private MyService.Stub idlStub = new MyService.Stub() {
public boolean myMethod() throws RemoteException {
...
}
}
So the service has an anonymous subclass of the Stub as a member instance. This anonymous class implements all the methods in your RPC-interface. The anonymous instance also happens to be an IBinder. So what you return from onBind is simpy the idlStub instance.

To recap. The client binds to the service and gets an IBinder back, which it can "narrow" to the interface described in the aidl-file. The service implements a subclass to the generated Stub-class in the interface and returns that subclas for onBind. Easy as 1,2,3, well, actually my head is spinning already :-)

Callbacks: So now we have a GUI-client that can call methods on the service. So let us make the service call back into the GUI-client.
To do that we first define a call back interface, so create a MyCallback.aidl and define the methods that the service should be able to call on the clients. Then add a method on the service for registering clients, the nice thing the Android team did was to make the RPC symmetric, which means that you can use aidl-created interfaces as types for method parameters.
Great, now we have an interface called MyCallbak and a service method called something like registerClient(MyCallback client).
The service side is really easy to implement, there is no Stub voodoo at all, you get an invocation for register client, the method argument is already marshalled and ready to use.
The client side, the one that is to receive the callbacks has some more work to do. It must do the same thing with the Stub-class we did on the service side:

private MyCallback.Stub callback = new MyCallback.Stub() {
...
}

That callback member can then be used as argument to the registerClient(callback);

Conclusion: So does the RPC mechanism in android zoom. Well, it works and is reasonably easy to use but until I see some annotations on interfaces and more automagic I wouldn't say it zooms.

2 comments:

  1. I agree about the annotations. Why on Earth would anyone design an IDL for Java now that we have annotations? Sure, Android was started around October '03, and Java 5.0 was released in '04, but at least, it should have been fixed before the first public release.

    ReplyDelete
  2. I would take the opportunity to thank all the bots posting nice comments including links to unrelated commercial sites. It is nice to see that the machine community appreciates my blog and 7 years old posts. However, I do think that I'll delete your posts anyway.

    ReplyDelete

Note: only a member of this blog may post a comment.