Archive

Posts Tagged ‘android’

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:

The Android IPC system

January 3rd, 2011 10 comments

The information below comes from a number of sources, including my own experiments with the Android IPC and some disparate internet sources.

The overall architecture of the Android IPC system is shown in the diagram below. It consists of four major blocks; one in kernel space, and the other three in user space. The dashed lines represent the logical RPC calls. The solid lines represent the actual data flow.

  • BinderDriver: This is the core of IPC system. It passes data between a ServiceProvider(s) and a ServiceUser(s). This kernel component is provided by Android.
  • ServiceProvider: Provides some kind of service. It parses the received RPC data from the BinderDriver and does the real work. Application developers will either make use of existing service providers (such as the Camera or AudioFlinger), or in some cases will write their own.
  • ServiceManager: This is a special singleton ServiceProvider that provides service manager services for other service providers. This component is provided by Android.
  • ServiceUser: This is the client. It remote calls the ServiceProvider by generating an RPC and sending it to the BinderDriver. Application developers typically write their own ServiceUser as part of their application.

Here is a typical flow of events for a fictitious MultServiceProvider (a service provider that multiplies two numbers for a client) and a MultServiceUser client which doesn’t know how to do multiplication (maybe because the numbers are quaternions) and needs to use the MultServiceProvider:

  1. ServiceManager runs first (at power-up) and registers a special node (node O) with the BinderDriver.
  2. The MultServiceProvider gets an IServiceManager proxy object for the special node O by calling the global defaultServiceManager() function.
  3. The MultServiceProvider then calls defaultServiceManager()->addService("Multiplier", new MultServiceProvider()) to add itself as a service provider and then waits in an infinite loop for someone to request its services. The addService RPC call is routed to the ServiceManager through the BinderDriver.
  4. The BinderDriver notices that the RPC is for the ServiceManager to add a new service, so besides routing the RPC to the ServiceManager it generates another node (let’s call it node M), for the new MultServiceProvider.
  5. The ServiceManager reads the data from the BinderDriver and processes the IServiceManager::addService RPC call.
  6. The MultServiceUser client process gets an IServiceManager proxy object for the special node O (again by using defaultServiceManager()).
  7. The client does an IServiceManager::getService("Multiplier") RPC call to get the MultServiceProvider. This call is routed to the ServiceManager through the BinderDriver.
  8. The ServiceManager reads the RPC data from the BinderDriver, processes the IServiceManager::getService request and returns back the node representing the MultServiceProvider.
  9. MultServiceUser calls MultServiceProvider::multiply(a, b). This call is routed to  the MultServiceProvider by the BinderDriver.
  10. The MultServiceProvider handles the MultServiceProvider::multiply RPC call and sends the product of the 2 numbers in a reply to the BinderDriver.
  11. The BinderDriver routes the reply back to the client.
  12. The client reads the data from the BinderDriver which contains the result of “a * b”.

In a future post I hope to discuss the whole architecture in more detail, with concrete code examples for how to use IBinder, IInterface, BBinder, BpInterface, BnInterface, etc… to create a ServiceProvider and a ServiceUser all in native C++ code on Android.

Categories: Uncategorized Tags: