一、引言:
作为Android系统中使用最多的IPC通信机制,binder确实非常复杂,在网上看了很多人的博客,还有各种书籍中对binder的介绍,各有各的优点和侧重点:
深入理解Android卷I/卷II:基本就完成针对native层binder机制进行讲述,并对binder驱动也进行了一定的分析,但是在demo上做的就比较简略,只是带过;
深入理解Android内核设计思想:讲的太详细,从智能指针开始,然后从java层的binder开始分析,再到native,直至binder驱动,涉及的面太广了,如果对binder理解不深的可能会看得云里雾里,丧失信心;
而网上有一些博客讲的就不错,先以demo为例,让读者对binder有一个大概的印象,然后尝试去分析,让人对binder有一定的了解,贴出一下binder的参考书籍:
《深入理解Android卷I/卷II》
《深入理解Android内核设计思想》
博文:
android binder 基础实例及解析
听说你Binder机制学的不错
二、 binder通信的最简demo:
binder是基于client-service的通信方式,自然要从两方面来定义,这里我们写一个native的service,名叫“MyService”,demo的目录结构如下:
myservice/
├── Android.mk
├── client
│ ├── Android.mk
│ ├── main_client.cpp
│ └── Main_MyClient.cpp
├── interface
│ ├── IMyService.cpp
│ └── IMyService.h
└── service
├── Android.mk
├── Main_MyService.cpp
├── MyService.cpp
└── MyService.h
1. 定义IMyService.h
#ifndef IMyService_H
#define IMyService_H
#include <binder/IInterface.h>
namespace android {
/* 固定写法:此处必须继承自IInterface类 */
class IMyService : public IInterface
{
public:
DECLARE_META_INTERFACE(MyService);
virtual int setNum(int a) = 0;
virtual int getNum() = 0;
};
/* 对client端而言,只会暴露BnxxxService, 而不知道有BpxxxService
* 此类也是固定写法 */
class BnMyService : public BnInterface<IMyService>
{
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
}
#endif
解释一下Bn和Bp:
Bp:binder proxy,即代理的意思,程序实际运行过程中,client端并不知道有一个Bp的存在,但实际上,所有对binder的操作,都是BpxxxService这个代理来完成的;
Bn:binder native,为service端服务的,用于从binder驱动中获取client端传过来的指令和数据,并调用xxxService里面对client定义的这些接口的实现函数,怎么实现的Bn不管,它只负责调用这些函数,并将结果写入到binder驱动中,其实,可以把Bn理解为服务端与binder驱动通信的管道;
2.定义IMyService.cpp:
#include "IMyService.h"
#include <binder/Parcel.h>
#include <utils/Log.h>
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: IMyService"
namespace android {
enum
{
SET_NUM = IBinder::FIRST_CALL_TRANSACTION,
GET_NUM,
};
/* binder代理端 */
class BpMyService : public BpInterface<IMyService> {
public:
BpMyService(const sp<IBinder>& impl)
: BpInterface<IMyService>(impl)
{
}
virtual int setNum(int a) {
ALOGD(" BpMyService::setNum a = %d ", a);
Parcel data,reply;
data.writeInt32(a);
remote()->transact(SET_NUM,data,&reply);
return reply.readInt32();
}
virtual int getNum() {
ALOGD(" BpMyService::getNum");
Parcel data,reply;
data.writeInterfaceToken(IMyService::getInterfaceDescriptor());
remote()->transact(GET_NUM,data,&reply);
return reply.readInt32();
}
};
/* 接口:这里面会去new BpxxxSerivce*/
IMPLEMENT_META_INTERFACE(MyService, "jztech.binder.IMyService");
/* naive端代码,即服务端代码 ,此代码的作用是从binder driver中
* 拿到client端的请求数据,如果有需要设入的值,就从binder driver
* 中读取出来,然后执行服务器端的函数,将返回值写入binder driver
* 实际上,Bn的代码就是为binder driver和service服务的通道 */
status_t BnMyService::onTransact (uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
int ret = -1;
switch (code) {
case SET_NUM:
{
int num = -1;
ALOGD("BnMyService::onTransact SET_NUM ");
num = data.readInt32();
ret = setNum(num);
reply->writeInt32(ret);
return NO_ERROR;
}
case GET_NUM:
{
ALOGD("BnMyService::onTransact GET_NUM ");
ret = getNum();
reply->writeInt32(ret);
return NO_ERROR;
}
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
}
client端调用的函数会通过transact将数据打包成Parcel 格式,然后发送到binder driver中;
BnMyService则是从binder drive中获知了client端发送的消息,调用本地MyService中实现的函数,并将结果写入binder驱动返回给client端;
3. 定义MyService.h:
#ifndef MyService_H
#define MyService_H
#include "../interface/IMyService.h"
#include <binder/BinderService.h>
namespace android {
class MyService : public BnMyService
{
public:
MyService();
~MyService();
/* 服务的唯一标识符,client端通过这个字符串找到此服务 */
static const char* getServiceName()
{
return "MyService";
}
virtual int setNum(int a);
virtual int getNum();
protected:
int myParam;
};
}
#endif
定义这个头文件开始可以理解为我们已经在写service端了,这个头文件就是为了最终的MyServices函数实现,自然要继承自BnMyService,这里有一个函数叫getServiceName,里面返回的是字符串“MyService”,这个就是我们native层服务的唯一标识,非常重要,后续client端在获取service服务的时候,输入这个就能找到我们的这个服务了;
- 定义MyService.cpp
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <binder/Parcel.h>
#include <utils/threads.h>
#include <cutils/properties.h>
#include "MyService.h"
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: MyService"
namespace android {
MyService::MyService() {
myParam = 0;
}
MyService::~MyService()
{
}
int MyService::setNum(int a) {
ALOGD("MyService::setNum a = %d myParam %d", a, myParam);
myParam += a;
return 0;//OK
}
int MyService::getNum() {
ALOGD("MyService::getNum myParam = %d", myParam);
return myParam;
}
}
这个文件最简单了,因为最终client端就是调用这个文件里面的实现,在这里,我们维护一个成员变量myParam,client端每调用一次setNum,我们就将myParam与设入值累加,然后client端再调用一次getNum,获取返回的myParam;
好了,我想说,整个binder的核心其实已经写完了,就这四个文件,让他们分别作用于client端和service端就能够实现binder的通信了。
接下来,就是写测试代码了,既然binder是Android中最著名的IPC通信,自然,我们要在两个进程中去实现通信,我们写两个文件,一个作为client端,一个作为service端;
5. 编写Main_MyService.cpp:
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include "MyService.h"
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: MyService-main"
using namespace android;
int main(int argc, char** argv)
{
/* 1. 打开binder driver并且进行内核地址的mmap */
sp<ProcessState> proc(ProcessState::self());
/* 2.获取Android的SM并将我们的服务添加进去 */
sp<IServiceManager> sm = defaultServiceManager();
sm->addService(String16(MyService::getServiceName()), new MyService());
/* 3.启动子线程,一般的server都不需要再启动这样一个子线程来协助主线程工作 */
ProcessState::self()->startThreadPool();
/* 4.loop循环,从binder driver中获取指令,并由Bn的transact转为onTransact
* 最终回调到Bn端,进而执行xxxService里面的代码 */
IPCThreadState::self()->joinThreadPool();
return 0;
}
这个是服务端,代码非常简单,就是一个main函数,而且,全部都是固定句式,第二步中的getServiceName就是之前我说的唯一标识符,当然,你完全可以不用在IMyService.h去实现这个函数,直接在这里传入“MyService”即可,但是,我还是要强调的是,这个标识符很重要,下面client端马上就能用到,不要写错了哦~
第三步创建一个子线程,这步可以直接删掉,我们看安卓源码mediaserver是有这句的,大神们分析是因为当一个进程注册了多个server时,可能导致主线程太过繁忙,所以创建这么个子线程协同工作,当然,我们自己的server完全没那么复杂,所以可以给直接干掉;
6. 编写Main_MyClient.cpp:
#include <stdio.h>
#include "../interface/IMyService.h"
#include <binder/IServiceManager.h>
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: Client-main"
using namespace android;
sp<IMyService> mMyService;
void initMyServiceClient() {
if (mMyService == 0) {
/* 1.获取SM,并创建client端的gProcess,这里面又会去打开binder driver */
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
/* 2.由名字获取服务 */
binder = sm->getService(String16("MyService"));
if (binder != 0)
{
break;
}
ALOGE("MyService not published, waiting...");
sleep(1);
} while (1);
mMyService = interface_cast<IMyService>(binder);
}
}
int main(int argc, char* argv[]) {
/* 获取binder service */
initMyServiceClient();
if(mMyService ==NULL) {
ALOGE("cannot find MyService");
return -1;
}
while(1) {
mMyService->setNum(1);
sleep(1);
ALOGD("getNum %d", mMyService->getNum());
}
return 0;
}
这里,我们定义了一个全局变量mMyService来获取服务,直接看主函数,先调用initMyServiceClient去获取service端的服务,然后使用一个while循环不停地往service端请求setNum,之后再请求getNum看是否实现了和service端的通信,整个demo就真的全部写完了;
7. 三个Android.mk:
a. 最外层Android.mk:
include $(call all-subdir-makefiles)
b. client端mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
Main_MyClient.cpp \
../interface/IMyService.cpp
LOCAL_SHARED_LIBRARIES := \
libui libcutils libutils libbinder
LOCAL_C_INCLUDES := \
frameworks/base/include \
frameworks/native/include \
$(VENDOR_SDK_INCLUDES)
LOCAL_MODULE:= my_binder_client
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
c. service端mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
Main_MyService.cpp \
MyService.cpp \
../interface/IMyService.cpp
LOCAL_SHARED_LIBRARIES := \
libui libcutils libutils libbinder
LOCAL_C_INCLUDES := \
frameworks/base/include \
frameworks/native/include/binder \
$(VENDOR_SDK_INCLUDES) \
LOCAL_MODULE:= my_binder_service
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
三、 验证结果:
将生成的两个可执行文件推入单板,然后将service端作为后台进程执行,再执行client端:
通过adb获取打印log:
源码位置:
Git上源码路径