Android Binder 全解析(3) -- AIDL原理剖析

IBinder 與 Binder

一個在同進(jìn)程的對象的抽象是 Object娱局,但這個對象是不能被跨進(jìn)程使用的,要想跨進(jìn)程使用,在 Android 中就必須依附于 Binder Framework。基于抽象設(shè)計的原理窍奋,Android系統(tǒng)將一個可遠(yuǎn)程操作的應(yīng)用定義為 IBinder,在這個接口中定義了一個可遠(yuǎn)程調(diào)用對象應(yīng)該具有的屬性和方法酱畅,在代碼中實際使用的Binder 也都是繼承自 IBinder 對象琳袄。我們接下來分析 IBinder 中定義了那些接口,是怎么抽象出可遠(yuǎn)程調(diào)用相關(guān)的方法的纺酸。

public interface IBinder {

    // 查看 binder 對應(yīng)的進(jìn)程是否存活
    public boolean pingBinder ();

    // 查看 binder 是否存活窖逗,需要注意的是,可能在返回的過程中餐蔬,binder 不可用
    public boolean isBinderAlive ();

    /**
     * 執(zhí)行一個對象的方法碎紊,
     *
     * @param 需要執(zhí)行的命令
     * @param 傳輸?shù)拿顢?shù)據(jù),這里一定不能為空
     * @param 目標(biāo)               Binder 返回的結(jié)果樊诺,可能為空
     * @param 操作方式仗考,0           等待 RPC 返回結(jié)果,1 單向的命令词爬,最常見的就是 Intent.
     */
    public boolean transact (int code, Parcel data, Parcel reply, int flags) throws RemoteException;

    // 注冊對Binder死亡通知的觀察者秃嗜,在其死亡后,會收到相應(yīng)的通知
    // 這里先跳過顿膨,后續(xù)
    public void linkToDeath (DeathRecipient recipient, int flags) throws RemoteException;
}

如注釋中所述锅锨,最重要的方法是 transact 方法,要理解這個方法是干嘛的恋沃,得看ipcthreadstate.cpp
源代碼里的說明必搞。

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){
        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s",getpid(),getuid(),
        (flags&TF_ONE_WAY)==0?"READ REPLY":"ONE WAY");
        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;
    }

這是截取過后的源碼,刨除了一些支線邏輯芽唇,從這個代碼里面可以看到的主要邏輯就是:根據(jù)Uid 和 Pid (用戶id和進(jìn)程id)進(jìn)行相應(yīng)的校驗,校驗通過后,將相應(yīng)的數(shù)據(jù)寫入 writeTransactionData 匆笤,其后在 waitForResponse 里面讀取前面寫入的值研侣,并執(zhí)行相應(yīng)的方法,最后返回結(jié)果炮捧。

總結(jié)地來說庶诡,就是 IBinder 抽象了遠(yuǎn)程調(diào)用的接口,任何一個可遠(yuǎn)程調(diào)用的對象都應(yīng)該實現(xiàn)這個接口咆课。由于 IBinder 對象是一個高度抽象的結(jié)構(gòu)末誓,直接使用這個接口對于應(yīng)用層的開發(fā)者而言學(xué)習(xí)成本太高,需要涉及到不少本地實現(xiàn)书蚪,因而 Android 實現(xiàn)了 Binder 作為 IBinder 的抽象類喇澡,提供了一些默認(rèn)的本地實現(xiàn),當(dāng)開發(fā)者需要自定義實現(xiàn)的時候殊校,只需要重寫 Binder 中的protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 方法即可晴玖。


自動售貨機(jī)的故事

先偏一個題,夏天一到为流,天氣也變得炎熱呕屎,地鐵旁邊的自動售貨機(jī)開始有更多的人關(guān)顧了。那么售貨機(jī)是怎么工作的了敬察?通過對這個分析秀睛,可以對Binder Proxy/Stub 模式更好地理解。

和我們打交道得是售貨機(jī)莲祸,而不是背后的零售商蹂安,道理很簡單,零售商的位置是固定的虫给,也就意味著有很大的交通成本藤抡,而和售貨機(jī)打交道就輕松很多,畢竟售貨機(jī)就在身邊抹估。因而從客戶端的角度上看缠黍,只需要知道售貨機(jī)即可。

再從零售商的角度來看药蜻,要和為數(shù)眾多的售貨機(jī)打交道也是不容易的事情瓷式,需要大量的維護(hù)和更新成本,如果將起交由另一個中介公司语泽,就能夠省心不少贸典。零售商只關(guān)心這個中介公司,按時發(fā)貨踱卵,檢查營收廊驼,這就是所有它應(yīng)該完成的工作据过。

如上圖所示,在 Binder Framework 中也采用了類似的結(jié)構(gòu)妒挎,Proxy 就相當(dāng)于前面提及的售貨機(jī)绳锅,Stub 相當(dāng)于這里的中介公司,在這樣的設(shè)計下酝掩,客戶端只需要和 Proxy 打交道鳞芙,服務(wù)端只需要和 Stub 打交道,調(diào)理清晰很多期虾。這種模式又被稱為 Proxy / Stub 模式原朝,這種模式也值得我們在日后的開發(fā)中借鑒。另外需要注意的是镶苞,為了開發(fā)的需要喳坠,通常 Proxy 和 Stub 實現(xiàn)了相同的接口。

在這里 Stub 是具體的遠(yuǎn)程方法實現(xiàn)宾尚,也被稱為存根丙笋,Proxy 繼承了相應(yīng)的接口,但只是在這里面調(diào)用遠(yuǎn)程方法煌贴,并返回相應(yīng)的結(jié)果御板。


AIDL 是什么?它是如何運作的牛郑?

雖然上述的模式幫助我們將遠(yuǎn)程方法調(diào)用與服務(wù)具體實現(xiàn)解耦開來怠肋,但是這里面還是又不少代碼需要實現(xiàn),而且遠(yuǎn)程方法調(diào)用這一塊應(yīng)該是重復(fù)代碼淹朋,那么有什么方法來幫我們簡化這個步驟嗎笙各?

Android 的設(shè)計者在一開始也意識到這個問題,推出了 AIDL础芍,如下圖所示

現(xiàn)在我們來分析下杈抢,Android Framework 是如何把 AIDL 運行起來的,首先來看看一個大數(shù)相乘的例子仑性,想把這個耗時的操作惶楼,放到另一個進(jìn)程上來調(diào)用,于是定義了下面的 IMultiply.aidl 文件诊杆。

// IMultiply.aidl
package com.wandoujia.baymax.aidl;

interface IMultiply {
    long multiply(long left, long right);
}

在 Android Studio 中點擊 Clean / Rebuild Project 之后歼捐,可以在 build / genreated / Source 的目錄里面找到 IMultiply.java 文件,具體的代碼如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/QisenTang/Program/Wandoulabs/baymax/packages/baymax/src/main/aidl/com/wandoujia/baymax/aidl/IMultiply.aidl
 */
package com.wandoujia.baymax.aidl;
// Declare any non-default types here with import statements

public interface IMultiply extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.wandoujia.baymax.aidl.IMultiply {
        private static final java.lang.String DESCRIPTOR = "com.wandoujia.baymax.aidl.IMultiply";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub () {
            this.attachInterface (this,
                                  DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.wandoujia.baymax.aidl.IMultiply interface,
         * generating a proxy if needed.
         */
        public static com.wandoujia.baymax.aidl.IMultiply asInterface (android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.wandoujia.baymax.aidl.IMultiply))) {
                return ((com.wandoujia.baymax.aidl.IMultiply) iin);
            }
            return new com.wandoujia.baymax.aidl.IMultiply.Stub.Proxy (obj);
        }

        @Override
        public android.os.IBinder asBinder () {
            return this;
        }

        @Override
        public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString (DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_multiply: {
                    data.enforceInterface (DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong ();
                    long _arg1;
                    _arg1 = data.readLong ();
                    long _result = this.multiply (_arg0,
                                                  _arg1);
                    reply.writeNoException ();
                    reply.writeLong (_result);
                    return true;
                }
            }
            return super.onTransact (code,
                                     data,
                                     reply,
                                     flags);
        }

        private static class Proxy implements com.wandoujia.baymax.aidl.IMultiply {
            private android.os.IBinder mRemote;

            Proxy (android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder () {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor () {
                return DESCRIPTOR;
            }

            @Override
            public long multiply (long left, long right) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain ();
                android.os.Parcel _reply = android.os.Parcel.obtain ();
                long _result;
                try {
                    _data.writeInterfaceToken (DESCRIPTOR);
                    _data.writeLong (left);
                    _data.writeLong (right);
                    mRemote.transact (Stub.TRANSACTION_multiply,
                                      _data,
                                      _reply,
                                      0);
                    _reply.readException ();
                    _result = _reply.readLong ();
                } finally {
                    _reply.recycle ();
                    _data.recycle ();
                }
                return _result;
            }
        }

        static final int TRANSACTION_multiply = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public long multiply (long left, long right) throws android.os.RemoteException;
}

這段自動的生成的代碼晨汹,可讀性上不是很好豹储,但不影響我們進(jìn)行分析。這段自動生成的代碼中淘这,最重要的是三個部分剥扣,Proxy巩剖,Stub和asInterface。

首先看看Proxy的實現(xiàn)钠怯,首先是將遠(yuǎn)程的Binder作為參數(shù)傳入進(jìn)來球及,再來看看 multiply 這個方法里面的下面幾個步驟。首先是將left 和 right的參數(shù)寫入到_data中去呻疹,同時在 遠(yuǎn)程binder 調(diào)用結(jié)束后,得到返回的 _reply 筹陵,在沒有異常的情況下刽锤,返回_reply.readLong()的結(jié)果。根據(jù) Android Binder 完全解析(一)概述 提到的內(nèi)容朦佩,這里可以粗略地將 Binder 看做遠(yuǎn)程服務(wù)拋出的句柄并思,通過這個句柄就可以執(zhí)行相應(yīng)的方法了。這里需要額外說明的是语稠,寫入和傳輸?shù)臄?shù)據(jù)宋彼,都是 Parcelable,Android Framework 中提供的仙畦。

_data.writeInterfaceToken(DESCRIPTOR);
_data.writeLong(left);
_data.writeLong(right);
mRemote.transact(Stub.TRANSACTION_multiply, _data, _reply, 0);
_reply.readException();
_result = _reply.readLong();

再來看看Stub里面的實現(xiàn)输涕,Stub本身繼承了Binder抽象類,本身將作為一個句柄慨畸,實現(xiàn)在服務(wù)端莱坎,但傳遞給客戶端使用。同樣也看看 onTransact里面的方法寸士,首先是通過 long _arg0; _arg0 = data.readLong();讀取 left 和 right 參數(shù)檐什,在this.multiply(_arg0, _arg1)計算過后,將結(jié)果寫入到reply中弱卡。

細(xì)心的讀者乃正,可以從上面的描述中,發(fā)現(xiàn)一些有意思的東西婶博。Proxy 是寫入?yún)?shù)瓮具,讀取值;Stub 是讀取參數(shù)凡蜻,寫入值搭综。正好是一對,那因此我們是不是可以做出這樣的論斷呢划栓?Proxy 和 Stub 操作的是一份數(shù)據(jù)兑巾?恭喜你,答對了忠荞。

在前文提及的內(nèi)容里蒋歌,用戶空間和內(nèi)核空間是互相隔離的帅掘,客戶端和服務(wù)端在同一用戶空間,而 Binder Driver 在內(nèi)核空間中堂油,常見的方式是通過copy_from_user 和 copy_to_user 兩個系統(tǒng)調(diào)用來完成修档,但 Android Framework 考慮到這種方式涉及到兩次內(nèi)存拷貝,在嵌入式系統(tǒng)中不是很合適府框,于是 Binder Framework 通過 ioctl 系統(tǒng)調(diào)用吱窝,直接在內(nèi)核態(tài)進(jìn)行了相關(guān)的操作,節(jié)省了寶貴的空間迫靖。

可能大家也注意點 Proxy這里是private權(quán)限的院峡,外部是無法訪問的,但這里是 Android 有意為之系宜,拋出了 asInterface方法照激,這樣避免了對 Proxy可能的修改。


系統(tǒng)使用AIDL的場景

AIDL 被Android 系統(tǒng)廣泛使用盹牧,在許多地方都能看到相應(yīng)的場景俩垃,這里以 電話服務(wù) 作為例子,來簡單說明下如何在系統(tǒng)沒有提供掛斷電話的API的情況下強(qiáng)行掛電話汰寓。

通過的 ITelephony.aidl 的查看口柳,可以在里面找到相應(yīng)的接口,不過這個類是 internal 包里面的有滑,應(yīng)用層無法訪問啄清。那怎么來完成這個操作了?

 /**
  * End call if there is a call in progress, otherwise does nothing.
  *
  * @return whether it hung up
  */
 boolean endCall();

首先在相同包名下申明同樣的AIDL文件俺孙,再通過編譯后辣卒,就會生成相應(yīng)的 Stub 文件。其次通過反射拿到 TELEPHONY_SERVICE 的binder睛榄。這個 binder 就是可以操作另一個進(jìn)程來掛斷電話的句柄荣茫。將這個binder 作為 Proxy 的參數(shù),并通過 asInterface 注入進(jìn)去场靴,從何獲得相應(yīng)的接口啡莉,最后調(diào)用 telephony.endCall() 即可完成操作。

Method method = Class.forName ("android.os.ServiceManager")
                                 .getMethod ("getService",
                                             String.class);
            IBinder binder = (IBinder) method.invoke (null,
                                                      new Object[]{TELEPHONY_SERVICE});
            ITelephony telephony = ITelephony.Stub.asInterface (binder);
            telephony.endCall ();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旨剥,一起剝皮案震驚了整個濱河市咧欣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轨帜,老刑警劉巖魄咕,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚌父,居然都是意外死亡哮兰,警方通過查閱死者的電腦和手機(jī)毛萌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喝滞,“玉大人阁将,你說我怎么就攤上這事∮以猓” “怎么了做盅?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窘哈。 經(jīng)常有香客問我言蛇,道長,這世上最難降的妖魔是什么宵距? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮吨拗,結(jié)果婚禮上满哪,老公的妹妹穿的比我還像新娘。我一直安慰自己劝篷,他們只是感情好哨鸭,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著娇妓,像睡著了一般像鸡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哈恰,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天只估,我揣著相機(jī)與錄音,去河邊找鬼着绷。 笑死蛔钙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荠医。 我是一名探鬼主播吁脱,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彬向!你這毒婦竟也來了兼贡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤娃胆,失蹤者是張志新(化名)和其女友劉穎遍希,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體里烦,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡孵班,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年涉兽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篙程。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡枷畏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出虱饿,到底是詐尸還是另有隱情拥诡,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布氮发,位于F島的核電站渴肉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爽冕。R本人自食惡果不足惜仇祭,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颈畸。 院中可真熱鬧乌奇,春花似錦、人聲如沸眯娱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徙缴。三九已至试伙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間于样,已是汗流浹背疏叨。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留穿剖,地道東北人考廉。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像携御,于是被迫代替她去往敵國和親昌粤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容