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
