這次要示範的是如何使用 AOSP 裡面的 Binder 相關 API,來跟
system_server 裏的
VibratorService 通訊,使用震動器。
我不是要解析 Binder 內部原理,解析 Android 原始碼的文章去 CSDN 隨便搜都一大堆(大陸人超喜歡解析原始碼),本文後面附的參考書籍也不錯,我在這邊單純只是要示範如何使用而已。以下講解的 App,已經放上 Github 了:
https://github.com/mshockwave/android-binder-demo-with-vibrator
架構
我本來的計劃是想直接載入 libhardware(或者 libhardware_legacy) 來操作 vibrator,但是想也知道:這種不用在 Manifest 聲明權限就可以使用硬體的好康,要是現在還存在的話 Google 早就不用玩了,process 本身權限也不夠。當然如果你有 root 權限的話,可以寫一個像是 MediaServer 一樣的 native service 來做前述的事。在不 root 的情況下,似乎就只剩下乖乖跟 system_server 進行通訊來使用 vibrator 了。
因此這個 App 就是製作一個「Native 版 AIDL 檔」來跟 system_server 裏的 VibratorService 溝通。而後面也會講到,事實上 AIDL 檔只是 Binder 的 Java 綁定的一個包裝罷了。下列文中會用到一些 AOSP 的 header files 或是 shared library,礙於篇幅關係,就先不在這邊說明如何引入了。
(一)Binder 初識
Binder 乍看之下還頗複雜的,但仔細一看才發現,看起來複雜的地方只是「包裝」而已,它核心的部分也是簡單的訊息傳遞。藉由哪邊傳遞訊息呢?答案是 kernel ,Binder 負責訊息傳遞的組件事實上是一個 kernel module,我剛開始非常好奇為什麼要搞那麼複雜,但後來有稍微理解了一點:kernel 的記憶體空間也是每個 process 共享的,跟 Sys V 的 shared memory IPC 道理一樣是共用記憶體。
再來我們要有的概念就是 Binder 可以分成「業務」以及「通訊」兩部分。上面剛提到的,Binder 的包裝看起來很複雜,事實上他裡面就是把業務以及通訊融合在一起,傳統的 IPC (像是我們熟悉的 Sys V 系列) 都是要手動將資料做讀寫,而 Binder 則是將物件導向裡的介面以及繼承發揮得淋漓盡致,把資料的讀寫全部藏起來。
(二)業務介面:IInterface
先來從最簡單的業務介面介紹起。(IMyVibrator.h)
class IMyVibrator : public IInterface {
public:
DECLARE_META_INTERFACE(MyVibrator);
virtual bool hasVibrator(void) = 0;
virtual void vibrate(int32_t uid,
String16& opPkg,
int64_t milliseconds, int32_t usageHint,
sp<IBinder>& token) = 0;
virtual void vibratePattern(int32_t uid,
String16& opPkg,
int64_t pattern[], int32_t repeat, int32_t usageHint,
sp<IBinder>& token) = 0;
virtual void cancelVibrate(sp<IBinder>& token) = 0;
};
請先忽略函式參數的意思。這邊定義的就是最終客戶端操作的介面,其中最有趣的莫過於 DECLARE_META_INTERFACE 這個 macro 了,他定義於 IInterface 裡,目前 5.0.0 (因為最近幾個版本 AOSP 檔案路徑變動很多) 座落在 frameworks/native/include/binder/IInterface.h。
#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(); \
上方只是先單純的做一些宣告,descriptor 顧名思義就是用來辨識這個 Binder 節點,要注意的是不要跟後面講到 ServiceManager 裡 addService/getService 的那個服務名稱搞混,這邊的 descriptor 是 Binder 驅動裡的辨識符,而後者的服務名稱只適用於 ServiceManager。asInterface 則是要等一下才會講到,但如果有用過 AIDL 的朋友就會想起,使用 AIDL 時也有 asInterface 這個函式 ( 例:IMyInterface.Stub.asInterface() ),而我在這邊就先告訴您:他們兩者做的事是一樣的。
(三)客戶端通訊介面:BpInterface
剛剛的業務介面可能會讓你一頭霧水,感覺沒在做什麼,但沒錯,就如字面上的意思:他只是個介面,所以先請耐心地看下去,看看負責通訊的 BpInterface 類。BpInterface 我們在這邊要知道的,首先是他繼承了 BpRefBase。
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);
protected:
virtual IBinder* onAsBinder();
};
而 BpRefBase 裡有一個成員變數 mRemote 以及跟他相關的 remote() 函式。
class BpRefBase : public virtual RefBase
{
protected:
BpRefBase(const sp<IBinder>& o);
virtual ~BpRefBase();
virtual void onFirstRef();
virtual void onLastStrongRef(const void* id);
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
inline IBinder* remote() { return mRemote; }
inline IBinder* remote() const { return mRemote; }
private:
BpRefBase(const BpRefBase& o);
BpRefBase& operator=(const BpRefBase& o);
IBinder* const mRemote;
RefBase::weakref_type* mRefs;
volatile int32_t mState;
};
我們透過 BpInterface 的建構子來設定 mRemote,這點請稍微留意,到時候則會呼叫 mRemote 的 transact() 函式來傳送處理過後的訊息,而這邊也是本篇文章討論 Binder 部分的最底層,再往下如何跟 Binder 驅動溝通,就留給各位了,您也可以參見本篇文章後面附上的參考書籍,裡面有非常詳盡的解說。
(三)通訊 + 業務: BpXXXX
到了這步驟,我們當然就要把業務以及通訊部分融合在一起啦。(MyVibratorProxy.cpp)
using namespace android;
//Client proxy interface
class BpMyVibrator : public BpInterface<IMyVibrator> {
public:
BpMyVibrator(const sp<IBinder>& remote) : BpInterface<IMyVibrator>(remote) {
ALOGD("BpMyVibrator constructor");
}
virtual bool hasVibrator(void) {
ALOGD("BpMyVibrator::hasVibrator invoked");
Parcel _data, _reply;
_data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
remote() -> transact(TRANSACTION_hasVibrator, _data, &_reply);
/*
* Must read the exception before fetching return data!!
* Since methods in AIDL file will always throw RemoteException
*/
ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
int retInt32 = _reply.readInt32();
//ALOGD("Return in int: %d", retInt32);
return (bool)(0 != retInt32);
}
virtual void vibrate(int32_t uid,
String16& opPkg,
int64_t milliseconds, int32_t usageHint,
sp<IBinder>& token) {
ALOGD("BpMyVibrator::vibrate invoked");
Parcel _data, _reply;
_data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
_data.writeInt32(uid);
_data.writeString16(opPkg);
_data.writeInt64(milliseconds);
_data.writeInt32(usageHint);
_data.writeStrongBinder(token);
remote() -> transact(TRANSACTION_vibrate, _data, &_reply);
//ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
}
virtual void vibratePattern(int32_t uid,
String16& opPkg,
int64_t pattern[], int32_t repeat, int32_t usageHint,
sp<IBinder>& token) {
ALOGD("BpMyVibrator::vibratePattern invoked");
Parcel _data, _reply;
_data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
_data.writeInt32(uid);
_data.writeString16(opPkg);
size_t len = (size_t)(sizeof(pattern) / sizeof(pattern[0]));
_data.write((const void*)pattern, len);
_data.writeInt32(repeat);
_data.writeInt32(usageHint);
_data.writeStrongBinder(token);
remote() -> transact(TRANSACTION_vibratePattern, _data, &_reply);
//ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
}
virtual void cancelVibrate(sp<IBinder>& token) {
ALOGD("BpMyVibrator::cancelVibrate invoked");
Parcel _data, _reply;
_data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
_data.writeStrongBinder(token);
remote() -> transact(TRANSACTION_cancelVibrate, _data, &_reply);
//ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
}
};
IMPLEMENT_META_INTERFACE(MyVibrator, "android.os.IVibratorService");
先從建構子開始看,前面有提到,我們是用建構子來傳遞通訊用的 IBinder 物件,所以一定要呼叫父建構子。接下來每一個函式都是我們在 IMyVibrator 裡宣告的業務函式,看看每一個函式都在做什麼?看起來就是把函式參數稍微處理一下,打包到
Parcel 型態的包裹裡,然後用前面提過的 remote() 與 transact() 函式傳送出去。到這邊您大概就猜的出來 BpInterface 的那個 "p" 是什麼的縮寫了:proxy。
沒錯,在 BpMyVibrator 裡我們做的工作就像一個 proxy,代理人,當客戶端在使用 IMyVibrator 時,其實他們是呼叫這些代理函式。還有另一個問題就是,Parcel 有提供像是 int32, float 等基本型態的封裝,那麼較複雜的物件呢?答案是你的類別必須實作 Parcelable 介面,把類別可以變成 Parcel 攜帶,這部分 Android 官方文件介紹的很清楚(雖然是 java 的 Parcelable,但大同小異),這邊就不提了。另外 transact() 函式第一個參數是用來辨識傳送給哪一個函式,要注意的是 AIDL 檔案在生成的時候,是照函式的順序由上而下編號的,例如我這次傳送的對象,IVibratorService,AIDL 檔 (位於 /frameworks/base/core/java/android/os/IVibratorService.aidl) 長這樣:
interface IVibratorService
{
boolean hasVibrator();
void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}
所以我在客戶端這邊的編號就要按照他的順序:(IMyVibrator.h)
#define TRANSACTION_hasVibrator (IBinder::FIRST_CALL_TRANSACTION + 0)
#define TRANSACTION_vibrate (IBinder::FIRST_CALL_TRANSACTION + 1)
#define TRANSACTION_vibratePattern (IBinder::FIRST_CALL_TRANSACTION + 2)
#define TRANSACTION_cancelVibrate (IBinder::FIRST_CALL_TRANSACTION + 3)
FIRST_CALL_TRANSACTION 預設的數值是 1。
在代理函式實作步驟裡面,有幾個重點。第一,Parcel 包以什麼順序寫入,在另一端就會以什麼順序解開,Parcel 並沒有像 Bundle 那樣是以 key-value 對儲存。第二,您應該有發現,傳送出去的包,第一個寫入的一定是前面提到的 descriptor,以供 Binder 辨識對象。第三,Parcel 原生支援打包 IBinder 物件,也就是
writeStrongBinder() 函式 (應該說是打包 strong pointer 型態的 IBinder,也就是 sp<IBinder>),因此可以看到 AOSP 裡常常把一個 IBinder 傳送給對方,當作單純的 token,就是不想再多創造一個 Parcelable 的物件,而多利用原生支持的 IBinder 傳送。第四,如上方程式碼註解的,如果對方有丟出 Exception,例如我們這個 App 因為對方是用 java 的 AIDL,一定會丟出 RemoteException,那我們
必須先調用 readExceptionCode() 讀取例外,接著才能讀取到正確的返回值 (我那時在這邊卡很久)。
在 MyVibratorProxy.cpp 最後一行有一個
IMPLEMENT_META_INTERFACE 的 macro,是不是很眼熟呢?沒錯,他就是 DECLARE_META_INTERFACE 的「另一半」,實作那些宣告。直接來看看他的內容:(也是座落於 IInterface.h)
#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() { } \
我們最困惑的
asInterface() 函式,終於在這邊獲得解答了。先看看第12行到第14行在做什麼吧,
queryLocalInterface 宣告在 IBinder 類別裡,他的註解說:
/**
* Check if this IBinder implements the interface named by
* descriptor. If it does, the base pointer to it is returned,
* which you can safely static_cast<> to the concrete C++ interface.
*/
看來就是說,如果這個「業務 + 通訊 綜合體」有實作 descriptor 對應的業務介面,就把該業務介面回傳。但是 BpInterface 與 BnInterface 這兩者貌似只有 BnInterface 有實作這個函式,因此那三行現在對我們來說是沒用的,會回傳 NULL。再看下去,發現原來 asInterface 在這邊的作用單純只是 new 一個 BpInterface!那新的 BpInterface 怎麼通訊?想起前面說要稍微留意一下 BpInterface 的建構子嗎?看看這邊,
obj 這個 IBinder 物件就是通訊用的,也就是會變成 BpInterface 中的 mRemote 值,可以透過 remote() 得到。這下您應該可以理解這小段的標題為什麼有一個
BpXXXX 了吧:一定要把 「業務 + 通訊 綜合體」命名成 BpXXXX 才行。
Summary
Binder 美的地方就在於他把 IPC 向上提升一個境界,將物件導向的概念融合於自身,Firefox OS(B2G) 也有使用這個觀念,他們使用 local socket,但是用一些 IDL(介面描述語言) 加以包裝,讓開發者與使用者不用再直接處理通訊的事情。
Second Part 將會分析比較 AIDL 與 此篇的原生 Binder 的實作,希望能夠更融會貫通,不管是 Native 對 Native 的通訊,還是如本 App 一樣 Native 對 Java 的通訊,都可以得心應手。
Book
《 深入理解 Android,卷 I 》(簡),鄧凡平,機械工業出版社,2011。
Note: 作者是 Android Media 領域的專家,所以有關 MediaServer 等等的部分書裡都介紹的不錯,唯一可惜的是 HAL 層提到的比較少,然後作者有時候用的比喻台灣人可能比較難以理解(笑。