1. 前言
Framework是一個中間層,它對接了底層實作,封裝了複雜的内部邏輯,并提供供外部使用的接口。Framework層是應用程式開發的基礎。
Binder Framework層分為C++和Java兩個部分,為了達到功能的複用,中間通過JNI進行銜接。
Binder Framework的C++部分,頭檔案位于這個路徑:
/frameworks/native/include/binder/
實作位于這個路徑:
/frameworks/native/libs/binder/
Binder庫最終會編譯成一個動态連結庫:libbinder.so,供其他程序連結使用。
為了便于說明,下文中我們将Binder Framework 的C++部分稱之為libbinder。
2. 主要結構
libbinder中,将實作分為Proxy和Native兩端。Proxy對應了上文提到的Client端,是服務對外提供的接口。而Native是服務實作的一端,對應了上文提到的Server端。類名中帶有小寫字母p的(例如BpInterface),就是指Proxy端。類名帶有小寫字母n的(例如BnInterface),就是指Native端。
Proxy代表了調用方,通常與服務的實作不在同一個程序,是以下文中,我們也稱Proxy端為“遠端”端。Native端是服務實作的自身,是以下文中,我們也稱Native端為”本地“端。
這裡,我們先對libbinder中的主要類做一個簡要說明,了解一下它們的關系,然後再詳細的講解。
類名 說明
----------------------------------------------------------------------------------
BpRefBase RefBase的子類,提供remote()方法擷取遠端Binder
IInterface Binder服務接口的基類,Binder服務通常需要同時提供本地接口和遠端接口
BpInterface 遠端接口的基類,遠端接口是供用戶端調用的接口集
BnInterface 本地接口的基類,本地接口是需要服務中真正實作的接口集
IBiner Binder對象的基類,BBinder和BpBinder都是這個類的子類
BpBinder 遠端Binder,這個類提供transact方法來發送請求,BpXXX實作中會用到
BBinder 本地Binder,服務實作方的基類,提供了onTransact接口來接收請求
ProcessState 代表了使用Binder的程序
IPCThreadState 代表了使用Binder的線程,這個類中封裝了與Binder驅動通信的邏輯
Parcel 在Binder上傳遞的資料的包裝器
下圖描述了這些類之間的關系:
另外說明一下,Binder服務的實作類(圖中紫色部分)通常都會遵守下面的命名規則:
a. 服務的接口使用I字母作為字首
b. 遠端接口使用Bp作為字首
c. 本地接口使用Bn作為字首

看了上面這些介紹,你可能還是不太容易了解。不過不要緊,下面我們會逐漸拆分講解這些内容。
在這幅圖中,淺黃色部分的結構是最難了解的,是以我們先從它們着手。
我們先來看看IBinder這個類。這個類描述了所有在Binder上傳遞的對象,它既是Binder本地對象BBinder的父類,也是Binder遠端對象BpBinder的父類。這個類中的主要方法說明如下:
方法名 說明
-------------------------------------------------------------------------------
localBinder 擷取本地Binder對象
remoteBinder 擷取遠端Binder對象
transact 進行一次Binder操作
queryLocalInterface 嘗試擷取本地Binder,如何失敗傳回NULL
getInterfaceDescriptor 擷取Binder的服務接口描述,其實就是Binder服務的唯一辨別
isBinderAlive 查詢Binder服務是否還活着
pingBinder 發送PING_TRANSACTION給Binder服務
BpBinder的執行個體代表了遠端Binder,這個類的對象将被用戶端調用。其中handle方法會傳回指向Binder服務實作者的句柄,這個類最重要就是提供了transact方法,這個方法會将遠端調用的參數封裝好發送的Binder驅動。
由于每個Binder服務通常都會提供多個服務接口,而這個方法中的uint32_t code參數就是用來對服務接口進行編号區分的。Binder服務的每個接口都需要指定一個唯一的code,這個code要在Proxy和Native端配對好。當用戶端将請求發送到服務端的時候,服務端根據這個code(onTransact方法中)來區分調用哪個接口方法。
BBinder的執行個體代表了本地Binder,它描述了服務的提供方,所有Binder服務的實作者都要繼承這個類(的子類),在繼承類中,最重要的就是實作onTransact方法,因為這個方法是所有請求的入口。是以,這個方法是和BpBinder中的transact方法對應的,這個方法同樣也有一個uint32_t code參數,在這個方法的實作中,由服務提供者通過code對請求的接口進行區分,然後調用具體實作服務的方法。
IBinder中定義了uint32_t code允許的範圍:
FIRST_CALL_TRANSACTION = 0x00000001,
LAST_CALL_TRANSACTION = 0x00ffffff,
Binder服務要保證自己提供的每個服務接口有一個唯一的code,例如某個Binder服務可以将:add接口code設為1,minus接口code設為2,multiple接口code設為3,divide接口code設為4,等等。
講完了IBinder,BpBinder和BBinder三個類,我們再來看看BpReBase,IInterface,BpInterface和BnInterface。
每個Binder服務都是為了某個功能而實作的,是以其本身會定義一套接口集(通常是C++的一個類)來描述自己提供的所有功能。而Binder服務既有自身實作服務的類,也要有給用戶端程序調用的類。為了便于開發,這兩中類裡面的服務接口應當是一緻的,例如:假設服務實作方提供了一個接口為add(int a, int b)的服務方法,那麼其遠端接口中也應當有一個add(int a, int b)方法。是以為了實作友善,本地實作類和遠端接口類需要有一個公共的描述服務接口的基類(即上圖中的IXXXService)來繼承。而這個基類通常是IInterface的子類,IInterface的定義如下:
class IInterface : public virtual RefBase
{
public:
IInterface();
static sp<IBinder> asBinder(const IInterface*);
static sp<IBinder> asBinder(const sp<IInterface>&);
protected:
virtual ~IInterface();
virtual IBinder* onAsBinder() = 0;
};
之是以要繼承自IInterface類是因為這個類中定義了onAsBinder讓子類實作。onAsBinder在本地對象的實作類中傳回的是本地對象,在遠端對象的實作類中傳回的是遠端對象。onAsBinder方法被兩個靜态方法asBinder方法調用。有了這些接口之後,在代碼中便可以直接通過IXXX::asBinder方法擷取到不用區分本地還是遠端的IBinder對象。這個在跨程序傳遞Binder對象的時候有很大的作用(因為不用區分具體細節,隻要直接調用和傳遞就好)。
下面,我們來看一下BpInterface和BnInterface的定義:
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;
protected:
virtual IBinder* onAsBinder();
};
// ----------------------------------------------------------------------
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);
protected:
virtual IBinder* onAsBinder();
};
這兩個類都是模闆類,它們在繼承自INTERFACE的基礎上各自繼承了另外一個類。這裡的INTERFACE便是我們Binder服務接口的基類。另外,BnInterface繼承了BBinder類,由此可以通過複寫onTransact方法來提供實作。BpInterface繼承了BpRefBase,通過這個類的 remote 方法可以擷取到指向服務實作方的句柄。在用戶端接口的實作類中,每個接口在組裝好參數之後,都會調用remote()->transact來發送請求,而這裡其實就是調用的BpBinder的transact方法,這樣請求便通過Binder到達了服務實作方的onTransact中。這個過程如下圖所示:
基于Binder架構開發的服務,除了滿足上文提到的類名規則之外,還需要遵守其他一些共同的規約:
(1) 為了進行服務的區分,每個Binder服務需要指定一個唯一的辨別,這個辨別通過 getInterfaceDescriptor 傳回,類型是一個字元串。通常,Binder服務會在類中定義 static const android::String16 descriptor; 這樣一個常量來描述這個辨別符,然後在 getInterfaceDescriptor 方法中傳回這個常量。
(2) 為了便于調用者擷取到調用接口,服務接口的公共基類需要提供一個 android::sp<IXXX> asInterface 方法來傳回基類對象指針。
由于上面提到的這兩點對于所有Binder服務的實作邏輯都是類似的。為了簡化開發者的重複工作,在libbinder中,定義了兩個宏來簡化這些重複工作,它們是:
#define DECLARE_META_INTERFACE(INTERFACE) \
static const android::String16 descriptor; \
static android::sp<I##INTERFACE> asInterface( \
const android::sp<android::IBinder>& obj); \
virtual const android::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE(); \
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const android::String16 I##INTERFACE::descriptor(NAME); \
const android::String16& \
I##INTERFACE::getInterfaceDescriptor() const { \
return I##INTERFACE::descriptor; \
} \
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL) { \
intr = new Bp##INTERFACE(obj); \
} \
} \
return intr; \
} \
I##INTERFACE::I##INTERFACE() { } \
I##INTERFACE::~I##INTERFACE() { } \
有了這兩個宏之後,開發者隻要在接口基類(IXXX)頭檔案中,使用 DECLARE_META_INTERFACE 宏便完成了需要的元件的聲明。然後在cpp檔案中使用 IMPLEMENT_META_INTERFACE 便完成了這些元件的實作。
3. Binder的初始化
在講解Binder驅動的時候我們就提到:任何使用Binder機制的程序都必須要對/dev/binder裝置進行open以及mmap之後才能使用,這部分邏輯是所有使用Binder機制程序共同的。對于這種共同邏輯的封裝便是Framework層的職責之一。libbinder中,ProcessState類封裝了這個邏輯,相關代碼見下文。
這裡是ProcessState構造函數,在這個函數中,初始化mDriverFD的時候調用了open_driver方法打開binder裝置,然後又在函數體中,通過mmap進行記憶體映射。
ProcessState::ProcessState()
: mDriverFD(open_driver())
, mVMStart(MAP_FAILED)
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mStarvationStartTimeMs(0)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
{
if (mDriverFD >= 0) {
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
// *sigh*
ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
close(mDriverFD);
mDriverFD = -1;
}
}
LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened. Terminating.");
}
open_driver的函數實作如下所示。在這個函數中完成了三個工作:
a. 首先通過open系統調用打開了dev/binder裝置
b. 然後通過ioctl擷取Binder實作的版本号,并檢查是否比對
c. 最後通過ioctl設定程序支援的最大線程數量
關于這部分邏輯背後的處理,在講解Binder驅動的時候,我們已經講解過了。
static int open_driver()
{
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC); //open
if (fd >= 0) {
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers); //擷取binder驅動版本号
if (result == -1) {
ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
close(fd);
fd = -1;
}
if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
ALOGE("Binder driver protocol does not match user space protocol!");
close(fd);
fd = -1;
}
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); //設定max threads
if (result == -1) {
ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
}
} else {
ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
}
return fd;
}
ProcessState是一個Singleton(單例)類型的類,在一個程序中,隻會存在一個執行個體。通過ProcessState::self()接口擷取這個執行個體。一旦擷取這個執行個體,便會執行其構造函數,由此完成了對于Binder裝置的初始化工作。
4. 關于Binder傳遞資料的大小限制
由于Binder的資料需要跨程序傳遞,并且還需要在核心上開辟空間,是以允許在Binder上傳遞的資料并不是無無限大的。mmap中指定的大小便是對資料傳遞的大小限制:
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) // 1M - 8k
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
這裡我們看到,在進行mmap的時候,指定了最大size為BINDER_VM_SIZE,即 1M - 8k的大小。 是以我們在開發過程中,一次Binder調用的資料總和不能超過這個大小。
對于這個區域的大小,我們也可以在裝置上進行确認。這裡我們還之前提到的 system_server 為例。上面我們講解了通過procfs來擷取映射的記憶體位址,除此之外,我們也可以通過 showmap 指令,來确定這塊區域的大小,相關指令如下:
angler:/ # ps | grep system_server
system 1889 526 2353404 135968 SyS_epoll_ 72972eeaf4 S system_server
angler:/ # showmap 1889 | grep "/dev/binder"
1016 4 4 0 0 4 0 0 1 /dev/binder
這裡可以看到,這塊區域的大小正是 1M - 8K = 1016k。
注: 通過showmap指令可以看到程序的詳細記憶體占用情況。在實際的開發過程中,當我們要對某個程序做記憶體占用分析的時候,這個指令是相當有用的。建議讀者嘗試通過showmap指令檢視system_server或其他感興趣程序的完整map,看看這些程序都依賴了哪些庫或者子產品,以及記憶體占用情況是怎樣的。
5. 與驅動的通信
上文提到 ProcessState 是一個單例類,一個程序隻有一個執行個體。而負責與Binder驅動通信的 IPCThreadState 也是一個單例類。但這個類不是一個程序隻有一個執行個體,而是一個線程有一個執行個體。
IPCThreadState 負責了與驅動通信的細節處理。這個類中的關鍵幾個方法說明如下:
方法 說明
-----------------------------------------------------------------------
transact 公開接口。供Proxy發送資料到驅動,并讀取傳回結果
sendReply 供Server端寫回請求的傳回結果
waitForResponse 發送請求後等待響應結果
talkWithDriver 通過ioctl BINDER_WRITE_READ來與驅動通信
writeTransactionData 寫入一次事務的資料
executeCommand 處理binder_driver_return_protocol協定指令
freeBuffer 通過BC_FREE_BUFFER指令釋放Buffer
BpBinder::transact 方法在發送請求的時候,其實就是直接調用了 IPCThreadState 對應的方法來發送請求到Binder驅動的,相關代碼如下:
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
if (mAlive) {
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
而IPCThreadState::transact方法主要邏輯如下:
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
status_t err = data.errorCheck();
flags |= TF_ACCEPT_FDS;
if (err == NO_ERROR) {
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
err = waitForResponse(NULL, NULL);
}
return err;
}
這段代碼應該還是比較好了解的:首先通過 writeTransactionData 寫入資料,然後通過 waitForResponse 等待傳回結果。TF_ONE_WAY 表示此次請求是單向的,即:不用真正等待結果即可傳回。
而 writeTransactionData 方法其實就是在組裝 binder_transaction_data 資料,并沒有與驅動進行互動進行發送:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
} else if (statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
對于binder_transaction_data在講解Binder驅動的時候我們已經詳細講解過了。而這裡的Parcel我們還不了解,那麼接下來我們馬上就來看一下這個類。
6. 資料包裝器:Parcel
Binder上提供的是跨程序的服務,每個服務包含了不同的接口,每個接口的參數數量和類型都不一樣。那麼當用戶端想要調用服務端的接口,參數是如何跨程序傳遞給服務端的呢?除此之外,服務端想要給用戶端傳回結果,結果又是如何傳遞回來的呢?
這些問題的答案就是:Parcel。Parcel就像一個包裝器,調用者可以以任意順序往裡面放入需要的資料,所有寫入的資料就像是被打成一個整體的包,然後可以直接在Binde上傳輸。
Parcel提供了所有基本類型的寫入和讀出接口,下面是其中的一部分:
...
status_t writeInt32(int32_t val);
status_t writeUint32(uint32_t val);
status_t writeInt64(int64_t val);
status_t writeUint64(uint64_t val);
status_t writeFloat(float val);
status_t writeDouble(double val);
status_t writeCString(const char* str);
status_t writeString8(const String8& str);
status_t readInt32(int32_t *pArg) const;
uint32_t readUint32() const;
status_t readUint32(uint32_t *pArg) const;
int64_t readInt64() const;
status_t readInt64(int64_t *pArg) const;
uint64_t readUint64() const;
status_t readUint64(uint64_t *pArg) const;
float readFloat() const;
status_t readFloat(float *pArg) const;
double readDouble() const;
status_t readDouble(double *pArg) const;
intptr_t readIntPtr() const;
status_t readIntPtr(intptr_t *pArg) const;
bool readBool() const;
status_t readBool(bool *pArg) const;
char16_t readChar() const;
status_t readChar(char16_t *pArg) const;
int8_t readByte() const;
status_t readByte(int8_t *pArg) const;
// Read a UTF16 encoded string, convert to UTF8
status_t readUtf8FromUtf16(std::string* str) const;
status_t readUtf8FromUtf16(std::unique_ptr<std::string>* str) const;
const char* readCString() const;
...
是以對于基本類型,開發者可以直接調用接口寫入和讀出。而對于非基本類型,需要由開發者将其拆分成基本類型然後寫入到Parcel中(讀出的時候也是一樣)。 Parcel會将所有寫入的資料進行打包,Parcel本身可以作為一個整體在程序間傳遞。接收方在收到Parcel之後,隻要按寫入同樣的順序讀出即可。
這個過程,和我們現實生活中寄送包裹做法是一樣的:我們将需要寄送的包裹放到硬紙盒中交給快遞公司。快遞公司将所有的包裹進行打包,然後集中放到運輸車中送到目的地,到了目的地之後然後再進行拆分。
Parcel既包含C++部分的實作,也同時提供了Java的接口,中間通過JNI銜接。Java層的接口其實僅僅是一層包裝,真正的實作都是位于C++部分中,它們的關系如下圖所示:
特别需要說明一下的是,Parcel類除了可以傳遞基本資料類型,還可以傳遞Binder對象:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
這個方法寫入的是sp<IBinder>類型的對象,而IBinder既可能是本地Binder,也可能是遠端Binder,這樣我們就不可以不用關心具體細節直接進行Binder對象的傳遞。
這也是為什麼IInterface中定義了兩個asBinder的static方法,如果你不記得了,請回憶一下這兩個方法:
static sp<IBinder> asBinder(const IInterface*);
static sp<IBinder> asBinder(const sp<IInterface>&);
而對于Binder驅動,我們前面已經講解過:Binder驅動并不是真的将對象在程序間序列化傳遞,而是由Binder驅動完成了對于Binder對象指針的解釋和翻譯,使調用者看起來就像在程序間傳遞對象一樣。
7. Framework層的線程管理
在講解Binder驅動的時候,我們就講解過驅動中對應線程的管理。這裡我們再來看看,Framework層是如何與驅動層對接進行線程管理的。
ProcessState::setThreadPoolMaxThreadCount 方法中,會通過 BINDER_SET_MAX_THREADS 指令設定程序支援的最大線程數量:
#define DEFAULT_MAX_BINDER_THREADS 15
status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) { //open binder時已經set過一次了
status_t result = NO_ERROR;
if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) != -1) {
mMaxThreads = maxThreads;
} else {
result = -errno;
ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
}
return result;
}
由此驅動便知道了該Binder服務支援的最大線程數。驅動在運作過程中,會根據需要,并在沒有超過上限的情況下,通過 BR_SPAWN_LOOPER 指令通知程序建立線程:
IPCThreadState 在收到 BR_SPAWN_LOOPER 請求之後,便會調用 ProcessState::spawnPooledThread 來建立線程,此方法負責為線程設定名稱并建立線程:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
...
case BR_SPAWN_LOOPER:
mProcess->spawnPooledThread(false);
break;
...
}
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
String8 name = makeBinderThreadName();
ALOGV("Spawning new pooled thread, name=%s\n", name.string());
sp<Thread> t = new PoolThread(isMain);
t->run(name.string());
}
}
線程在run之後,會調用threadLoop将自身添加的線程池中:
virtual bool threadLoop()
{
IPCThreadState::self()->joinThreadPool(mIsMain);
return false;
}
而 IPCThreadState::joinThreadPool 方法中,會根據目前線程是否是主線程發送 BC_ENTER_LOOPER 或者 BC_REGISTER_LOOPER 指令告知驅動線程已經建立完畢。整個調用流程如下圖所示:
8. C++ Binder服務舉例
單純的理論知識也許并不能讓我們非常好的了解,下面我們以一個具體的Binder服務例子來結合上文的知識進行講解。
下面以PowerManager為例,來看看C++的Binder服務是如何實作的。
下圖是PowerManager C++部分的實作類圖(PowerManager也有Java層的接口,但我們這裡就不讨論了)。
圖中Binder Framework中的類我們在上文中已經介紹過了,而PowerManager相關的四個類,便是在Framework的基礎上開發的。
IPowerManager 定義了 PowerManager 所有對外提供的功能接口,其子類都繼承了這些接口。
a. BpPowerManager是提供給用戶端調用的遠端接口
b. BnPowerManager中隻有一個onTransact方法,該方法根據請求的code來對接每個請求,并直接調用PowerManager中對應的方法
c. PowerManager是服務真正的實作
在 IPowerManager.h 中,通過 DECLARE_META_INTERFACE(PowerManager);聲明一些Binder必要的元件。在 IPowerManager.cpp 中,通過 IMPLEMENT_META_INTERFACE(PowerManager, "android.os.IPowerManager");宏來進行實作。
9. 本地實作:Native端
服務的本地實作主要就是實作 BnPowerManager 和 PowerManager 兩個類,PowerManager 是 BnPowerManager 的子類,是以在 BnPowerManager 中調用自身的virtual方法其實都是在子類PowerManager類中實作的。
BnPowerManager 類要做的就是複寫 onTransact 方法,這個方法的職責是:根據請求的code區分具體調用的是那個接口,然後按順序從Parcel中讀出打包好的參數,接着調用留待子類實作的虛函數。 需要注意的是:這裡從Parcel讀出參數的順序需要和BpPowerManager中寫入的順序完全一緻,否則讀出的資料将是無效的。
電源服務包含了好幾個接口。雖然每個接口的實作邏輯各不一樣,但從Binder架構的角度來看,它們的實作結構是一樣。而這裡我們并不關心電源服務的實作細節,是以我們取其中一個方法看其實作方式即可。
首先我們來看一下 BnPowerManager::onTransact 中的代碼片段:
status_t BnPowerManager::onTransact(uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags) {
switch (code) {
...
case IPowerManager::REBOOT: {
CHECK_INTERFACE(IPowerManager, data, reply);
bool confirm = data.readInt32();
String16 reason = data.readString16();
bool wait = data.readInt32();
return reboot(confirm, reason, wait);
}
...
}
}
這段代碼中我們看到了實作中是如何根據code區分接口,并通過Parcel讀出調用參數,然後調用具體服務方的函數。
而PowerManager這個類才真正是服務實作的本體,reboot方法真正實作了重新開機的邏輯:
status_t PowerManager::reboot(bool confirm, const String16& reason, bool wait) {
const std::string reason_str(String8(reason).string());
if (!(reason_str.empty() || reason_str == kRebootReasonRecovery)) {
LOG(WARNING) << "Ignoring reboot request with invalid reason \""
<< reason_str << "\"";
return BAD_VALUE;
}
LOG(INFO) << "Rebooting with reason \"" << reason_str << "\"";
if (!property_setter_->SetProperty(ANDROID_RB_PROPERTY,
kRebootPrefix + reason_str)) {
return UNKNOWN_ERROR;
}
return OK;
}
通過這樣結構的設計,将架構相關的邏輯(BnPowerManager中的實作)和業務本身的邏輯(PowerManager中的實作)徹底分離開了,保證每一個類都非常的“幹淨”(架構相關的放在父類中,邏輯功能放在子類中),這一點是很值得我們在做軟體設計時學習的。
10. 服務的釋出
服務實作完成之後,并不是立即就能讓别人使用的。上文中,我們就說到過:所有在Binder上釋出的服務必須要注冊到 ServiceManager 中才能被其他子產品擷取和使用。而在 BinderService 類中,提供了 publishAndJoinThreadPool 方法來簡化服務的釋出,其代碼如下:
static void publishAndJoinThreadPool(bool allowIsolated = false) {
publish(allowIsolated);
joinThreadPool();
}
static status_t publish(bool allowIsolated = false) {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated);
}
...
static void joinThreadPool() {
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool(); //TODO: 做了啥?
ps->giveThreadPoolName();
IPCThreadState::self()->joinThreadPool();
}
由此可見,Binder服務的釋出其實有三個步驟:
(1) 通過 IServiceManager::addService 在 ServiceManager 中進行服務的注冊
(2) 通過 ProcessState::startThreadPool 啟動線程池
(3) 通過 IPCThreadState::joinThreadPool 将主線程加入的Binder中
11. 遠端接口:Proxy端
Proxy 類是供用戶端使用的。BpPowerManager 需要實作 IPowerManager 中的所有接口。
我們還是以上文提到的reboot接口為例,來看看 BpPowerManager::reboot 方法是如何實作的:
virtual status_t reboot(bool confirm, const String16& reason, bool wait)
{
Parcel data, reply;
data.writeInterfaceToken(IPowerManager::getInterfaceDescriptor());
data.writeInt32(confirm);
data.writeString16(reason);
data.writeInt32(wait);
return remote()->transact(REBOOT, data, &reply, 0);
}
這段代碼很簡單,邏輯就是:通過Parcel寫入調用參數進行打包,然後調用remote()->transact将請求發送出去。
其實 BpPowerManager 中其他方法,甚至所有其他 BpXXX 中所有的方法,實作都是和這個方法一樣的套路。就是:通過 Parcel 打包資料,通過 remote()->transact 發送資料。而這裡的 remote() 傳回的其實就是 BpBinder 對象,由此經由 IPCThreadState 将資料發送到了驅動層。如果你已經不記得,請重新看一下下面這幅圖:
另外,需要一下的是,這裡的 REBOOT 就是請求的 code,而這個 code 是在 IPowerManager 中定義好的,這樣子類可以直接使用,并保證是一緻的:
enum {
ACQUIRE_WAKE_LOCK = IBinder::FIRST_CALL_TRANSACTION,
ACQUIRE_WAKE_LOCK_UID = IBinder::FIRST_CALL_TRANSACTION + 1,
RELEASE_WAKE_LOCK = IBinder::FIRST_CALL_TRANSACTION + 2,
UPDATE_WAKE_LOCK_UIDS = IBinder::FIRST_CALL_TRANSACTION + 3,
POWER_HINT = IBinder::FIRST_CALL_TRANSACTION + 4,
UPDATE_WAKE_LOCK_SOURCE = IBinder::FIRST_CALL_TRANSACTION + 5,
IS_WAKE_LOCK_LEVEL_SUPPORTED = IBinder::FIRST_CALL_TRANSACTION + 6,
USER_ACTIVITY = IBinder::FIRST_CALL_TRANSACTION + 7,
WAKE_UP = IBinder::FIRST_CALL_TRANSACTION + 8,
GO_TO_SLEEP = IBinder::FIRST_CALL_TRANSACTION + 9,
NAP = IBinder::FIRST_CALL_TRANSACTION + 10,
IS_INTERACTIVE = IBinder::FIRST_CALL_TRANSACTION + 11,
IS_POWER_SAVE_MODE = IBinder::FIRST_CALL_TRANSACTION + 12,
SET_POWER_SAVE_MODE = IBinder::FIRST_CALL_TRANSACTION + 13,
REBOOT = IBinder::FIRST_CALL_TRANSACTION + 14,
SHUTDOWN = IBinder::FIRST_CALL_TRANSACTION + 15,
CRASH = IBinder::FIRST_CALL_TRANSACTION + 16,
};
12. 服務的擷取
在服務已經釋出之後,用戶端該如何擷取其服務接口然後對其送出請求調用呢?
很顯然,用戶端應該通過BpPowerManager的對象來請求其服務。但看一眼BpPowerManager的構造函數,我們會發現,似乎沒法直接建立一個這類的對象,因為這裡需要一個sp<IBinder>類型的參數。
BpPowerManager(const sp<IBinder>& impl) : BpInterface<IPowerManager>(impl)
{
}
那麼這個sp<IBinder>參數我們該從哪裡擷取呢?
回憶一下前面的内容:Proxy其實是包含了一個指向Server的句柄,所有的請求發送出去的時候都需要包含這個句柄作為一個辨別。而想要拿到這個句柄,我們自然應當想到ServiceManager。我們再看一下ServiceManager的接口自然就知道這個sp<IBinder>該如何擷取了:。
/**
* Retrieve an existing service, blocking for a few seconds
* if it doesn't yet exist.
*/
virtual sp<IBinder> getService( const String16& name) const = 0;
/**
* Retrieve an existing service, non-blocking.
*/
virtual sp<IBinder> checkService( const String16& name) const = 0;
這裡的兩個方法都可以擷取服務對應的sp<IBinder>對象,一個是阻塞式的,另外一個不是。傳遞的參數是一個字元串,這個就是服務在addServer時對應的字元串,而對于PowerManager來說,這個字元串就是”power”。是以,我們可以通過下面這行代碼建立出BpPowerManager的對象。
sp<IBinder> bs = defaultServiceManager()->checkService(serviceName);
sp<IPowerManager> pm = new BpPowerManager(bs);
但這樣做還會存在一個問題:BpPowerManager中的方法調用是經由驅動然後跨程序調用的。通常情況下,當我們的用戶端與PowerManager服務所在的程序不是同一個程序的時候,這樣調用是沒有問題的。那假設我們的用戶端又剛好和PowerManager服務在同一個程序該如何處理呢?
針對這個問題,Binder Framework提供的解決方法是:通過interface_cast這個方法來擷取服務的接口對象,由這個方法本身根據是否是在同一個程序,來自動确定傳回一個本地Binder還是遠端Binder。interface_cast是一個模闆方法,其源碼如下:
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
調用這個方法的時候我們需要指定Binder服務的IInterface,是以對于PowerManager,我們需要這樣擷取其Binder接口對象:
const String16 serviceName("power");
sp<IBinder> bs = defaultServiceManager()->checkService(serviceName);
if (bs == NULL) {
return NAME_NOT_FOUND;
}
sp<IPowerManager> pm = interface_cast<IPowerManager>(bs);
我們再回頭看一下 interface_cast 這個方法體,這裡是在調用 INTERFACE::asInterface(obj),而對于 IPowerManager 來說,其實就是 IPowerManager::asInterface(obj)。那麼 IPowerManager::asInterface 這個方法是哪裡定義的呢?
這個正是上文提到的 DECLARE_META_INTERFACE 和 IMPLEMENT_META_INTERFACE 兩個宏所起的作用。IMPLEMENT_META_INTERFACE 宏包含了下面這段代碼:
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL) { \
intr = new Bp##INTERFACE(obj); \
} \
} \
return intr; \
} \
這裡我們将“##INTERFACE”通過“PowerManager”代替,得到的結果就是:
android::sp<IPowerManager> IPowerManager::asInterface(
const android::sp<android::IBinder>& obj)
{
android::sp<IPowerManager> intr;
if (obj != NULL) {
intr = static_cast<IPowerManager*>(
obj->queryLocalInterface(
IPowerManager::descriptor).get());
if (intr == NULL) {
intr = new BpPowerManager(obj);
}
}
return intr;
}
這個便是 IPowerManager::asInterface 方法的實作,這段邏輯的含義就是:
a. 先嘗試通過 queryLocalInterface 看看能夠獲得本地Binder,如果是在服務所在程序調用,自然能擷取本地Binder,否則将傳回NULL
b. 如果擷取不到本地Binder,則建立并傳回一個遠端Binder。
由此保證了:我們在程序内部的調用,是直接通過方法調用的形式。而不在同一個程序的時候,才通過Binder進行跨程序的調用。
13. C++層的ServiceManager
前文已經兩次介紹過 ServiceManager 了,我們知道這個子產品負責了所有Binder服務的管理,并且也看到了Binder驅動中對于這個子產品的實作。可以說 ServiceManager 是整個 Binder IPC 的控制中心和交通樞紐。這裡我們就來看一下這個子產品的具體實作。
ServiceManager 是一個獨立的可執行檔案,在裝置中的程序名稱是 /system/bin/servicemanager,這個也是其可執行檔案的路徑。
ServiceManager 實作源碼的位于這個路徑:frameworks/native/cmds/servicemanager/,其main函數的主要内容如下:
int main()
{
struct binder_state *bs;
bs = binder_open(128*1024);
if (!bs) {
ALOGE("failed to open binder driver\n");
return -1;
}
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
...
binder_loop(bs, svcmgr_handler);
return 0;
}
這段代碼很簡單,主要做了三件事情:
a. binder_open(128*1024); 是打開Binder,并指定緩存大小為128k,由于ServiceManager提供的接口很簡單(下文會講到),是以并不需要普通程序那麼多(1M - 8K)的緩存。
b. binder_become_context_manager(bs) 使自己成為Context Manager。這裡的Context Manager是Binder驅動裡面的名稱,等同于ServiceManager。binder_become_context_manager的方法實作隻有一行代碼:ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0); 看過Binder驅動部分解析的内容,這行代碼應該很容易了解了。
c. binder_loop(bs, svcmgr_handler); 是在Looper上循環,等待其他子產品請求服務。
service_manager.c中的實作與普通Binder服務的實作有些不一樣:并沒有通過繼承接口類來實作,而是通過幾個c語言的函數來完成了實作。這個檔案中的主要方法如下:
方法名稱 方法說明
------------------------------------------------------
main 可執行檔案入口函數,剛剛已經做過說明
svcmgr_handler 請求的入口函數,類似于普通Binder服務的onTransact
do_add_service 注冊一個Binder服務
do_find_service 通過名稱查找一個已經注冊的Binder服務
ServiceManager中,通過svcinfo結構體來描述已經注冊的Binder服務(Android11及以後就不是了):
struct svcinfo
{
struct svcinfo *next;
uint32_t handle;
struct binder_death death;
int allow_isolated;
size_t len;
uint16_t name[0];
};
next是一個指針,指向下一個服務,通過這個指針将所有服務串成了連結清單。handle是指向Binder服務的句柄,這個句柄是由Binder驅動翻譯,指向了Binder服務的實體(參見驅動中:Binder中的“面向對象”),name是服務的名稱。
ServiceManager的實作邏輯并不複雜,這個子產品就好像在整個系統上提供了一個全局的HashMap而已:通過服務名稱進行服務注冊,然後再通過服務名稱來查找。而真正複雜的邏輯其實都是在Binder驅動中實作了。
14. ServiceManager的接口
源碼路徑:
frameworks/native/include/binder/IServiceManager.h
frameworks/native/libs/binder/IServiceManager.cpp
ServiceManager的C++接口定義如下:
class IServiceManager : public IInterface
{
public:
DECLARE_META_INTERFACE(ServiceManager);
virtual sp<IBinder> getService( const String16& name) const = 0;
virtual sp<IBinder> checkService( const String16& name) const = 0;
virtual status_t addService( const String16& name,
const sp<IBinder>& service,
bool allowIsolated = false) = 0;
virtual Vector<String16> listServices() = 0;
enum {
GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
CHECK_SERVICE_TRANSACTION,
ADD_SERVICE_TRANSACTION,
LIST_SERVICES_TRANSACTION,
};
};
這裡我們看到,ServiceManager 提供的接口隻有四個,這四個接口說明如下:
接口名稱 接口說明
------------------------------------------------------
addService 向ServiceManager中注冊一個新的Service
getService 查詢Service。如果服務不存在,将阻塞數秒
checkService 查詢Service,但是不會阻塞
listServices 列出所有的服務
這其中,最後一個接口是為了調試而提供的。通過adb shell連接配接到裝置上之後,可以通過輸入 service list 輸出所有注冊的服務清單。這裡”service”可執行檔案其實就是通過調用 listServices 接口擷取到服務清單的。
service指令的源碼路徑在這裡:frameworks/native/cmds/service
service list的輸出看起來像下面這樣(一次輸出可能有一百多個服務,這裡省略了):
255|angler:/ # service list
Found 125 services:
0 sip: [android.net.sip.ISipService]
1 nfc: [android.nfc.INfcAdapter]
2 carrier_config: [com.android.internal.telephony.ICarrierConfigLoader]
3 phone: [com.android.internal.telephony.ITelephony]
4 isms: [com.android.internal.telephony.ISms]
...
普通的Binder服務我們需要通過 ServiceManager 來擷取接口才能調用,那麼 ServiceManager 的接口有如何獲得呢?在 libbinder 中,提供了一個 defaultServiceManager 方法來擷取 ServiceManager 的Proxy,并且這個方法不需要傳入參數。原因我們在驅動篇中也已經講過了:Binder的實作中,為 ServiceManager 留了一個特殊的位置,不需要像普通服務那樣通過辨別去查找。defaultServiceManager 代碼如下:
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
while (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(ProcessState::self()->getContextObject(NULL));
if (gDefaultServiceManager == NULL)
sleep(1);
}
}
return gDefaultServiceManager;
}
15. 結束語
本文我們詳細講解了Binder Framework C++層的實作。