如何创建一个Android Native Binder Service

时间:2021-12-02 05:28:41

Binder是Android特有的进程间通信(IPC)机制和远程方法调用系统,整个核心平台的跨进程操作几乎都是通过binder进行的,相对于其他的IPC方式,binder有如下特点:

  • 通过驱动程序来进行进程间通信
  • 通过共享内存来提高性能
  • 每个进程都有处理请求的线程池
  • 引用计数功能,支持跨进程的对象引用
  • 进程间同步调用
  • 可以跨进程传输文件描述符
  • 服务端终止通知

这篇文章我通过一个实际的例子来演示如何创建一个本地的binder服务,并验证几个binder的特性,主要演示以下几个功能:
1. 客户端通过binder获取一块共享的匿名内存,把内容给打印出来
2. 客户端通过binder获取一个服务端已经打开的文件的文件描述符,写入内容,并在服务端打印出文件的内容
3. 客户端通过binder设置和获取服务端的一个整数成员
4. 客户端通过binder设置 和获取服务端一个字符串成员
5. 验证binder的death notification功能
6. 通过dumpsys工具获取服务端的信息
演示程序已经上传到github,通过下面的命令获取:

    git clone https://github.com/mason-Wang/binderipctest.git

1.定义接口

//IIpcTestService.h
class IIpcTestService : public IInterface
{
public:
    DECLARE_META_INTERFACE(IpcTestService);

    virtual sp<IMemory> getMemory() const = 0; 
    virtual int getFileDescriptor() = 0;
    virtual void dumpFile() = 0;
    virtual void setInt(int value) = 0;
    virtual int getInt() = 0;
    virtual void setString(const char* str) = 0;
    virtual char* getString() = 0;
};

class BnIpcTestService : public BnInterface<IIpcTestService> 
{
public:
    virtual status_t onTransact(uint32_t code,
                            const Parcel& data,
                            Parcel* reply,
                            uint32_t flags = 0);
};

这个接口定义了客户端和服务端进程间通信所用到的所有接口,客户端和服务端都会继承这个接口,并实现。BnIpcTestService是服务端的代理。

2.实现接口

// IIpcTestService.cpp
enum {
    GET_MEMORY = IBinder::FIRST_CALL_TRANSACTION,
    GET_FD,
    DUMP_FILE,
    SET_INT,
    GET_INT,
    SET_STR,
    GET_STR,
};

class BpIpcTestService : public BpInterface<IIpcTestService>
{
public:
    BpIpcTestService(const sp<IBinder>& impl)
        : BpInterface<IIpcTestService>(impl)
    {
    }

    virtual sp<IMemory> getMemory() const
    {
        Parcel data, reply;
        sp<IMemory> mem;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        status_t status = remote()->transact(GET_MEMORY, data, &reply);
        if (status == NO_ERROR) {
            mem = interface_cast<IMemory>(reply.readStrongBinder());
            if (mem != 0 && mem->pointer() == NULL) {
                mem.clear();
            }
        }
        return mem;
    }

    virtual int getFileDescriptor()
    {
        Parcel data, reply;
        int fd;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        status_t status = remote()->transact(GET_FD, data, &reply);
        if (status == NO_ERROR) {
            //Note:we must dup the got file descriptor, because the file descriptor 
            //will be closed after this function return, the close is did by the ~Parcel()
            fd = dup(reply.readFileDescriptor());
        }
        return fd;
    }

    virtual void dumpFile() 
    {
        Parcel data, reply;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        remote()->transact(DUMP_FILE, data, &reply);
    }

    virtual void setInt(int value)
    {
        Parcel data, reply;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        data.writeInt32(value);
        remote()->transact(SET_INT, data, &reply);
    }

    virtual int getInt()
    {
        Parcel data, reply;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        remote()->transact(GET_INT, data, &reply);
        return reply.readInt32();
    }

    virtual void setString(const char* str) 
    {
        Parcel data, reply;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        data.writeCString(str);
        remote()->transact(SET_STR, data, &reply);
    }

    virtual char* getString()
    {
        Parcel data, reply;
        const char* str1;
        char* str2;
        data.writeInterfaceToken(IIpcTestService::getInterfaceDescriptor());
        remote()->transact(GET_STR, data, &reply);
        str1 = reply.readCString();
        str2 = (char *)malloc(strlen(str1) + 1);
        strcpy(str2, str1);
        return str2;
    }

};

IMPLEMENT_META_INTERFACE(IpcTestService, "android.test.IIpcTestService");

status_t BnIpcTestService::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch (code) {
        case GET_MEMORY: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            reply->writeStrongBinder(getMemory()->asBinder());
            return NO_ERROR;
        }break;
        case GET_FD: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            reply->writeFileDescriptor(getFileDescriptor());
            return NO_ERROR;
        }break;
        case DUMP_FILE: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            dumpFile();
            return NO_ERROR;
        }break;
        case SET_INT: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            setInt(data.readInt32());
            return NO_ERROR;
        }break;
        case GET_INT: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            reply->writeInt32(getInt());
            return NO_ERROR;
        }break;
        case SET_STR: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            setString(data.readCString());
            return NO_ERROR;
        }break;
        case GET_STR: {
            CHECK_INTERFACE(IIpcTestService, data, reply);
            reply->writeCString(getString());
            return NO_ERROR;
        }break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

BpIpcTestService是客户端的代理,客户端使用代理类来跟服务端通信,每一个方法调用都有一个唯一的ID,在服务端的onTransact方法中利用这个ID来调用对应的服务端方法。BpIpcTestService把客户端的方法调用的参数打包成Parcel,通过binder传输到服务端,在服务端解包parcel,把参数传递给对应的方法,服务端的返回值也是通过parcel传输。

3.定义服务端接口

// IpcTestService.h
class IpcTestService : 
        public BinderService<IpcTestService>,
        public BnIpcTestService
    {
    public:
        static const char* getServiceName() { return "android.ipctest";}
        IpcTestService();
        virtual status_t dump(int fd, const Vector<String16>& args);
        virtual sp<IMemory> getMemory() const;  
        virtual int getFileDescriptor();
        virtual void dumpFile();
        virtual void setInt(int value);
        virtual int getInt();
        virtual void setString(const char* str);
        virtual char* getString();

    private:
        virtual void onFirstRef();
        sp<MemoryBase> mMem;
        int mFd;
        int mIntVal;
        char *mStr;
    };

服务端继承自BnIpcTestService

4. 实现服务端接口

// IpcTestService.cpp
IpcTestService::IpcTestService() 
        : mMem(NULL),
          mFd(-1),
          mIntVal(0),
          mStr(NULL)

    {
        ALOGD("IpcTestService() is called.");
    }

    void IpcTestService::onFirstRef()
    {
        ALOGD("onFirstRef() is called.");
        mMem = new MemoryBase(new MemoryHeapBase(512, 0, "IpcTestMem"), 0, 512);
        strcpy((char *)mMem->pointer(), "Hello Ashmem");
    }

    sp<IMemory> IpcTestService::getMemory() const
    {
        ALOGD("getMemory() is called.");
        return mMem;
    }

    status_t IpcTestService::dump(int fd, const Vector<String16>& args  __unused)
    {
        const size_t SIZE = 256;
        char buffer[SIZE];
        String8 result;

        ALOGD("dump() is called.");
        snprintf(buffer, SIZE, "mFd = %d\n"
                            "mIntVal = %d\n"
                            "mStr = %s\n",
                            mFd, mIntVal, mStr);
        result.append(buffer);
        write(fd, result.string(), result.size());
        return NO_ERROR;
    }

    int IpcTestService::getFileDescriptor()
    {
        ALOGD("getFileDescriptor is called.");
        mFd = open("/tmp/ipctest", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
        return mFd;
    }

    void IpcTestService::dumpFile() 
    {
        ALOGD("dumpFile is called.");
        int size, offset;
        char* data;
        if (mFd < 0) return;
        offset = lseek(mFd, 0, SEEK_CUR);
        if (offset < 0) return;
        ALOGD("dumpFile: current offset is %d.", offset);
        size = lseek(mFd, 0, SEEK_END);
        if (size < 0) return;
        if (lseek(mFd, 0, SEEK_SET) < 0) return;
        ALOGD("dumpFile: file size is %d.", size);
        data = (char*)malloc(size);
        if (data == 0) return;
        if (read(mFd, data, size) < 0) {
            free(data);
            return;
        }
        ALOGD("dumpFile:%s", data);
        free(data);
    }

    void IpcTestService::setInt(int value)
    {
        ALOGD("setInt:%d", value);
        mIntVal = value;
    }

    int IpcTestService::getInt()
    {
        ALOGD("getInt is called");
        return mIntVal;
    }

    void IpcTestService::setString(const char *str)
    {
        ALOGD("setString:%s", str);
        int len = strlen(str);
        mStr = (char *)malloc(len+1);
        if (mStr != NULL) strcpy(mStr, str);
    }

    char* IpcTestService::getString()
    {
        ALOGD("getString is called.");
        return mStr;
    }

因为所有跟binder通信相关的操作都在服务端的代理BnIpcTestService中实现,所以服务端接口的实现非常简单。

5.实现服务端

// main_server.cpp
int main(int argc __unused, char** argv __unused)
{
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    IpcTestService::publishAndJoinThreadPool();
}

服务端的实现非常简单,把Service添加到ServiceManager中,然后 等待客户端建立连接并处理其请求。

6. 实现客户端

// main_client.cpp
class ServerDeathRecipient : public IBinder::DeathRecipient
{
    virtual void binderDied(const wp<IBinder>& who __unused)
    {
        ALOGD("The android.ipctest server is dead!");
    }
};

int main(int argc __unused, char** argv __unused)
{
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("android.ipctest"));
    sp<IIpcTestService> ipcTestService;
    sp<IMemory> mem;

    if (binder == 0) {
        ALOGE("get ipctest service error!");
        exit(1);
    }

    // Test death notification
    // Client register a death notifier, when the server is dead, client will be notified
    sp<ServerDeathRecipient> dr = new ServerDeathRecipient();
    binder->linkToDeath(dr); 
    ipcTestService = interface_cast<IIpcTestService>(binder);

    // Test Ashmem
    // Get the memory and dump the content
    mem = ipcTestService->getMemory();
    if (mem != 0) {
        ALOGD("The ashmem content is:%s", mem->pointer());
    }
    mem.clear();

    // Test file descriptor transfer
    // Get the file descriptor from server
    int fd = ipcTestService->getFileDescriptor();
    if (fd < 0) {
        ALOGE("Bad file descriptor!");
        exit(1);
    }
    // Write a string to the file and let server dump the file content 
    const char *str = "Hello World!";
    write(fd, str, strlen(str) + 1);
    ipcTestService->dumpFile();
    close(fd);

    // Test set/get a integer to/from server
    ipcTestService->setInt(10);
    int value = ipcTestService->getInt();
    ALOGD("Got int value:%d", value);

    // Test set/get a string to/from server
    ipcTestService->setString("Hello World!");
    char* string = ipcTestService->getString();
    ALOGD("Got string:%s", string);
    free(string);

    proc->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}

客户端先通过 ServiceManager获取服务,然后调用对应的接口来跟服务端交互。


测试验证

把程序放到Android的代码目录下,在程序目录下执行mm编译,最后会产生/system/bin/IpcTestServer/system/bin/IpcTestClient两个可执行文件,把这两个文件复制到Android系统中,例如/tmp目录下,然后就可以验证了。
先让服务端运行并打印log:
shell@android:/tmp # logcat | grep IpcTest
shell@android:/tmp # ./IpcTestServer &
可以看到有如下log

D/IpcTestService(18751): IpcTestService() is called.
D/IpcTestService(18751): onFirstRef() is called.

然后运行客户端:
shell@android:/tmp # ./IpcTestClient &
log如下:

D/IpcTestService(18751): getMemory() is called.
D/IpcTestClient(18770): The ashmem content is:Hello Ashmem
D/IpcTestService(18751): getFileDescriptor is called.
D/IpcTestService(18751): dumpFile is called.
D/IpcTestService(18751): dumpFile: current offset is 13.
D/IpcTestService(18751): dumpFile: file size is 13.
D/IpcTestService(18751): dumpFile:Hello World!
D/IpcTestService(18751): setInt:10
D/IpcTestService(18751): getInt is called
D/IpcTestClient(18770): Got int value:10
D/IpcTestService(18751): setString:Hello World!
D/IpcTestService(18751): getString is called.
D/IpcTestClient(18770): Got string:Hello World!

我们接着试一下dumpsys工具的使用,dumpsys工具可以调用到服务端的dump方法,可以在这个方法中加入很多的状态信息,方便调试服务端。
shell@android:/tmp # dumpsys android.ipctest
log输出如下:

mFd = 12
mIntVal = 10
mStr = Hello World!
D/IpcTestService(18751): dump() is called.

最后我们验证binder的death notification功能,我们把服务端kill掉,看客户端会不会有通知。
shell@android:/tmp # kill -9 18751
可以看到log输出如下:
D/IpcTestClient(18770): The android.ipctest server is dead!