2015年1月30日 星期五

Using Binder to Vibrate - Android Binder 實戰 (Client Side, First Part)

    這次要示範的是如何使用 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 層提到的比較少,然後作者有時候用的比喻台灣人可能比較難以理解(笑。

   

沒有留言:

張貼留言