Click here for the cross-linked version of this entry
I wanted to go back to my Bluetooth exercise once again but then I stumbled into this beautiful sample program from John Guilfoyle. I decided immediately that before playing again with that wicked Bluetooth service, I have to test-drive again the service framework and the low-level mechanisms behind it.
My Android experiences with services were so far restricted to repeating the sample program in the documentation. This time I wanted to try something more ambitious: connecting two applications through services. In order to do that, it is worth getting behind the facade of the Android service framework. This thread contains a lot of information that I try to recap here.
The workhorse of the Android IPC is the "IBinder class". As its name implies, android.os.IBinder is really an interface, implemented by the real worker objects, namely android.os.BinderNative and android.os.BinderProxy. The binder provides a simple functionality of synchronous method invocation. Calling the transact() method on the binder is able to transmit a function code, a java.os.Parcel instance and some flags. The result is returned in another Parcel instance. Look for this method signature:
The client calls the transact method and the other side of the binder, in the other side of the process separation will receive a callback.
The IPC has a thread pool for each process and the onTransact method is invoked in the context of a thread in the IPC thread pool. Parcel is the abstraction of the data the IPC framework is able to serialize and deserialize, you can look at the documentation of the android.os.Parcel object to see, what data types are supported. The most interesting data type is the IBinder itself (readStrongBinder, writeStrongBinder) so it is possible to pass binder endpoints to another processes. The interface mechanism that the aidl tool provides is really simple after that. Aidl takes the interface description and turns it into Java code that serializes and deserializes invocation parameters and return values to and from Parcels. Take for example our trusty IAdderService which has a method with this signature:
Aidl has to generate a code that serializes two integers into the request Parcel and deserializes the return integer from the response Parcel in transact() because this is the client side of the stub. In onTransact method, as it is the receiver side of the call, it has to deserialize the input parameters, invoke the service-side implementation of the method then serialize the result. And it does exactly that (excerpts from the IAdderService.java generated by the aidl tool):
The service framework does not provide any means to find out, what is the serialization and deserialization stub on the client and the service side which I consider a drawback. This is a reason why it is impossible to discover the service interfaces (one can retrieve the client-side binder but not the interface stub, therefore it is impossible to know, how to talk to the service without knowing the interface stub from some other, independent source). The framework simply assumes that the client- and the server-side stubs match, if not, then it is the programmer's fault.
I was now ready to realize my dream to connect two applications by means of service invocations. The best way is, of course, Intent-based invocation but I wanted to work that around just for the sake of hacking. As Dianne Hackbod pointed out, however, Android application lifecycle makes the direct connection impossible. Depending on resource situation, Android apps just come and go and therefore the binders connecting them may stop functioning without any warning. One has to go through services because service dependencies are respected by the application manager. So I created the following simple application (actually, two applications and one service).
You can download the example program from here
The task is to receive a callback from Application 2 (called SClientApp) by Application 1 (called HostappClient). The connection between the two applications goes through a service (HostappServiceImpl, co-located with HostappClient). Both applications bind this service in a usual way. In addition to that, HostappClient provides the HostappServiceImpl with a callback binder. Why binder, why not interface? It could be interface and it would be more elegant to do so. The Windows version of the aidl tool in the current version (m3-rc37a) has an annoying bug that makes the import statement almost unusable so there is no way to import our own interface classes. This is not a problem, however, if we know the relationship of binders and interfaces. We just pass the binder reference and apply the interface stubs onto it when the binder arrives. The call flow is the following:
- HostappClient binds HostappServiceImpl. When the service binding callback arrives, we pass the binder of HostAppClient's ISetTextService to HostappServiceImpl.
- Now HostappClient launches SClientApp as subActivity. The first thing SClientApp does is to bind HostAppServiceImpl. When the service is bound succesfully, SClientApp passes the hugely valuable text result to HostAppServiceImpl which in turn calls HostappClient's ISetTextService whose binder we have received previously.
- One interesting bit remained: the use of android.os.Handler in HostappClient. When HostappClient receives the service callback, it cannot directly manipulate the UI, only the UI thread is allowed to do that. The service callback runs in an IPC thread, not the UI thread, therefore it uses a Handler instance to pass a Runnable to the UI thread which is then executed there (the Handler executes the Runnable in the context of the thread it was created in and our Handler instance was created in the context of the UI thread).
Huh, pretty complicated instead of returning that hugely important string in the intent invocation bundle? And that is not the end, because our example program is not perfect. It is not prepared for low-memory situations when each of our three players may be destroyed and resurrected by the application manager. The worst-case scenario is this:
- HostappClient is launched and when it binds HostappServiceImpl, these two entities are in the memory.
- When HostappClient makes an intent invocation to SClientApp, both HostappClient and HostappService may be destroyed by the application manager.
- SClientApp now binds HostappServiceImpl and these two are in the memory now. The IBinder instance passed by HostappClient is now lost.
- When SClientApp finishes, SClientApp and HostappServiceImpl may both be destroyed again. Our hugely valuable text data passed by SClientApp to HostappServiceImpl is lost then.
- HostappClient is resurrected but it has nothing to receive from HostappServiceImpl.
A robust implementation of HostappServiceImpl would store setMyText() invocations in a persistent storage, e.g. SQLite database if the callback to HostappClient fails (DeadObjectException or appService==null in HostappServiceImpl.setMyText). When the binder is set by HostappClient's invocation to setCallback(), HostappServiceImpl would check its storage and would immediately send back setMyText() callbacks to HostappClient from the persistent storage. This exercise is left, however, to the interested reader.