首页 > 代码库 > Binder 通讯数据流概览

Binder 通讯数据流概览


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.

  

This is a follow-up (with actual code examples) to a post I wrote a while ago on how to use theAndroid 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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
classIDemo : publicIInterface {
    public:
        enum{
            ALERT = IBinder::FIRST_CALL_TRANSACTION,
            PUSH,
            ADD
        };
        // Sends a user-provided value to the service
        virtualvoid        push(int32_t data)          = 0;
        // Sends a fixed alert string to the service
        virtualvoid        alert()                     = 0;
        // Requests the service to perform an addition and return the result
        virtualint32_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.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
classBnDemo : publicBnInterface<IDemo> {
    virtualstatus_t onTransact(uint32_t code, constParcel& 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) {
        caseALERT: {
            alert();
            returnNO_ERROR;
        }break;
        casePUSH: {
            int32_t inData = http://www.mamicode.com/data.readInt32();
            push(inData);
            returnNO_ERROR;
        }break;
        caseADD: {
            int32_t inV1 = data.readInt32();
            int32_t inV2 = data.readInt32();
            int32_t sum = add(inV1, inV2);
            reply->writeInt32(sum);
            returnNO_ERROR;
        }break;
        default:
            returnBBinder::onTransact(code, data, reply, flags);
    }
}

This is the Demo class, which would normally do the real work on the service side of the binder.

?
1
2
3
4
5
6
7
8
9
10
11
classDemo : publicBnDemo {
    virtualvoid push(int32_t data) {
        // Do something with the data the client pushed
    }
    virtualvoid alert() {
        // Handle the alert
    }
    virtualint32_t add(int32_t v1, int32_t v2) {
        returnv1 + 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.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
classBpDemo : publicBpInterface<IDemo> {
    public:
        BpDemo(constsp<IBinder>& impl) : BpInterface<IDemo>(impl) { }
 
        virtualvoid push(int32_t push_data) {
            Parcel data, reply;
            data.writeInterfaceToken(IDemo::getInterfaceDescriptor());
            data.writeInt32(push_data);
            remote()->transact(PUSH, data, &reply);
        }
 
        virtualvoid alert() {
            Parcel data, reply;
            data.writeInterfaceToken(IDemo::getInterfaceDescriptor());
            remote()->transact(ALERT, data, &reply, IBinder::FLAG_ONEWAY);
        }
 
        virtualint32_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);
            returnres;
        }
};

Finally, we start the service as follows:

?
1
2
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:

?
1
2
3
4
5
6
7
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 atBinderDemo.

/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
 vi:ai:tabstop=8:shiftwidth=4:softtabstop=4:expandtab
 */
  
 /*
 * Author: Gabriel Burca <gburca dash binder at ebixio dot com>
 *
 * Sample code for using binders in Android from C++
 *
 * The Demo service provides 3 operations: push(), alert(), add(). See
 * the IDemo class documentation to see what they do.
 *
 * Both the server and client code are included below.
 *
 * To view the log output:
 * adb logcat -v time binder_demo:* *:S
 *
 * To run, create 2 adb shell sessions. In the first one run "binder" with no
 * arguments to start the service. In the second one run "binder N" where N is
 * an integer, to start a client that connects to the service and calls push(N),
 * alert(), and add(N, 5).
 */
  
 #define LOG_TAG "binder_demo"
  
 /* For relevant code see:
 frameworks/base/{include,libs}/binder/{IInterface,Parcel}.h
 frameworks/base/include/utils/{Errors,RefBase}.h
 */
  
 #include <stdlib.h>
  
 #include "utils/RefBase.h"
 #include "utils/Log.h"
 #include "utils/TextOutput.h"
  
 #include <binder/IInterface.h>
 #include <binder/IBinder.h>
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
 #include <binder/IPCThreadState.h>
  
 using namespace android;
  
  
 #define INFO(...) \
 do { \
 printf(__VA_ARGS__); \
 printf("\n"); \
 LOGD(__VA_ARGS__); \
 } while(0)
  
 void assert_fail(const char *file, int line,const char *func, const char *expr) {
 INFO("assertion failed at file%s, line %d, function %s:",
 file, line, func);
 INFO("%s", expr);
 abort();
 }
  
 #define ASSERT(e) \
 do { \
 if (!(e)) \
 assert_fail(__FILE__, __LINE__, __func__, #e); \
 } while(0)
  
  
 // Where to print the parcel contents: aout, alog, aerr. alog doesn‘t seem to work.
 #define PLOG aout
  
  
  
 // Interface (our AIDL) - Shared by server and 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);// Expands to 5 lines below:
 //static const android::String16 descriptor;
 //static android::sp<IDemo> asInterface(const android::sp<android::IBinder>& obj);
 //virtual const android::String16& getInterfaceDescriptor() const;
 //IDemo();
 //virtual ~IDemo();
 };
  
 // Client
 class BpDemo : public BpInterface<IDemo> {
 public:
 BpDemo(const sp<IBinder>& impl) : BpInterface<IDemo>(impl) {
 LOGD("BpDemo::BpDemo()");
 }
  
 virtual void push(int32_t push_data) {
 Parcel data, reply;
 data.writeInterfaceToken(IDemo::getInterfaceDescriptor());
 data.writeInt32(push_data);
  
 aout << "BpDemo::push parcel to be sent:\n";
 data.print(PLOG); endl(PLOG);
  
 remote()->transact(PUSH, data, &reply);
  
 aout << "BpDemo::push parcel reply:\n";
 reply.print(PLOG); endl(PLOG);
  
 LOGD("BpDemo::push(%i)", push_data);
 }
  
 virtual void alert() {
 Parcel data, reply;
 data.writeInterfaceToken(IDemo::getInterfaceDescriptor());
 data.writeString16(String16("The alert string"));
 remote()->transact(ALERT, data, &reply, IBinder::FLAG_ONEWAY);// asynchronous call
 LOGD("BpDemo::alert()");
 }
  
 virtual int32_t add(int32_t v1,int32_t v2) {
 Parcel data, reply;
 data.writeInterfaceToken(IDemo::getInterfaceDescriptor());
 data.writeInt32(v1);
 data.writeInt32(v2);
 aout << "BpDemo::add parcel to be sent:\n";
 data.print(PLOG); endl(PLOG);
 remote()->transact(ADD, data, &reply);
 LOGD("BpDemo::add transact reply");
 reply.print(PLOG); endl(PLOG);
  
 int32_t res;
 status_t status = reply.readInt32(&res);
 LOGD("BpDemo::add(%i,%i) = %i (status: %i)", v1, v2, res, status);
 return res;
 }
 };
  
 //IMPLEMENT_META_INTERFACE(Demo, "Demo");
 // Macro above expands to code below. Doing it by hand so we can log ctor and destructor calls.
 const android::String16 IDemo::descriptor("Demo");
 const android::String16& IDemo::getInterfaceDescriptor() const {
 return IDemo::descriptor;
 }
 android::sp<IDemo> IDemo::asInterface(const android::sp<android::IBinder>& obj) {
 android::sp<IDemo> intr;
 if (obj != NULL) {
 intr = static_cast<IDemo*>(obj->queryLocalInterface(IDemo::descriptor).get());
 if (intr == NULL) {
 intr = new BpDemo(obj);
 }
 }
 return intr;
 }
 IDemo::IDemo() { LOGD("IDemo::IDemo()"); }
 IDemo::~IDemo() { LOGD("IDemo::~IDemo()"); }
 // End of macro expansion
  
 // Server
 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) {
 LOGD("BnDemo::onTransact(%i)%i", code, flags);
 data.checkInterface(this);
 data.print(PLOG); endl(PLOG);
  
 switch(code) {
 case ALERT: {
 alert(); // Ignoring the fixed alert string
 return NO_ERROR;
 } break;
 case PUSH: {
 int32_t inData = http://www.mamicode.com/data.readInt32();
 LOGD("BnDemo::onTransact got%i", inData);
 push(inData);
 ASSERT(reply != 0);
 reply->print(PLOG); endl(PLOG);
 return NO_ERROR;
 } break;
 case ADD: {
 int32_t inV1 = data.readInt32();
 int32_t inV2 = data.readInt32();
 int32_t sum = add(inV1, inV2);
 LOGD("BnDemo::onTransact add(%i,%i) = %i", inV1, inV2, sum);
 ASSERT(reply != 0);
 reply->print(PLOG); endl(PLOG);
 reply->writeInt32(sum);
 return NO_ERROR;
 } break;
 default:
 return BBinder::onTransact(code, data, reply, flags);
 }
 }
  
 class Demo : public BnDemo {
 virtual void push(int32_t data) {
 INFO("Demo::push(%i)", data);
 }
 virtual void alert() {
 INFO("Demo::alert()");
 }
 virtual int32_t add(int32_t v1,int32_t v2) {
 INFO("Demo::add(%i,%i)", v1, v2);
 return v1 + v2;
 }
 };
  
  
 // Helper function to get a hold of the "Demo" service.
 sp<IDemo> getDemoServ() {
 sp<IServiceManager> sm = defaultServiceManager();
 ASSERT(sm != 0);
 sp<IBinder> binder = sm->getService(String16("Demo"));
 // TODO: If the "Demo" service is not running, getService times out and binder == 0.
 ASSERT(binder != 0);
 sp<IDemo> demo = interface_cast<IDemo>(binder);
 ASSERT(demo != 0);
 return demo;
 }
  
  
 int main(int argc, char **argv) {
  
 if (argc == 1) {
 LOGD("We‘re the service");
  
 defaultServiceManager()->addService(String16("Demo"),new Demo());
 android::ProcessState::self()->startThreadPool();
 LOGD("Demo service is now ready");
 IPCThreadState::self()->joinThreadPool();
 LOGD("Demo service thread joined");
 } else if (argc == 2) {
 INFO("We‘re the client:%s", argv[1]);
  
 int v = atoi(argv[1]);
  
 sp<IDemo> demo = getDemoServ();
 demo->alert();
 demo->push(v);
 const int32_t adder = 5;
 int32_t sum = demo->add(v, adder);
 LOGD("Addition result:%i + %i = %i", v, adder, sum);
 }
  
 return 0;
 }

Binder 通讯数据流概览