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

   

2015年1月29日 星期四

How To Use Android Internal APIs

    When I was working on this demo(exercise, actually), I want to use the ServiceManager java class(which is hidden in Android SDK) since the demo is aim to build a simple native service which has a role like the system_server ( accessing some crucial system parts like hardware )except that it isn't live in the system_server process. So neither the bindService routine, which is designed for "App Service", nor the getService method, which is created for the services live in system_server, will work.

    Here are the steps:
  1. We need to build the AOSP, at least some specific framework parts, which located mostly in framework/base. You can go there and invoked the mma or mm command to build the module instead of the whole Android system.
       
  2. Assume your output dir is out, then go to out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/, copy the classes.jar to ${YOUR_SDK_DIR}/platforms/android-${YOUR_API_LEVEL}/ .
       
  3. In the dir ${YOUR_SDK_DIR}/platforms/android-${YOUR_API_LEVEL}, unpack both the classes.jar we've just copy and the android.jar since a jar file is simply an archive file.
       
  4. This step depends on your need. Since I only wants the ServiceManager class, I only have to copy content in the android folder in the output of the classes.jar, which holds the android.* classes we normally seen in the java import statements, into the corresponding android folder in the output of the android.jar. By doing this, you can still keep some libraries like dalvik.* in the origin SDK but not exist in the AOSP framework part.
       
  5. Change dir to the modified android.jar output dir. Repack them using the command:
    jar -cf ./your_new_android.jar *
    Remember to cascade your new android.jar file path RIGHT AFTER the -f option.
       
  6. Then you will got the new android.jar which can used in the App development. I also kept the original android.jar and wrote a simple script to switch between the origin one and the "Hacked" one:
    #!/bin/bash
    
    USAGE="Usage: ${0} <aosp|sdk>"
    
    case "$1" in 
    "aosp") ln -sf android_aosp.jar android.jar
     ;;
    
    "sdk") ln -sf android_sdk_origin.jar android.jar
     ;;
    *) echo $USAGE
     ;;
    
    esac
    
    exit 0
    
    In the old Eclipse era, we can add the new android.jar to the build path and raise the priority among the dependency libraries. However, since Android Studio is highly integrated with Android SDK, we can't modify the actual SDK files in the IDE.