Binder @ 用cpp实现一个service

来自个人维基
跳转至: 导航搜索

目录

一、前言

binder,是android中随处可见的IPC机制。

关于其设计思想及设计原理可以认真研读一下这篇文章,写得很好:http://disanji.net/2011/02/28/android-bnder-design/

不过在这里,我们不会去深研其实现原理,而是以一个应用开发者的角度,看一下如何使用它。

IPC,即进程间通信,作用是使一个进程能够访问另一个并行进程的数据。而binder,从直观的角度上看来,更为简单,就是在一个进程中能够调用另一个进程的函数,就好像调用本地函数一样。

二、概述

为了实现上面所说的“从一个进程‘调用’另一个进程的函数”,binder首先要求客户端和服务端都要定义一些相同的接口,然后给服务编号,在客户端要访问服务端某一服务时就通过这个编号来指定,同时在传递这个编号时也带有数据(int32、string、pointer)作为参数,数据的交换就通过这些参数实现。

上面这些都很好理解,不过这还是没有说明核心——IPC的部分在哪?其实这部分和linux已有的IPC机制类似,就是将数据从用户空间通过linux driver写虚拟文件(/dev/binder)的方式,在内核空间实现。不过binder使用mmap将空间映射的方式作了优化,避免了内存分配、减少数据拷贝次数,具体可以查看上第一章推荐的那篇文章,里面有详细的描述。

而对于“binder首先要求客户端和服务端都要定义一些相同的接口”的实现,相信有过面向对象基础的朋友们已经猜到了...

——没错,正是采用继承共同的父类的方式实现!

各类继承关系图如下:

Binder class.jpg

这里所实现的service是模拟一个火炮,所提供的服务有“开火”、“检查剩余炮弹”和“装弹”。

三、用C++实现一个service

1、实现共同父类IMyBinder

这个类除了要定义上面所说的函数外,还要定义一些binder内部使用的函数和变量。

//IMyBinder.h

#ifndef I_MY_BINDER_H
#define I_MY_BINDER_H

#include <binder/IInterface.h>
#include <binder/IPermissionController.h>
#include <utils/Vector.h>
#include <utils/String16.h>



namespace android {

    class IMyBinder : public IInterface {
        public:
            DECLARE_META_INTERFACE(MyBinder);

            virtual bool loadBomb(const int num) = 0;
            virtual int checkBomb(void) = 0;
            virtual bool fire(void) = 0;

        enum {
            LOAD_BOMB_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
            CHECK_BOMB_TRANSACTION,
            FIRE_TRANSACTION
        };
    };
}
#endif /* I_MY_BINDER_H */
//IMyBinder.cpp

#include <IMyBinder.h>
#include <BpMyBinder.h>

namespace android {

    IMPLEMENT_META_INTERFACE(MyBinder, "IMyBinder");
}

2、实现BnMyBinder

Bn中的首个字母B是代表binder,那第二字母代表什么意思呢?答案就是native,即表示这个类是在真正实现对应功能的进程中创建的。相对应的,下面将会说到的BpMyBinder中的p则是proxy的首字母,为代理的意思。

在BnMyBinder中必须要实现的函数是 onTransact,因为所有的IPC服务调用都是通过这个函数、根据服务编号完成分发实现的。

//BnMyBinder.h

#ifndef BN_MY_BINDER_H
#define BN_MY_BINDER_H

#include <IMyBinder.h>


namespace android {

    class BnMyBinder : public BnInterface<IMyBinder> {
        public:
            virtual status_t onTransact(uint32_t code,
                                        const Parcel& data,
                                        Parcel* reply,
                                        uint32_t flags = 0);
    };
}
#endif /* BN_MY_BINDER */
//BnMyBinder.cpp

#define LOG_TAG "BnMyBinder"

/*#include <binder/IPCThreadState.h>
#include <utils/Log.h>

#include <private/android_filesystem_config.h>

#include <sys/time.h>
#include <sys/resource.h>

#include <signal.h>
#include <stdio.h>
#include <unistd.h>*/

#include <BnMyBinder.h>
#include <utils/Debug.h>
#include <utils/Log.h>
#include <binder/Parcel.h>

namespace android {


    status_t BnMyBinder::onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {

        switch(code) {
            case LOAD_BOMB_TRANSACTION:
            {
                CHECK_INTERFACE(IMyBinder, data, reply);
                bool err = const_cast<BnMyBinder*>(this)->loadBomb(data.readInt32());
                reply->writeInt32(err);
            }
            return NO_ERROR;
            break;
                
            case CHECK_BOMB_TRANSACTION:                
            {
                CHECK_INTERFACE(IMyBinder, data, reply);
                int restBomb = const_cast<BnMyBinder*>(this)->checkBomb();
                reply->writeInt32(restBomb);
            }
            return NO_ERROR;
            break;

            case FIRE_TRANSACTION:
            {
                CHECK_INTERFACE(IMyBinder, data, reply);
                bool err = const_cast<BnMyBinder*>(this)->fire();
                reply->writeInt32(err);
            }
            return NO_ERROR;
            break;  

            default:
                return BBinder::onTransact(code, data, reply, flags);
        }

    }
}

3、实现BpMyBinder

如上面所说,BpMyBinder是“代理”,那它是什么代表的是什么呢?

——其实所代理就是IPC服务的提供者,使得在android的所有进程中均能通过这些代理调用其他进程的服务/函数,就像调用本地的服务/函数一样。

为了提供这些服务,BpMyBinder必然要定义使用这些服务的函数(或者说方法),另外,由于BpMyBinder也是继承自IMyBinder,所以就能保证这些函数名和BnMyBinder(同样继承自IMyBinder)中保持一致,方便设计者的使用和查看。

//BpMyBinder.h

#ifndef BP_MY_BINDER_H
#define BP_MY_BINDER_H

#include <IMyBinder.h>

namespace android {
    class BpMyBinder : public BpInterface<IMyBinder> {
        public:
            BpMyBinder(const sp<IBinder>& impl) //indispensable
                : BpInterface<IMyBinder>(impl) {
            }
            
            virtual bool loadBomb(const int num);
            virtual int checkBomb(void);
            virtual bool fire(void);       
    };


}
#endif /* BP_MY_BINDER_H */
//BpMyBinder.cpp

#include <binder/Parcel.h>
#include <BpMyBinder.h>

namespace android {

    bool BpMyBinder::loadBomb(const int num) {
        Parcel data, reply;
        data.writeInterfaceToken(IMyBinder::getInterfaceDescriptor());
        data.writeInt32(num);
        remote()->transact(LOAD_BOMB_TRANSACTION, data, &reply);
        return reply.readInt32();
    }

    int BpMyBinder::checkBomb(void) {
        Parcel data, reply;
        data.writeInterfaceToken(IMyBinder::getInterfaceDescriptor());
        remote()->transact(CHECK_BOMB_TRANSACTION, data, &reply);
        return reply.readInt32();
    }

    bool BpMyBinder::fire(void) {
        Parcel data, reply;
        data.writeInterfaceToken(IMyBinder::getInterfaceDescriptor());
        remote()->transact(FIRE_TRANSACTION, data, &reply);
        return reply.readInt32();
    }
}

4、实现MyBinder

MyBinder继承自BnMyBinder,这里主要是在native实现IMyBinder中所定义的纯虚函数,从而使得 BnMyBinder.onTransact的功能分发得以实现。

//MyBinder.h

#include <BnMyBinder.h>

namespace android {

    class MyBinder : public BnMyBinder {            
        public:
            virtual bool loadBomb(const int num);
            virtual int checkBomb(void);
            virtual bool fire(void);            
    };
}
//MyBinder.cpp

#include <sys/types.h>
#include <unistd.h>
#include <grp.h>

#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <utils/RefBase.h>

#include <MyBinder.h>

using namespace android;

//namespace android {

static int restBomb = 0;

bool MyBinder::loadBomb(const int num) {
    restBomb+=num;
    return true;
}

int MyBinder::checkBomb(void) {
    return restBomb;
}

bool MyBinder::fire(void) {
    if(0 < restBomb) {
        restBomb--;
        return true;
    } else {
        return false;
    }
}

//}

5、Makefile

由于上面这些文件要同时被Server和Client进程引用,同时也不能直接运行,所以应该编译成库,方便其他可执行程序的引用。

//Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= IMyBinder.cpp BnMyBinder.cpp BpMyBinder.cpp MyBinder.cpp

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE:= libMyBinder

LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)

LOCAL_SHARED_LIBRARIES := libcutils libutils libbinder

#LOCAL_SHARED_LIBRARIES := libutils

include $(BUILD_SHARED_LIBRARY)

四、验证

上面的工作是完成了MyBinder这个service的运行模型,但到目前为此是不能运行的,原因是这些仅仅是一些“类”,还未实例化!因此,为了对其进行验证,我们必须创建其实例。

由于是IPC服务,自然我们要创建服务器和客户端,并使其运行在不同的进程中。

1、创建server

这里主要是为MyBinder这个service创建一个实例,并加入线程池中(实际是让其线程进行监听):

//MyServer.cpp

#include <sys/types.h>
#include <unistd.h>
#include <grp.h>

#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <utils/RefBase.h>

#include <MyBinder.h>

using namespace android;


int main(int argc, char** argv) {
    sp<ProcessState> proc(ProcessState::self());

    sp<IServiceManager> sm = defaultServiceManager();

    sm->addService(String16("service.MyBinder"), new MyBinder());

    //MyBinder::instantiate();

    ProcessState::self()->startThreadPool();

    IPCThreadState::self()->joinThreadPool();

    return 0;
}

mk文件,让其编译成可执行文件;另外,由于要新建MyBinder,所以要包含MyBinder中的头文件和MyBinder库文件:

#Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_C_INCLUDES:= $(LOCAL_PATH)/../MyBinder/

LOCAL_SRC_FILES:= MyServer.cpp

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE:= MyServer

LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)

LOCAL_SHARED_LIBRARIES := libcutils libutils libbinder libMyBinder

#LOCAL_SHARED_LIBRARIES := libutils

include $(BUILD_EXECUTABLE)

2、创建client

在client进程中,根据用户输入的口令,获取"MyBinder"服务,然跨进程“调用”函数,完成“装弹”、“开炮”等任务。

//MyClient.cpp


#define LOG_TAG "MyClient"

#include <sys/types.h>
#include <unistd.h>
#include <grp.h>

#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <utils/RefBase.h>

#include <../MyBinder/IMyBinder.h>

#define ALOGD printf
#define ALOGW printf
#define ALOGE printf

using namespace android;

static void help() {
	printf("USAGE:\n\tMyClient fire\n\tMyClient loadBomb <bomb number>\n\tMyClient checkBomb\n");
}

int main(int argc, char** argv) {

    //ALOGD("argv[0]=%s, argv[1] = %s, argv[2] = %s\n", *argv, *(argv+1), *(argv+2));
	if(argc < 2) {
		help();
		exit(0);
	}
    char *cmd = *(argv+1);
    char *arg = *(argv+2);

    sp<IBinder> binder =
        defaultServiceManager()->getService(String16("service.MyBinder"));
    sp<IMyBinder> service = interface_cast<IMyBinder>(binder);
    if (service.get() == NULL) {
        ALOGW("Cannot connect to the service.MyBinder");
        return -1;
    }
    //ALOGD("get service.MyBinder sucess.\n");

    if(!strcmp(cmd, "fire")) {
        if(true == service->fire()) {
            ALOGD("Bang!\n");
        } else {
            ALOGW("no bomb..\n");
        }
    } else if(!strcmp(cmd, "loadBomb")) {
        if(NULL != arg) {
            if(true == service->loadBomb((char)*arg-48)) {
                ALOGD("loadBomb sucess.\n");
            } else {
                ALOGE("loadBomb error.\n");
            }
        } else {
			ALOGE("bomb number should be given.\n");
		}
    } else if(!strcmp(cmd, "checkBomb")) {
        ALOGD("rest bomb:%d\n", service->checkBomb());
    } else {
		help();
	}
    
    return 0;
}


mk文件:

#Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_C_INCLUDES:= $(LOCAL_PATH)/../MyBinder/

LOCAL_SRC_FILES:= MyClient.cpp

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE:= MyClient

LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)

LOCAL_SHARED_LIBRARIES := libcutils libutils libbinder libMyBinder

#LOCAL_SHARED_LIBRARIES := libutils

include $(BUILD_EXECUTABLE)

3、验证结果

将上面的文件编译,将分别得到一个库文件和二个可执行文件,即这些文件push到手机中就可以开始验证了。

(1)在火炮未准备就绪的情况下(即server未运行),“开炮”

C:\DaS\tel123> adb shell MyClient fire

结果(均为通过 adb logcat -v time | egrep "MyClient|MyBinder" 命令查看):

01-02 11:44:50.558 W/ADB_SERVICES( 4504): create_local_service_socket() name=shell:MyClient fire
01-02 11:44:50.577 I/ServiceManager( 4529): Waiting for service service.MyBinder...
01-02 11:44:51.578 I/ServiceManager( 4529): Waiting for service service.MyBinder...
01-02 11:44:52.578 I/ServiceManager( 4529): Waiting for service service.MyBinder...
01-02 11:44:53.579 I/ServiceManager( 4529): Waiting for service service.MyBinder...
01-02 11:44:54.579 I/ServiceManager( 4529): Waiting for service service.MyBinder...
01-02 11:44:55.579 W/MyClient( 4529): Cannot connect to the service.MyBinder

(2)

准备火炮:

C:\DaS\tel123> adb shell MyServer

再开炮:

C:\DaS\tel123> adb shell MyClient fire

结果:

01-02 11:45:03.247 W/MyClient( 4534): no bomb..

没炮弹了?查看一下:

C:\DaS\tel123> adb shell MyClient checkBomb

结果:

01-02 11:45:21.359 D/MyClient( 4536): rest bomb:0

还真是!装弹:

C:\DaS\tel123> adb shell MyClient loadBomb 2

结果显示装弹成功:

01-02 11:45:29.909 D/MyClient( 4538): loadBomb sucess.

保险起见,再检查一下:

C:\DaS\tel123> adb shell MyClient checkBomb

返回所剩炮弹:

01-02 11:45:33.128 D/MyClient( 4540): rest bomb:2

好吧,可以开炮了吧:

C:\DaS\tel123> adb shell MyClient fire

结果:

01-02 11:45:41.779 D/MyClient( 4542): Bang!

再来:

C:\DaS\tel123> adb shell MyClient fire

结果:

01-02 11:45:45.202 D/MyClient( 4544): Bang!

兴起!再来!:

C:\DaS\tel123> adb shell MyClient fire

额,又没炮弹了?:

01-02 11:45:54.152 W/MyClient( 4546): no bomb..

对,好像刚是只装了两发:

C:\DaS\tel123> adb shell MyClient checkBomb

结果:

01-02 11:45:56.566 D/MyClient( 4548): rest bomb:0


上述源码可从文件:MyBinder.rar打包下载.


参考资料:

http://disanji.net/2011/02/28/android-bnder-design/

http://blog.csdn.net/a345017062/article/details/6175519

http://www.cnblogs.com/innost/archive/2011/01/09/1931456.html