Using Android IPC binders from native code
This is a follow-up (with actual code examples) to a post I wrote a while ago on how to use the Android IPC system from native C++ code. The code is hosted on GitHub at Android IPC binder demo. A number of readers have asked for sample code after reading the previous post, so hopefully this helps.
When executed without any arguments, the binary acts as a service (named “Demo”). When executed with an integer argument (ex: “binder 743”), the binary acts as a client that searches for the “Demo” service, binds to it, and exercises its API. To keep things simple, there’s virtually no error checking. Some debug messages are sent to stdout, and others to logcat.
The suggested way to run this demo to have 3 windows open and issue the following commands in them:
- adb logcat -v time binder_demo:* *:S
- adb shell binder
- adb shell binder 456
Now for a brief explanation of the code. The IInterface, BpInterface, and BnInterface classes are provided by the Android framework.
We start by defining an interface (think AIDL) that will be shared between the service and the client:
class IDemo : public IInterface { public: enum { ALERT = IBinder::FIRST_CALL_TRANSACTION, PUSH, ADD }; // Sends a user-provided value to the service virtual void push(int32_t data) = 0; // Sends a fixed alert string to the service virtual void alert() = 0; // Requests the service to perform an addition and return the result virtual int32_t add(int32_t v1, int32_t v2) = 0; DECLARE_META_INTERFACE(Demo); }; // This implementation macro would normally go in a cpp file IMPLEMENT_META_INTERFACE(Demo, "Demo");
Next we define the server end, which is made up of 2 classes: BnDemo, and its derived class, Demo. BnDemo extracts the arguments from the data Parcel sent by the client, calls the appropriate virtual function (implemented in the Demo class) to do the heavy-lifting, and packs the returned values (if any) into a reply Parcel to be sent back to the client.
class BnDemo : public BnInterface<IDemo> { virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); }; status_t BnDemo::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { data.checkInterface(this); switch(code) { case ALERT: { alert(); return NO_ERROR; } break; case PUSH: { int32_t inData = data.readInt32(); push(inData); return NO_ERROR; } break; case ADD: { int32_t inV1 = data.readInt32(); int32_t inV2 = data.readInt32(); int32_t sum = add(inV1, inV2); reply->writeInt32(sum); return NO_ERROR; } break; default: return BBinder::onTransact(code, data, reply, flags); } }
This is the Demo class, which would normally do the real work on the service side of the binder.
class Demo : public BnDemo { virtual void push(int32_t data) { // Do something with the data the client pushed } virtual void alert() { // Handle the alert } virtual int32_t add(int32_t v1, int32_t v2) { return v1 + v2; } };
Now we define a service proxy, to be used on the client side. Notice again that any data the client needs to send to the service is packed in a Parcel and results (if any) are also returned in a Parcel.
class BpDemo : public BpInterface<IDemo> { public: BpDemo(const sp<IBinder>& impl) : BpInterface<IDemo>(impl) { } virtual void push(int32_t push_data) { Parcel data, reply; data.writeInterfaceToken(IDemo::getInterfaceDescriptor()); data.writeInt32(push_data); remote()->transact(PUSH, data, &reply); } virtual void alert() { Parcel data, reply; data.writeInterfaceToken(IDemo::getInterfaceDescriptor()); remote()->transact(ALERT, data, &reply, IBinder::FLAG_ONEWAY); } virtual int32_t add(int32_t v1, int32_t v2) { Parcel data, reply; data.writeInterfaceToken(IDemo::getInterfaceDescriptor()); data.writeInt32(v1); data.writeInt32(v2); remote()->transact(ADD, data, &reply); int32_t res; status_t status = reply.readInt32(&res); return res; } };
Finally, we start the service as follows:
defaultServiceManager()->addService(String16("Demo"), new Demo()); android::ProcessState::self()->startThreadPool();
And the client can now connect to the service and call some of the provided functions:
sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->getService(String16("Demo")); sp<IDemo> demo = interface_cast<IDemo>(binder); demo->alert(); demo->push(65); int32_t sum = demo->add(453, 827);
There’s a lot more that could be said, but I’m not planning on writing the book on the subject. If you’ve made it this far, you should be able to figure out the rest. The full compilable code is at BinderDemo.
Hey, I got BinderDemo working. Fan-tas-tic. But I need to run the server instance as root. Otherwise the client will not get a binder instance. Is this correct? Thanks,
Timur
hi,
may I ask how to compile this code?
I got NDK, but still missing some library, I think this belong to framework.
So I got framework code, but how to set environment to compile in command line?
Thanks.
HI,
I’m trying to create a native service on Android, which is similar to your demo. But when I try to compile the codes, it seems that there are some libs missing. Could you give out some details on how you compile the codes?
Make sure you have the Android NDK properly installed, and that you can build one of the sample NDK applications first, before attempting to build BinderDemo.
Can I have the service written on native side and Java client use this service?
You should be able to.
Can I have the service written on Java side and let a native client to use it?
There are couple minor modifications required to make it work on latest KitKat:
– LOGD macro is now ALOGD
– text output is now under #include , not in utils
– frameworks/native/include has to be added to LOCAL_C_INCLUDES
Thank you for the great example!
Thank you for your wonderful explanation. I was very much in in need of the binder framework in android NDK. helped me a lot 🙂