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上源碼路徑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章