Saturday, January 16, 2010

Oneway interfaces

In early betas, the Android IPC was strictly synchronous. This means that service invocations had to wait for the return value of the remote method to arrive back to the caller. This is generally an advantage because the caller can be sure that the called service received the invocation by the time the remote method returns. In some cases, however, this causes the caller to wait unnecessarily. If synchronicity is not required and the method has no return value, oneway AIDL interfaces may be used.

Oneway methods are specified by addind the oneway keyword to the AIDL interface definition.

oneway interface IncreaseCounter { void increaseCounter( int diff ); }

Only the entire interface can be oneway and these methods must all have void return value. The stub compiled from oneway AIDL interface does not have return path for remote methods on the service side and does not wait for the method to execute on the client side. The delivery is reliable (no invocations are lost) but the timing is not guaranteed, e.g. two sequential oneway invocations may arrive at the invoked service in different order. Oneway interfaces are therefore more complicated to use (they are also faster and don't block the caller) and are used extensively by the Android framework internally to deliver event notifications.

Click here to download the example program.

Update: In order to address a comment in the comment section, the project was adapted to the Android Studio tool chain. Click here to download the updated sources and read this blog post, how to import the project into Android Studio.


Our primitive example has an Activity and a Service. The service exposes two interfaces: one "normal" (Counter.aidl) and one oneway (increaseCounter.aidl). The interesting bit here is how one service can expose two interfaces. The onBind method checks the Intent used to bind the service and returns different binders based on the Intent. The important point here not to use the Intent extra Bundle to differentiate among interfaces. I did this and I can confirm that even though extras arrive at onBind (the API documentation states the contrary) but the framework gets completely confused and thinks that the service has already been bound with the same Intent (the framework seemingly cannot figure out that the Intent extras were different). In the example program I abused the category field therefore and this works nicely.

7 comments:

Anonymous said...

releaseService() isn't invoked in onDestory in the enclosed example... :-)

Gabor Paller said...

?
I am not aware of such a method in Android ...

michal said...

cool example thanks for sharing!

Elangovan Manickam said...

I know this thread is 7 years old now. But I just want to point few things about this blog.

First, the onBind() method on the service will receive the call only once in its lifetime. Even though multiple clients trying to bind to the same service, the android system just returns the cached IBinder object without calling the onBind method. So I couldn't understand how your 'If' condition in the onBind method will work. if you want them to work then you need to stop the service before binding again.

Second, the oneway keyword don't have any impact if the service and client are in same application or in same process. Your example looks like having all in one app. So the asynchronous behavior that you expect wont happen.

Gabor Paller said...

Elangovan, you are wrong when you say that in this case the service will be bound once. Even though it is the same service implementation (CounterService.java), it is bound by different Intents hence considered different services by the Android service framework. If you don't believe, try it yourself. I now updated the post with sources adapted to newer Android tool chains (in particular, the aexp.oneway package name crashed the 25.0 aidl tool because it thought that "oneway" in the package name is a keyword hence every package name has been renamed "one_way") and ran the example on my Nexus 5 (Android 6.0.1).

The service is bound twice:
09-27 00:24:44.701 25653 25653 D ONEWAYACTIVITY: bindService(): IncreaseCounter
09-27 00:24:44.702 25653 25653 D ONEWAYACTIVITY: bindService(): Counter

and onBind is called twice:
09-27 00:24:44.710 25653 25653 D COUNTERSERVICE: onCreate
09-27 00:24:44.710 25653 25653 D COUNTERSERVICE: onBind: Intent { cat=[android.intent.category.aexp.one_way.if.increasecounter] cmp=aexp.one_way/.CounterService }
09-27 00:24:44.710 25653 25653 D COUNTERSERVICE: category: android.intent.category.aexp.one_way.if.increasecounter
09-27 00:24:44.711 25653 25653 D COUNTERSERVICE: onBind: Intent { cat=[android.intent.category.aexp.one_way.if.counter] cmp=aexp.one_way/.CounterService }
09-27 00:24:44.711 25653 25653 D COUNTERSERVICE: category: android.intent.category.aexp.one_way.if.counter
09-27 00:24:44.726 25653 25653 D ONEWAYACTIVITY: IncreaseCounterServiceConnection::onServiceConnected
09-27 00:24:44.727 25653 25653 D ONEWAYACTIVITY: CounterServiceConnection::onServiceConnected

Please, observe that even though the service class is the same (aexp.one_way.CounterService), the Android service framework handles these two services separately. As they are indeed separate services.

Elangovan Manickam said...

Hi Gabor,

That's interesting. In my experience I saw the onBind method called only once for a service lifetime even though multiple clients binds to the same service. it is different story if the service is destroyed and recreated again.

Could you please provide your feedback on the following explanation from google's android developer's site.

https://developer.android.com/guide/components/bound-services.html#Basics

"You can connect multiple clients to a service simultaneously. However, the system caches the IBinder service communication channel. In other words, the system calls the service's onBind() method to generate the IBinder only when the first client binds. The system then delivers that same IBinder to all additional clients that bind to that same service, without calling onBind() again."

May be we are not talking about the same thing?

Gabor Paller said...

Elangovan, as I wrote the onBind *is* called once for the same Intent. Intent.filterEquals defines, when two intents are the same. It says:
Determine if two intents are the same for the purposes of intent resolution (filtering). That is, if their action, data, type, class, and categories are the same. This does not compare any extra data included in the intents.

So filterEquals does not consider the extras but if other fields are different, they are different Intents. In our case, the category was different. As filterEquals does not match, the machinery behind the IBinder caching does not consider the two bindService requests connecting to the same service, even though the invocation eventually ends up in the same Java implementation (CounterService.java).