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:
public final boolean transact(int code, Parcel data, Parcel reply, int flags ) throws DeadObjectException;
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.
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags);
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:
int add( in int i1, in int i2 );
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):
public int add(int i1, int i2) throws android.os.DeadObjectException { android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInt(i1);
_data.writeInt(i2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_result = _reply.readInt();
}
...
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) {
try {
switch (code)
{
case TRANSACTION_add:
{ int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeInt(_result); return true;
}
}
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.
5 comments:
Hi Paller,
I ported this application in 0.9 sdk, & the Hostapp is working fine but when i am running SClientApp it is throwing security exception on bindService method & the documentation say bindService function will throw SecurityException if you do not have permission to bind to the given service.
I gave uses-permission android.permission.INTERNET but it is not working.
Do i require to give any other permission(s) in the AndroidManifest.xml file to bind this service ?
Please provide your suggestion on this.
/************************************************/
09-22 16:23:48.839: WARN/ActivityManager(52): Permission Denial: Accessing service ComponentInfo{aexp.hostapp/aexp.hostapp.HostappServiceImpl} from pid=522, uid=10047 requires null
09-22 16:23:51.417: WARN/System.err(522): java.lang.SecurityException: Not allowed to bind to service Intent { comp={aexp.hostapp/aexp.hostapp.HostappServiceImpl} }
09-22 16:23:51.458: WARN/System.err(522): at android.app.ApplicationContext.bindService(ApplicationContext.java:787)
09-22 16:23:51.468: WARN/System.err(522): at android.content.ContextWrapper.bindService(ContextWrapper.java:307)
09-22 16:23:51.487: WARN/System.err(522): at aexp.sclientapp.SClientApp.initService(SClientApp.java:39)
09-22 16:23:51.497: WARN/System.err(522): at aexp.sclientapp.SClientApp.onCreate(SClientApp.java:23)
09-22 16:23:51.517: WARN/System.err(522): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1121)
09-22 16:23:51.527: WARN/System.err(522): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2094)
09-22 16:23:51.548: WARN/System.err(522): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2147)
09-22 16:23:51.559: WARN/System.err(522): at android.app.ActivityThread.access$1800(ActivityThread.java:112)
09-22 16:23:51.587: WARN/System.err(522): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1572)
09-22 16:23:51.597: WARN/System.err(522): at android.os.Handler.dispatchMessage(Handler.java:88)
09-22 16:23:51.617: WARN/System.err(522): at android.os.Looper.loop(Looper.java:123)
09-22 16:23:51.637: WARN/System.err(522): at android.app.ActivityThread.main(ActivityThread.java:3708)
09-22 16:23:51.649: WARN/System.err(522): at java.lang.reflect.Method.invokeNative(Native Method)
09-22 16:23:51.667: WARN/System.err(522): at java.lang.reflect.Method.invoke(Method.java:492)
09-22 16:23:51.689: WARN/System.err(522): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:734)
09-22 16:23:51.697: WARN/System.err(522): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:492)
09-22 16:23:51.718: WARN/System.err(522): at dalvik.system.NativeStart.main(Native Method)
/************************************************/
Thanks
Ashutosh
Have you exported the service (android:exported="true" in the service tag).
Thanx Gabor... It worked for me
How could you make the things so easy....i have spent 2 days for this but I still have doubts regarding the functionalities of IPC binder when dealing with low level code flow....TRANSACTIN_WRITE_READ....Different ioctl commands and corresponding data structures
binder: release proc 1067, transaction 1798, not freed
I get the above exception after around4 mins.. can anybody tell me why does this message come up ans what are its implications.
Post a Comment