Most contrived Rx examples show you how to replace AsyncTask with Rx. Not many examples show how to abstract away complex async scenarios, such as oneway AIDL Bound Services. Using RxJava with AIDL Services will help us clean up the interface to consume the AIDL service – making it less … gross and easier to work with.
Assume you want to connect to a Bound Service which is
created through AIDL to get some data. Let’s also assume that all the calls on that bound service are oneway calls (async with a callback listener). In order to get the data you need you will have to set up a ServiceConnection then once connected you need to then use the service object to request the data. Since the connected service (AIDL Service) you’re working with only has oneway calls you basically have two layers of indirection you have to deal with before you can get at the data. For example – Suppose we pass in a orderId
and we get an Order object back from the bound service … we have to wait for two async calls to complete in order for that to happen:
- Connecting the Activity to the bound service via the
ServiceConnection
- Making the async call on the service after connection, passing in a callback listener to be called when the data is returned.
Currently anytime I need data I have to connect to a service, wait … query the service, wait … and then get a callback. If I need to make multiples of these types of calls things can get hairy, fast.
The goal here is to wrap this code up somewhere and abstract away the details and simply have an interface that returns Observable<Order> getOrder(long orderId);
Here’s how I’ve gone about implementing this … (full gist).
In the constructor I connect to the bound service with the bindService call. This is async. Once connected in the ServiceConnection I call onNext on the BehaviorSubject that holds the reference to the boundService. Since no one is listening yet, no big deal. That class just hangs outs and waits.
When an interested party subscribes to the getOder call, we return:
PublishSubject<Order>.asObservable()
This way the caller doesn’t know they’re working with a subject (which is recommended).
In that method I subscribe to the orderServiceSubject. If the service connection is still in flight, no big deal – Rx will allow us to wait until onNext on the orderServiceSubject is called. If the service connection has succeeded then the subscriber to orderServiceSubject will get it’s on next called with the bound service as its parameter.
Once we have reached this point, we have a bound service object that we can call methods on. Remember, these methods are set as ‘oneway’, meaning that they are also async and we must supply a callback listener that gets called once the work is complete (at this point it is starting to resemble JavaScript all of a sudden, like whoa).
Inside of this listener (the anonymous Stub class) we use the PublishSubject – orderSubject and call its onNext with the value that was returned. This will propagate the value all the way back to the activity and then we can do whatever we want with it.
As you can see, we can abstract away the nasty AIDL code behind Rx and make it a lot cleaner. Now the client can use RxJava and get the the benefits of Rx without dealing with the ugly guts of the AIDL system.
Special thanks to Dan Lew for proof reading this article.
Here’s the full implementation with an example of how you’d implement this with an Activity.
Dave Smith says
Wow, that’s an interesting use case for Rx I wouldn’t have imagined. I have a couple of questions as going cross-process with AIDL presents some unexpected challenges. I know nothing of RxJava or how it internally handles observables, so bear with me as these questions might seem silly:
1) Binder references are treated as GC roots that the application has no control over, so passing lots of them “over the wall” results in lots of opportunities for leaks. How does the reference chain extend from
IBoundOrderServiceListener.Stub
to theSubscription
owned by the activity?2) This has to do with schedulers. Binder callbacks happen on binder threads, which ought to remain as free as possible. Is it possible for
onNext()
to block inside ofonResponse()
(waiting for a subscriber or something) which might cause that thread to stay tied up?Donn Felker says
Hi Dave, I’m supper glad you chimed in (was hoping to talk to you about this actually). I’d love to work out any issues you see with this because it is very possible that I may be doing something incorrectly and not know it.
1) I tend to inject the RxOrderService interface, and its context is usually the ApplicationContext that I pass in through the CTOR. Therefore, it would be alive for the application and would not be bound to the Activity. That’s hard to demonstrate and I did not do that in this article, but that’s how I do it. Do you see a problem with that? Since its bound to the application it seems I should be able to avoid the Activity leak/etc. Thoughts?
2) We are calling onNext on the order subject, at this point (in onResponse) we’re still on the io Scheduler (Schedulers.io). When onNext is called Rx hops in and handles the threading work to deliver it to the Android Main thread. I feel this should leave the onResponse free and clear to do its job. Do you see something I’m not seeing or know something I may not? If so, please chime in as I’d love to improve this if possible.
Again, any feedback would be great. I have a lot of friends who know Rx but very few who work with AIDL a lot like I’m having to do at my current client. Thanks again for the post and the questions.
m1shk4 says
Great example of non-trivial rx usage!
But I don’t get two things(sorry if these are obvious):
1. Why AutoCloseable is used?
2. Where does context in close() implementation come from?
二ツ岩 says
It also works with non-AIDL services. And with Kotlin it’s possible to implement small wrapper to improve readability and simplify methods: https://gist.github.com/fuwaneko/e17fa7708ca43afe609f
Sylvain R. says
Hi Donn,
your sample is really interesting. I have a really close architecture but the difference is on the callback of remote service.
In my cas, I register a callback object as observer of the remote service.So I can’t figure out how to build my rx operations to make first the call to the remote service, and then wait for a callback on my observer.
Have you any idea on how to do it ?
I asked for help on stackoverflow, referencing you article :
http://stackoverflow.com/questions/33738558/rxandroid-with-remote-service-aidl
Lukasz Piliszczuk says
Hi Donn,
This is an interesting article.
I recently made a reactive wrapper around AIDL services for Android in app billing.
I used a different approach for implementing the reactive wrapper, inspired by the implementation of the Reactive Location library. Basically the difference is that I’m opening/closing a new service for every request, rather than having a “singleton” shared service instance.
You can find the implementation here: https://github.com/lukaspili/Android-Reactive-Billing/blob/master/library/src/main/java/com/github/lukaspili/reactivebilling/observable/BaseObservable.java
My problem with the AIDL service binding is about the thread on which the service connection callback is executed. In my tests, it always happen on the main thread, regardless of the thread that executes bindService().
And this is the case for both my implementation and yours. Because of this, the Rx schedulers are ignored.
My current fix is using semaphore to block the originating thread until the service is acquired. While I didn’t find any problems with that solution, I hope there is a cleaner alternative.
Any thoughts?
Thanks.
Lukasz
David Cowden says
FWIW the bound service plumbing is not really meant to be used that way. You should bind to a service for the lifetime of whatever thing might need it and unbind once the consumer no longer needs it. Binding is relatively expensive especially when actual ipc is involved. It’s not about singletons. It’s about scoping your resource access appropriately.
Lukasz Piliszczuk says
Hello David,
I would say that most of the times, the scoping which happens inside the observable is adequate. Connect to the service, fetch products or buy a product, then close the service. Feels more adequate than opening the service in activity start and stopping in activity stop, regardless if the user will use the billing service or not.