Archive

Archive for July, 2012

Using Android IPC binders from native code

July 7th, 2012 9 comments

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:

  1. adb logcat -v time binder_demo:* *:S
  2. adb shell binder
  3. 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.

Categories: Uncategorized Tags: