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

摘要 本文是 Android 系統學習系列文章中的第二章節,在前面一些細節概念的鋪墊下,大體上知道 Binder Framework 是怎麼運作的,在這邊文章中,將詳細說明下 Binder Framework 的具體實現,這一套機制如何盤活整個 Android 系統。對此係列感興趣的同學,可以收藏這個鏈接 Android 系統學習,也可以點擊 RSS訂閱 進行訂閱。

IBinder 與 Binder

一個在同進程的對象的抽象是 Object,但這個對象是不能被跨進程使用的,要想跨進程使用,在 Android 中就必須依附於 Binder Framework。基於抽象設計的原理,Android系統將一個可遠程操作的應用定義爲 IBinder,在這個接口中定義了一個可遠程調用對象應該具有的屬性和方法,在代碼中實際使用的Binder 也都是繼承自 IBinder 對象。我們接下來分析 IBinder 中定義了那些接口,是怎麼抽象出可遠程調用相關的方法的。

public interface IBinder {
    ... ...

    // 查看 binder 對應的進程是否存活
    public boolean pingBinder();
    // 查看 binder 是否存活,需要注意的是,可能在返回的過程中,binder 不可用
    public  boolean isBinderAlive();

    /**
     * 執行一個對象的方法,
     *
     * @param 需要執行的命令
     * @param 傳輸的命令數據,這裏一定不能爲空
     * @param 目標 Binder 返回的結果,可能爲空
     * @param 操作方式,0 等待 RPC 返回結果,1 單向的命令,最常見的就是 Intent.
     */
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException;

    // 註冊對Binder死亡通知的觀察者,在其死亡後,會收到相應的通知
    // 這裏先跳過,後續
    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;
}

這是截取過後的源碼,刨除了一些支線邏輯,從這個代碼裏面可以看到的主要邏輯就是:根據Uid 和 Pid (用戶id和進程id)進行相應的校驗,校驗通過後,將相應的數據寫入 writeTransactionData ,其後在 waitForResponse 裏面讀取前面寫入的值,並執行相應的方法,最後返回結果。

總結地來說,就是 IBinder 抽象了遠程調用的接口,任何一個可遠程調用的對象都應該實現這個接口。由於 IBinder 對象是一個高度抽象的結構,直接使用這個接口對於應用層的開發者而言學習成本太高,需要涉及到不少本地實現,因而 Android 實現了 Binder 作爲 IBinder 的抽象類,提供了一些默認的本地實現,當開發者需要自定義實現的時候,只需要重寫 Binder 中的protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 方法即可。


自動售貨機的故事

先偏一個題,夏天一到,天氣也變得炎熱,地鐵旁邊的自動售貨機開始有更多的人關顧了。那麼售貨機是怎麼工作的了?通過對這個分析,可以對Binder Proxy/Stub 模式更好地理解。

和我們打交道得是售貨機,而不是背後的零售商,道理很簡單,零售商的位置是固定的,也就意味着有很大的交通成本,而和售貨機打交道就輕鬆很多,畢竟售貨機就在身邊。因而從客戶端的角度上看,只需要知道售貨機即可。

再從零售商的角度來看,要和爲數衆多的售貨機打交道也是不容易的事情,需要大量的維護和更新成本,如果將起交由另一箇中介公司,就能夠省心不少。零售商只關心這個中介公司,按時發貨,檢查營收,這就是所有它應該完成的工作。

binder proxy / stub 結構

如上圖所示,在 Binder Framework 中也採用了類似的結構,Proxy 就相當於前面提及的售貨機,Stub 相當於這裏的中介公司,在這樣的設計下,客戶端只需要和 Proxy 打交道,服務端只需要和 Stub 打交道,調理清晰很多。這種模式又被稱爲 Proxy / Stub 模式,這種模式也值得我們在日後的開發中借鑑。另外需要注意的是,爲了開發的需要,通常 Proxy 和 Stub 實現了相同的接口。

在這裏 Stub 是具體的遠程方法實現,也被稱爲存根,Proxy 繼承了相應的接口,但只是在這裏面調用遠程方法,並返回相應的結果。

AIDL 是什麼?它是如何運作的?

雖然上述的模式幫助我們將遠程方法調用與服務具體實現解耦開來,但是這裏面還是又不少代碼需要實現,而且遠程方法調用這一塊應該是重複代碼,那麼有什麼方法來幫我們簡化這個步驟嗎?

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

AIDL

現在我們來分析下,Android Framework 是如何把 AIDL 運行起來的,首先來看看一個大數相乘的例子,想把這個耗時的操作,放到另一個進程上來調用,於是定義了下面的 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;
}

這段自動的生成的代碼,可讀性上不是很好,但不影響我們進行分析。這段自動生成的代碼中,最重要的是三個部分,ProxyStubasInterface

首先看看Proxy的實現,首先是將遠程的Binder作爲參數傳入進來,再來看看 multiply 這個方法裏面的下面幾個步驟。首先是將left 和 right的參數寫入到_data中去,同時在 遠程binder 調用結束後,得到返回的 _reply ,在沒有異常的情況下,返回_reply.readLong()的結果。根據 Android Binder 完全解析(一)概述 提到的內容,這裏可以粗略地將 Binder 看做遠程服務拋出的句柄,通過這個句柄就可以執行相應的方法了。這裏需要額外說明的是,寫入和傳輸的數據,都是 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裏面的實現,Stub本身繼承了Binder抽象類,本身將作爲一個句柄,實現在服務端,但傳遞給客戶端使用。同樣也看看 onTransact裏面的方法,首先是通過 long _arg0; _arg0 = data.readLong(); 讀取 left 和 right 參數,在this.multiply(_arg0, _arg1)計算過後,將結果寫入到reply中。

細心的讀者,可以從上面的描述中,發現一些有意思的東西。Proxy 是寫入參數,讀取值;Stub 是讀取參數,寫入值。正好是一對,那因此我們是不是可以做出這樣的論斷呢?Proxy 和 Stub 操作的是一份數據?恭喜你,答對了。

在前文提及的內容裏,用戶空間和內核空間是互相隔離的,客戶端和服務端在同一用戶空間,而 Binder Driver 在內核空間中,常見的方式是通過 copy_from_user 和 copy_to_user 兩個系統調用來完成,但 Android Framework 考慮到這種方式涉及到兩次內存拷貝,在嵌入式系統中不是很合適,於是 Binder Framework 通過 ioctl 系統調用,直接在內核態進行了相關的操作,節省了寶貴的空間。

可能大家也注意點 Proxy 這裏是private權限的,外部是無法訪問的,但這裏是 Android 有意爲之,拋出了 asInterface 方法,這樣避免了對 Proxy可能的修改。

系統使用AIDL的場景

AIDL 被Android 系統廣泛使用,在許多地方都能看到相應的場景,這裏以 電話服務 作爲例子,來簡單說明下如何在系統沒有提供掛斷電話的API的情況下強行掛電話。

通過的 ITelephony.aidl 的查看,可以在裏面找到相應的接口,不過這個類是 internal 包裏面的,應用層無法訪問。那怎麼來完成這個操作了?

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

首先在相同包名下申明同樣的AIDL文件,再通過編譯後,就會生成相應的 Stub 文件。其次通過反射拿到 TELEPHONY_SERVICE 的binder。這個 binder 就是可以操作另一個進程來掛斷電話的句柄。將這個binder 作爲 Proxy 的參數,並通過 asInterface 注入進去,從何獲得相應的接口,最後調用 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();

參考文獻

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

Published:May302016

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章