Android中的binder机制分析一:写一个native层最简单的demo

一、引言:
作为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服务的时候,输入这个就能找到我们的这个服务了;

  1. 定义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上源码路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值