Android Binder 完全解析(三)AIDL實(shí)現(xiàn)原理分析

目錄:
Android Binder 完全解析(一)概述
Android Binder 完全解析(二)設(shè)計(jì)詳解
Android Binder 完全解析(三)AIDL實(shí)現(xiàn)原理分析

如遇圖片無法顯示的情況维蒙,請點(diǎn)擊上面的鏈接進(jìn)行查看,我原來用的圖床太不穩(wěn)定了果覆,23333颅痊。

在前面一些細(xì)節(jié)概念的鋪墊下,大體上知道 Binder Framework 是怎么運(yùn)作的局待,在這邊文章中斑响,將詳細(xì)說明下 Binder Framework 的具體實(shí)現(xiàn),這一套機(jī)制如何盤活整個(gè) Android 系統(tǒng)燎猛。


IBinder 與 Binder

一個(gè)在同進(jìn)程的對象的抽象是 Object恋捆,但這個(gè)對象是不能被跨進(jìn)程使用的,要想跨進(jìn)程使用重绷,在 Android 中就必須依附于 Binder Framework沸停。基于抽象設(shè)計(jì)的原理昭卓,Android系統(tǒng)將一個(gè)可遠(yuǎn)程操作的應(yīng)用定義為 IBinder愤钾,在這個(gè)接口中定義了一個(gè)可遠(yuǎn)程調(diào)用對象應(yīng)該具有的屬性和方法,在代碼中實(shí)際使用的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í)行一個(gè)對象的方法敌土,
     * 
     * @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 方法躺坟,要理解這個(gè)方法是干嘛的,得看 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;
}

這是截取過后的源碼瞳氓,刨除了一些支線邏輯,從這個(gè)代碼里面可以看到的主要邏輯就是:根據(jù)Uid 和 Pid (用戶id和進(jìn)程id)進(jìn)行相應(yīng)的校驗(yàn)栓袖,校驗(yàn)通過后匣摘,將相應(yīng)的數(shù)據(jù)寫入 writeTransactionData,其后在 waitForResponse 里面讀取前面寫入的值裹刮,并執(zhí)行相應(yīng)的方法音榜,最后返回結(jié)果。

總結(jié)地來說捧弃,就是 IBinder 抽象了遠(yuǎn)程調(diào)用的接口赠叼,任何一個(gè)可遠(yuǎn)程調(diào)用的對象都應(yīng)該實(shí)現(xiàn)這個(gè)接口。由于 IBinder 對象是一個(gè)高度抽象的結(jié)構(gòu)违霞,直接使用這個(gè)接口對于應(yīng)用層的開發(fā)者而言學(xué)習(xí)成本太高嘴办,需要涉及到不少本地實(shí)現(xiàn),因而 Android 實(shí)現(xiàn)了 Binder 作為 IBinder 的抽象類买鸽,提供了一些默認(rèn)的本地實(shí)現(xiàn)涧郊,當(dāng)開發(fā)者需要自定義實(shí)現(xiàn)的時(shí)候,只需要重寫 Binder 中的
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 方法即可眼五。

看了一些繁瑣的概念后妆艘,再看一個(gè)美女放松下,稍后繼續(xù)看幼。

來自[Junhee_晴天](http://junhee-qingtian.lofter.com/)
來自[Junhee_晴天](http://junhee-qingtian.lofter.com/)

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

先偏一個(gè)題批旺,夏天一到,天氣也變得炎熱诵姜,地鐵旁邊的自動售貨機(jī)開始有更多的人關(guān)顧了汽煮。那么售貨機(jī)是怎么工作的了?通過對這個(gè)分析棚唆,可以對Binder Proxy/Stub 模式更好地理解暇赤。

和我們打交道得是售貨機(jī),而不是背后的零售商瑟俭,道理很簡單翎卓,零售商的位置是固定的,也就意味著有很大的交通成本摆寄,而和售貨機(jī)打交道就輕松很多失暴,畢竟售貨機(jī)就在身邊。因而從客戶端的角度上看微饥,只需要知道售貨機(jī)即可逗扒。

再從零售商的角度來看,要和為數(shù)眾多的售貨機(jī)打交道也是不容易的事情欠橘,需要大量的維護(hù)和更新成本矩肩,如果將起交由另一個(gè)中介公司,就能夠省心不少肃续。零售商只關(guān)心這個(gè)中介公司黍檩,按時(shí)發(fā)貨叉袍,檢查營收,這就是所有它應(yīng)該完成的工作刽酱。

binder proxy / stub 結(jié)構(gòu)

如上圖所示喳逛,在 Binder Framework 中也采用了類似的結(jié)構(gòu),Proxy 就相當(dāng)于前面提及的售貨機(jī)棵里,Stub 相當(dāng)于這里的中介公司润文,在這樣的設(shè)計(jì)下,客戶端只需要和 Proxy 打交道殿怜,服務(wù)端只需要和 Stub 打交道典蝌,調(diào)理清晰很多。這種模式又被稱為 Proxy / Stub 模式头谜,這種模式也值得我們在日后的開發(fā)中借鑒骏掀。另外需要注意的是,為了開發(fā)的需要乔夯,通常 Proxy 和 Stub 實(shí)現(xiàn)了相同的接口砖织。

在這里 Stub 是具體的遠(yuǎn)程方法實(shí)現(xiàn),也被稱為存根末荐,Proxy 繼承了相應(yīng)的接口侧纯,但只是在這里面調(diào)用遠(yuǎn)程方法,并返回相應(yīng)的結(jié)果甲脏。


AIDL 是什么眶熬?它是如何運(yùn)作的?

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

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

AIDL
AIDL

現(xiàn)在我們來分析下海渊,Android Framework 是如何把 AIDL 運(yùn)行起來的绵疲,首先來看看一個(gè)大數(shù)相乘的例子,想把這個(gè)耗時(shí)的操作臣疑,放到另一個(gè)進(jìn)程上來調(diào)用盔憨,于是定義了下面的 IMultiply.aidl 文件。

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

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

在 Android Studio 中點(diǎn)擊 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)行分析问慎。這段自動生成的代碼中萍摊,最重要的是三個(gè)部分,Proxy蝴乔,StubasInterface记餐。

首先看看Proxy的實(shí)現(xiàn)驮樊,首先是將遠(yuǎn)程的Binder作為參數(shù)傳入進(jìn)來薇正,再來看看 multiply 這個(gè)方法里面的下面幾個(gè)步驟。首先是將left 和 right的參數(shù)寫入到_data中去囚衔,同時(shí)在 遠(yuǎn)程binder 調(diào)用結(jié)束后挖腰,得到返回的 _reply ,在沒有異常的情況下练湿,返回_reply.readLong()的結(jié)果猴仑。根據(jù) Android Binder 完全解析(一)概述 提到的內(nèi)容,這里可以粗略地將 Binder 看做遠(yuǎn)程服務(wù)拋出的句柄肥哎,通過這個(gè)句柄就可以執(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里面的實(shí)現(xiàn),Stub本身繼承了Binder抽象類杈女,本身將作為一個(gè)句柄朱浴,實(shí)現(xiàn)在服務(wù)端,但傳遞給客戶端使用达椰。同樣也看看 onTransact里面的方法翰蠢,首先是通過 long _arg0; _arg0 = data.readLong(); 讀取 left 和 right 參數(shù),在this.multiply(_arg0, _arg1)計(jì)算過后啰劲,將結(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 兩個(gè)系統(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é)省了寶貴的空間集绰。

可能大家也注意點(diǎn) 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)的接口侍郭,不過這個(gè)類是 internal 包里面的询吴,應(yīng)用層無法訪問。那怎么來完成這個(gè)操作了亮元?

 /**
  * 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奉瘤。這個(gè) binder 就是可以操作另一個(gè)進(jìn)程來掛斷電話的句柄。將這個(gè)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();

參考文獻(xiàn)

  1. https://developer.android.com/guide/components/aidl.html
  2. http://8204129.blog.51cto.com/8194129/1357365

歡迎關(guān)注我的公眾號卖局,與您共同進(jìn)步!

石頭鋪
石頭鋪

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末双霍,一起剝皮案震驚了整個(gè)濱河市砚偶,隨后出現(xiàn)的幾起案子批销,更是在濱河造成了極大的恐慌,老刑警劉巖染坯,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件均芽,死亡現(xiàn)場離奇詭異,居然都是意外死亡单鹿,警方通過查閱死者的電腦和手機(jī)掀宋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仲锄,“玉大人劲妙,你說我怎么就攤上這事≈绱埃” “怎么了是趴?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澄惊。 經(jīng)常有香客問我,道長富雅,這世上最難降的妖魔是什么掸驱? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮没佑,結(jié)果婚禮上毕贼,老公的妹妹穿的比我還像新娘。我一直安慰自己蛤奢,他們只是感情好鬼癣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啤贩,像睡著了一般待秃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痹屹,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天章郁,我揣著相機(jī)與錄音,去河邊找鬼志衍。 笑死暖庄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楼肪。 我是一名探鬼主播培廓,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼春叫!你這毒婦竟也來了肩钠?” 一聲冷哼從身側(cè)響起俘侠,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔬将,沒想到半個(gè)月后爷速,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霞怀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年惫东,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毙石。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廉沮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出徐矩,到底是詐尸還是另有隱情滞时,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布滤灯,位于F島的核電站坪稽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳞骤。R本人自食惡果不足惜窒百,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一酥泛、第九天 我趴在偏房一處隱蔽的房頂上張望僻肖。 院中可真熱鬧,春花似錦逆航、人聲如沸美旧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榴嗅。三九已至妄呕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間录肯,已是汗流浹背趴腋。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留论咏,地道東北人优炬。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像厅贪,于是被迫代替她去往敵國和親蠢护。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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

  • IBinder 與 Binder 一個(gè)在同進(jìn)程的對象的抽象是 Object养涮,但這個(gè)對象是不能被跨進(jìn)程使用的葵硕,要想跨...
    小帝Ele閱讀 573評論 0 3
  • Android跨進(jìn)程通信IPC整體內(nèi)容如下 1眉抬、Android跨進(jìn)程通信IPC之1——Linux基礎(chǔ)2、Andro...
    隔壁老李頭閱讀 10,732評論 13 43
  • 你存在 在我深沉的夢境 在那些溫暖的回憶 還有那個(gè) 遙不可及的未來 冬夜凄冷 跳動的燭火 融釋我 深情的愛 孤燈下...
    隕恒閱讀 168評論 0 2
  • 自贊毀他嚴(yán)格來說也算是一大戒懈凹,守5戒方可成人蜀变,守五戒也屬于忍吧,雖然不能破五明介评,我們的睡眠就是無明本身的屬性库北,修道...
    huzx閱讀 308評論 0 0
  • 知道嗎,所有朋友里面和你相處最舒服们陆,不會尷尬也不用找話題寒瓦,我和你都是不善主動的人,卻能經(jīng)常找對方說話 無話不談坪仇。我...
    理千閱讀 314評論 0 0