1. 程式人生 > 實用技巧 >Android Binder機制 -- libbinder

Android Binder機制 -- libbinder

Binder簡介

Binder是android系統提供的一種IPC機制。Android為什麼選擇binder?而不使用linux系統已有的IPC機制。具體原因不清楚,但是,看一下Binder機制的優點,我們可能能瞭解幾分。

  1. 高效

    linux 的大多數IPC方式都需要至少兩次記憶體拷貝。而Binder只需要一次記憶體拷貝。儘管共享記憶體的方式不需要多餘的記憶體拷貝,但是對於多程序而言,要構建複雜的同步機制,這也會抵消共享記憶體零拷貝帶來效能優勢。

  2. 穩定

  3. 安全

libbinder

在native層,通過libbinder封裝的類,我們能夠很輕鬆(真的輕鬆嗎?)的使用Binder機制來獲取系統或應用提供的服務。

繼承關係

其繼承關係比較複雜,如果我們要實現自己的服務或代理類,我們需要實現的就是圖中紅色的部分。等了解了每個類的功能和知道如何實現binder service 和 binder client 後,再回過頭來看這個類圖,就會比較清晰了。

類介紹

IBinder、BBinder 和 BpBinder

IBinder本質上就是一個介面類。提供了,BBinderBpBinder需要用到的通用函式及class。

BBinder對應的是Binder實體物件,簡單的理解就是它表示 IPC通訊過程中的 服務提供者。也就是C/S模式中的Server。

BpBinder對應的時Binder代理物件,代理的就是BBinder

,通過BpBinder我們就能和BBinder物件建立聯絡。

IBinder中有一個比較重要的函式transact

// code 就是我們定義的協議碼
// data 就是我們要傳送的資料
// reply 就是接收到的資料
virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) = 0;

對於BBinder,作為Server,他只是接受來自BpBinder的請求,處理後回覆,所以,BBinder::transact的實現就是處理資料並返回處理結果。

對於BpBinder

,作為Client,他傳送請求給BBinder並等待其處理結果。所以,BpBinder::transact的實現就是將資料傳送給BBinder並等待其返回。

  1. BpBinder::transact

    BpBinder::mHandler, 這個handler對應這一個BBinder,通過它,我們就能將資料傳送到BBinder所在的程序。並交由其transact函式處理。

    當我們要傳送資料時,直接呼叫BpBinder::transact傳送即可,其就會將資料交由我們代理的BBinder物件處理。資料傳輸工作由IPCThreadState::self()->transact完成,這裡我們暫且不談其實現細節。

  2. BBinder::transact


    BpBinder::transact執行後,資料到達BBinder所在的程序時,首先會執行BBinder::transact函式,該函式內部又呼叫了BBinder::onTransact函式來處理,該函式原型如下:

        virtual status_t    onTransact( uint32_t code,
                                        const Parcel& data,
                                        Parcel* reply,
                                        uint32_t flags = 0);
    

    是一個虛擬函式,這意味著,我們可以重寫該函式來實現我們自己的協議和處理邏輯,這是我們自定義Binder service 的關鍵。

Interface、BnInterface 和 BpInterface

​ 前面的BBinderBpBinder本質上是對代理物件和被代理物件直接資料傳遞方式的封裝。BnInterfaceBpInterface則是對兩者的資料協議的封裝。


  1. DECLARE_META_INTERFACEIMPLEMENT_META_INTERFACE

    這兩個巨集的作用就是讓我們少寫了很多程式碼!!!其用法我們後面會提到。前者定義了一個成員變數和4個方法,後者就是實現了這4個方法。

  2. interface_cast

    這個函式的作用就是將IBinder物件轉換為IInterface物件(實際型別需要執行時才知道)。

  3. BpRefBase

    前面的類圖,可以看到BpRefBaseBpBinder之間是聚合關係。BpInterface繼承了BpRefBase,而BpRefBase持有了BpBinder

    通過remote函式,我們就能拿到其持有的BpBinder,然後在BpInterface就能傳送資料到BBinder物件了。

    為甚麼是聚合關係,而不是BpInterface直接繼承BpBinder??。我想,大概是,這樣做,整個程序只會存在一個物件,便於Binder進行生命週期管理。

要徹底理解這幾個類和巨集的意義,還是要結合例項來理解,接下來我們就編寫一個demo展示一下libbinder的用法。

使用方式

假定一個這樣的場景,我們的程式執行在普通使用者下,但是有時需要執行一些需要root許可權的命令。我們就能建立一個執行在root許可權下的service,程式通過Binder介面,將要執行的命令傳送給service,並等待service 命令輸出結果。

定義協議和介面

首先,我們需要繼承IInterface類,並在定義好我們介面函式,這些介面函式在服務端和客戶端都會被用到。

#include <binder/IInterface.h>
#include <string>

using namespace android;

class IExecService : public IInterface {
public:
    // 首先定義 協議碼。協議碼起始值必須大於等於  IBinder::FIRST_CALL_TRANSACTION
    enum {
        EXEC_COMMAND = IBinder::FIRST_CALL_TRANSACTION,
    };

    //定義元介面,
    DECLARE_META_INTERFACE(ExecService)

    // 定義我們的介面
    virtual std::string exec(const std::string &cmd) = 0;
};

首先DECLARE_META_INTERFACE,我們先看一下展開後的樣子:

static const android::String16 descriptor;                          
static android::sp<IExecService> asInterface(                       
            const android::sp<android::IBinder>& obj);                  
virtual const android::String16& getInterfaceDescriptor() const;    
IExecService();                                                     
virtual ~IExecService();                                            

額,這裡,比較重要的就是 asInterface方法。該方法主要是給Client端使用,將一個IBinder轉換成IInterface物件。

Service 端實現

首先繼承BnInterface

// 定義 服務介面
class BnExecService : public BnInterface<IExecService> {
protected:
    status_t onTransact( uint32_t code,
                         const Parcel& data,
                         Parcel* reply,
                         uint32_t flags = 0) override;
};

BnExecService類實際上繼承了 BBinderIExecServiceIInterface。此時BnExecService還是一個抽象類,因為我們還沒實現IExecService中的函式。前面我們提到過,當資料到來時,會執行到IBinder::transact--> BBInder::onTransact,那先來看看onTransact的實現。

status_t BnExecService::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
    switch (code) {
        case EXEC_COMMAND: {
            CHECK_INTERFACE(IExecService, data, reply);	
            std::string cmd(data.readCString());
            auto res = exec(cmd);
            reply->writeCString(res.c_str());
            return NO_ERROR;
        }
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

這也算是固定的寫法吧,如果code不是我們自定義的,就呼叫BBinder::onTransact來處理(處理Android預定義的Code)。

此外,我們使用了CHECK_INTERFACE巨集,那麼在Client端傳送資料時,首先就應該傳送descriptor(DECLARE_META_INTERFACE巨集幫我們的定義的)。

下一步就是實現exec方法,這裡我們在BnExecService的子類中實現,當然也可以直接在BnExecService中實現。

class ExecService : public BnExecService {
public:

    // 實現咯
    std::string exec(const std::string &cmd) override {

        FILE * filp = popen(cmd.c_str(), "r");
        if (!filp) {
            return "Failed to popen";
        }

        std::string result;
        char line[1024]{};
        while (fgets(line, sizeof(line), filp) != nullptr) {
            result.append(line);
        }

        if (pclose(filp) != 0) {
            return "Failed to pclose";
        }
        return  result;
    }
};

最後就是將ExecService註冊到ServiceMananger,這樣Client就能通過ServiceManager引用到它。

ProcessState::self()->startThreadPool(); 
// 註冊到 Service Manager
defaultServiceManager()->addService(String16("exec_service"), new ExecService());
IPCThreadState::self()->joinThreadPool();

ProcessStateIPCThreadState是binder通訊的關鍵,其完成了實際的資料接發操作。後面會詳細介紹的。

Client端實現

首先繼承BpInterface,這樣我們就能通過remote()方法獲取到IBinder物件(實際型別是BpBinder)。

// 定義代理介面
class BpExecService : public BpInterface<IExecService> {
public:
    BpExecService(const sp<IBinder> &binder);

    std::string exec(const std::string &cmd) override;
};

對於BnExecService::exec其實現是執行真正的邏輯。而BpExecService::exec其實現就是將code和IPC相關的資料打包傳送給Service

BpExecService::BpExecService(const sp<IBinder> &binder) : BpInterface<IExecService>(binder) {

}

std::string BpExecService::exec(const std::string &cmd) {
    Parcel data;
    Parcel reply;
    data.writeInterfaceToken(IExecService::getInterfaceDescriptor());
    data.writeCString(cmd.c_str());
    if (remote()->transact(EXEC_COMMAND, data, &reply, 0)) {
        return "";
    }

    return reply.readCString();
}

建構函式中的IBinder物件就是remote()方法返回的IBinder物件。這個對於物件從何而來???

還記得DECLARE_META_INTERFACE嗎?其定義了幾個方法。我們還沒實現的,現在就要通過IMPLEMENT_META_INTERFACE巨集實現了。

IMPLEMENT_META_INTERFACE(ExecService, "com.liutimo.IExecService");

第二個引數就是descriptor。其會在Service Mananger中顯示。

該巨集展開後如下:

	const android::String16 IExecService::descriptor("com.liutimo.IExecService"); //初始化  descriptor           
	const android::String16&                                            
            IExecService::getInterfaceDescriptor() const {              
        return IExecService::descriptor;                                
    }                            
    android::sp<IExecService> IExecService::asInterface(                
            const android::sp<android::IBinder>& obj)                   
    {                                                                   
        android::sp<IExecService> intr;                                 
        if (obj != NULL) {                                              
            intr = static_cast<IExecService*>(                          
                obj->queryLocalInterface(                               
                        IExecService::descriptor).get());               
            if (intr == NULL) {                                         
                intr = new BpExecService(obj);                          
            }                                                           
        }                                                               
        return intr;                                                    
    }                                                                   
    IExecService::IExecService() { }                                    
    IExecService::~IExecService() { }                                   

這裡比較重要的函式就是asInterface

先來看client 如何引用Service

ProcessState::self()->startThreadPool();
auto binder = defaultServiceManager()->getService(String16("exec_service"));
auto es = interface_cast<IExecService>(binder);
std::cout << es->exec(argv[1]) << std::endl;

interface_cast原型如下:

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

最終還是呼叫的IExecService::asInterface(obj)。回到asInterface的定義。

    android::sp<IExecService> IExecService::asInterface(                
            const android::sp<android::IBinder>& obj)                   
    {                                                                   
        android::sp<IExecService> intr;                                 
        if (obj != NULL) {                                              
            intr = static_cast<IExecService*>(                          
                obj->queryLocalInterface(                               
                        IExecService::descriptor).get());               
            if (intr == NULL) {                                         
                intr = new BpExecService(obj);                          
            }                                                           
        }                                                               
        return intr;                                                    
    } 

obj就是我們從service mananger拿到的IBinder物件(通過obj->transact方法就能傳輸資料到service端)。其實際型別分兩種情況:

  1. Client 和 Service 位於同一個程序空間。obj實際型別就是BBinder
  2. Client 和 Service 位於不同的程序空間,obj實際型別就是BpBinder

queryLocalInterface的左右就是clientService是不是在同一個程序空間。是的話,asInterface返回的實際上就是ExecService物件,否則,就建立一個BpExecService物件返回(我們通過BpExecService::exec就能進行IPC呼叫了)。

libbinder資料是如何傳輸的