Android Binder機制 -- libbinder
Binder簡介
Binder是android系統提供的一種IPC機制。Android為什麼選擇binder?而不使用linux系統已有的IPC機制。具體原因不清楚,但是,看一下Binder機制的優點,我們可能能瞭解幾分。
-
高效
linux 的大多數IPC方式都需要至少兩次記憶體拷貝。而Binder只需要一次記憶體拷貝。儘管共享記憶體的方式不需要多餘的記憶體拷貝,但是對於多程序而言,要構建複雜的同步機制,這也會抵消共享記憶體零拷貝帶來效能優勢。
-
穩定
-
安全
libbinder
在native層,通過libbinder封裝的類,我們能夠很輕鬆(真的輕鬆嗎?)的使用Binder機制來獲取系統或應用提供的服務。
繼承關係
其繼承關係比較複雜,如果我們要實現自己的服務或代理類,我們需要實現的就是圖中紅色的部分。等了解了每個類的功能和知道如何實現binder service 和 binder client 後,再回過頭來看這個類圖,就會比較清晰了。
類介紹
IBinder、BBinder 和 BpBinder
IBinder
本質上就是一個介面類。提供了,BBinder
和BpBinder
需要用到的通用函式及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
BBinder
並等待其處理結果。所以,BpBinder::transact
的實現就是將資料傳送給BBinder
並等待其返回。
-
BpBinder::transact
BpBinder::mHandler
, 這個handler對應這一個BBinder
,通過它,我們就能將資料傳送到BBinder
所在的程序。並交由其transact
函式處理。當我們要傳送資料時,直接呼叫
BpBinder::transact
傳送即可,其就會將資料交由我們代理的BBinder
物件處理。資料傳輸工作由IPCThreadState::self()->transact
完成,這裡我們暫且不談其實現細節。 -
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
前面的BBinder
和BpBinder
本質上是對代理物件和被代理物件直接資料傳遞方式的封裝。BnInterface
和BpInterface
則是對兩者的資料協議的封裝。
-
DECLARE_META_INTERFACE
和IMPLEMENT_META_INTERFACE
這兩個巨集的作用就是讓我們少寫了很多程式碼!!!其用法我們後面會提到。前者定義了一個成員變數和4個方法,後者就是實現了這4個方法。
-
interface_cast
這個函式的作用就是將
IBinder
物件轉換為IInterface
物件(實際型別需要執行時才知道)。 -
BpRefBase
前面的類圖,可以看到
BpRefBase
和BpBinder
之間是聚合關係。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
類實際上繼承了 BBinder
、IExecService
和IInterface
。此時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();
ProcessState
和IPCThreadState
是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端)。其實際型別分兩種情況:
- Client 和 Service 位於同一個程序空間。
obj
實際型別就是BBinder
。 - Client 和 Service 位於不同的程序空間,
obj
實際型別就是BpBinder
。
queryLocalInterface
的左右就是client
和Service
是不是在同一個程序空間。是的話,asInterface
返回的實際上就是ExecService
物件,否則,就建立一個BpExecService
物件返回(我們通過BpExecService::exec
就能進行IPC呼叫了)。