Friday, July 9, 2010

Direct service invocation considered harmful

I have a more than 2 years old - and quite popular - blog post about service binding and I thought I knew enough of Android services. Then I ran into a service binding pattern that I was not aware of before. I would like to clarify before I start that every alternative in this post "works" - meaning it does what it is intended to do on the Android versions released so far. Still, it seems that this hack I am going to present got into tutorials and books. I think there is a risk here that a good number of coders think about it as the right thing to do and not as a hack.

Click here to download the example program.

This is a version of the good old DualService presented in this blog entry. The main difference that the AIDL file has been removed and instead you will find this strange construct in DualService.java:

public class DualServiceBinder extends Binder implements ICounterService {
public int getCounterValue() {
return counter;
}
}

and in onBind:

public IBinder onBind( Intent intent ) {
return dualServiceBinder;
}

Now this DualServiceBinder "Binder derivative" is a strange thing. Binders are serialization-deserialization constructs that turn RPC invocations into byte streams and back. That byte stream can be sent through the Binder kernel driver and that's how interprocess communication works in Android. Binder derivatives are normally generated by the aidl tool. You can do it by hand but it is not for the faint of heart. Basically you have to implement transact/onTransact methods and serialize/deserialize your data by hand. If you follow my advice, you leave it to the aidl tool.

DualServiceBinder does none of it. It implements a plain Java interface (ICounterService) and exposes a plain Java method. On the other side, in DualServiceClient it justs casts the IBinder to ICounterService in the onServiceConnected method and invokes the method on it.

As you might have guessed, this is a hack. The author sidestepped entirely the Android IPC mechanism and abused two properties of the Android application model.

  • By default, the Android application manager loads activities/services/providers into the Dalvik VM running in the same Linux process.
  • Classes from the same DEX file (there is one DEX file per APK) are loaded by the same class loader.
These guys here do not use Binder at all. They need an IBinder implementation because of the onBind/onServiceConnected methods' contracts and Binder comes handy as it is an implementation of the IBinder interface. Other than that, the DualServiceBinder instance is just passed from the Service to the Activity without any IPC taking place. That is why it can be recasted to a simple Java interface and the method can be invoked without any IPC mechanism. The hack is actually faster than the AIDL-based implementation (as there is no serialization/deserialization, just in-memory parameter passing) but this approach is not supported in any way by the Android API contracts.

Do you think I found the hack in a shady, unsupported freeware? No, I found it in a book (actually, my attention was turned to this book by a fellow Android programmer). The book proposes another trick: the "service interface" of their Binder descendant exposes just a getService() method that returns a reference to the Service instance and later the Activity invokes public methods of the service directly, circumventing even their own tricky service interface.

I definitely think it is a bad idea to propagate this hack even though it works on the existing Android implementations and is somewhat faster than the regular, AIDL-based invocation.

9 comments:

Mark Murphy said...

Considering that this is the local binding pattern endorsed by the core Android team and demonstrated in their SDK samples, it seems odd that you consider this to be a "hack".

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html

ydant said...

Thanks for that, Mark. I thought I'd seen this mentioned in the API docs. Is there actual published statement that this is a valid pattern? Perhaps some assurance that the non-process separation will not be abandoned in the future?

Anonymous said...

I agree with Mark. Gabor, you are thinking about Services only in the remote setup. If used locally, the extensive aidl mechanism is not needed.

karni said...

Indeed, it is valid. As long as the Service runs in the same process as the application. If you define android:process="some_tag" then the process will run in a separate thread and then you have to use AIDL for IPC. At least, that's how I understood the docs.

whitemice said...

AIDL is an unnecessary overhead as *by default* all your components run in the same process. This comes as a shock to many Android developers, which is why I put some code examples together for this presentation:

Architecture your Android Application
http://tinyurl.com/34uy9nz

You now only have to worry about if your component is still there when you try and communicate with it. ;-)

karni said...

@whitemice You write in your presentation *do not use* . Well, *by default* doesn't mean it's the case. How do you perform binding to a (local??) service from 3 different applications? Let's say it's an RSS feed fetcher of some sort.

whitemice said...

@karni - In the spoken version of the presentation, I said there is no need to use RPC to talk to different components inside your own application.

"Do not use" is meant in the context that single APK distribution is the norm, which allowed me to direct the minority of exceptions towards an AIDL specific presentation (see slide 19) that was going on later that day. ;-)

I can name three occurrences where I've met teams using AIDL inside their own apps, suffered performance problems, and argued that direct communication is impossible.

Francois said...

Hi,
I'd like to know if there is a way to discover public interface of an existing service (without having the source code).
I already made some AIDL for stock music service, but I'd like to create other non open source music player (Samsung, Sony ...).
Is there a way to do it ?
Thanks,
Francois

sulthan said...

Hi Francois, I was thinking about the same problem. I can call music service in google and htc droids because the aidl is known, but how to call service methods on samsung?
I can't test it, but I have the following idea:

1/ first, you have to know the package and class name for the service (otherwise you cannot call it). That should be easily accessible from the list of running services, if you have the device.

2/ you get IBinder instance
3/ call getInterfaceDescriptor()
4/ get IInterface instance by passing descriptor to queryLocalInterface

When you have IInterface instance, you would usually cast it to the interface created from aidl but I think you can also use reflection (getClass().getMethods() etc.) to access all public methods and call them.

I think that should work.