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!