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首先要求客户端和服务端都要定义一些相同的接口”的实现,相信有过面向对象基础的朋友们已经猜到了...
——没错,正是采用继承共同的父类的方式实现!
各类继承关系图如下:
这里所实现的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