Android IPC入門——AIDL

Android IPC入門——AIDL

Android 是一個基於 Linux 的操作系統,保留了多進程的特性。在 Android 中IPC是一個老生常談的話題,每個進程都是一個單獨的JVM虛擬機,它們是相互獨立的,擁有自己特定的內存地址和空間,無法直接進行聯繫,而AIDL就像一座橋樑連接不同進程,橋制定了規則,規定人怎麼來往,哪些人可以來往。

AIDL(AndroidInterfaceDefinition Language),是一種IDL語言,定義Android中兩個進程間通信的規則。其底層採用的是Binder機制,可以看做是Binder的官方封裝框架

語法

AIDL語法和Java大致相似,但是也有着些許區別:

  • AIDL文件大概分爲兩類

    • 一類是定義parcelable對象,給其它AIDL文件使用AIDL支持的默認數據類型外的數據
    • 一類是用來定義接口的
  • 文件後綴名是.aidl

  • 支持的數據類型

    • Java八種基本數據類型(byte/short/int/long/float/double/boolean/char)
    • String 與 CharSequence 類型
    • List類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是實現parcelable接口的
    • Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是實現parcelable接口的,Map是不支持泛型的。
    • 實現parcelable接口的對象
  • 目標文件即使與你正在編寫的文件在同一個包下,也需要導包

  • AIDL中的定向 tag 表示了在跨進程通信中數據的流向

    • in 表示數據只能由客戶端流向服務端,表現爲服務端將會接收到一個那個對象的完整數據,但是客戶端的那個對象不會因爲服務端對傳參的修改而發生變動
    • out 表示數據只能由服務端流向客戶端,表現爲服務端將會接收到那個對象的的空對象,但是在服務端對接收到的空對象有任何修改,之後客戶端將會同步變動
    • inout 則表示數據可在服務端與客戶端之間雙向流通,表現爲服務端將會接收到客戶端傳來對象的完整信息,並且客戶端將會同步服務端對該對象的任何變動

使用步驟

聲明文件

創建AIDL文件,用來聲明需要傳輸的數據,傳輸調用的接口,創建好後系統會自動生成一個默認示例方法,該方法展示了所支持的基本數據類型,一般直接刪除然後聲明自己需要的方法即可。
在這裏插入圖片描述

// Declare any non-default types here with import statements

interface IAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
  
    void log(String msg);
}

編譯AIDL

我們所創建的AIDL文件其實並不是他最終的樣子,使用Make Project 會將其編譯成對應的 Java 文件。
在這裏插入圖片描述

public interface IAidlInterface extends android.os.IInterface
{
  /** Default implementation for IAidlInterface. */
  public static class Default implements com.whr.aidldemo.IAidlInterface
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.whr.aidldemo.IAidlInterface
  {
    private static final java.lang.String DESCRIPTOR = "com.whr.aidldemo.IAidlInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.whr.aidldemo.IAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.whr.aidldemo.IAidlInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.whr.aidldemo.IAidlInterface))) {
        return ((com.whr.aidldemo.IAidlInterface)iin);
      }
      return new com.whr.aidldemo.IAidlInterface.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
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.whr.aidldemo.IAidlInterface
    {
      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;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.whr.aidldemo.IAidlInterface sDefaultImpl;
    }
    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.whr.aidldemo.IAidlInterface impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.whr.aidldemo.IAidlInterface getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

該類比較複雜,僅簡單講解,包含以下成員:

  • 靜態匿名內部類 Default,是 AIDL 所定義接口的默認實現類
  • 抽象靜態匿名內部類 Stub,繼承自 Binder,實現 AIDL 定義接口。該類是 AIDL 最核心的一個類,AIDL 底層實現是基於 Binder,而具體的 Binder 實現代碼就是在該類中被編譯器自動補全的。

聲明服務

AIDL 本質上只是一個接口,用於定義規則,而具體的實現就需要依賴於四大組件的Service了。由於是多進程交互,我們需要在Manifest中對該 Service 聲明 process屬性,以此在別的進程中創建該 Service,該屬性有兩種聲明方式,一種是以:開頭連接子進程名,這樣聲明出來的進程前綴會和應用包名保持一致。另一種則是完全重寫,這樣聲明出來的進程名就是所聲明的名字。

	        <service
                android:name=".RemoteService"
                android:enabled="true"
                android:exported="true"
                android:process=":remote">
            <intent-filter>
                <action android:name="com.whr.aidldemo.RemoteService" />
            </intent-filter>
        </service>

另外這裏還加了一個intent-filter:這是爲了隱式啓動遠程服務所用。

講到intent-filter就得講還有一個屬性android:exported:代表是否能被其他應用隱式調用,其默認值是由service中有無intent-filter決定的,如果有intent-filter,默認值爲true,否則爲false。手動設置false的情況下,即使有intent-filter匹配,也無法打開,即無法被其他應用隱式調用。

創建服務

Service 是抽象類,當實現類繼承它的時候需要實現其onBind()方法,該方法返回IBinder對象,用於和綁定的Activity進行交互,通常在非IPC場景下隨便聲明一個Binder的實現類就可以了,而在這裏,則需要用到之前聲明的AIDL接口。

由於需要的是 IBinder對象,這樣顯然是不能講 AIDL 定義的接口直接進行實現後返回的,那要怎麼辦呢,這就涉及到 AIDL 文件自動編譯後生成的抽象靜態內部類 Stub了,對於該類的作用這裏不過多介紹。總之該類繼承自Binder並且要求實現 AIDL 定義好的接口,正好符合這裏的需要。

public class RemoteService extends Service {

    private Binder stub = new com.whr.aidldemo.IMyAidlInterface.Stub() {

        @Override
        public void log(String msg) throws RemoteException {
            Log.i("common", "pid:" + Process.myPid() + "msg:" + msg);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }
}

綁定服務

客戶端綁定遠程Service,並獲得遠程服務的代理對象,即獲取遠程Binder對象的接口的本地實現。
客戶端便可以通過接口調用遠程Service提供的方法。

因爲不處於一個進程,需要隱式啓動,在android5.0以前,我們只要設置Action就行了,但是之後需要把包名也一併設置,從源碼可以看出如果啓動service的intent的component和package都爲空並且版本大於LOLLIPOP(5.0)的時候,直接拋出異常。

    private void initAIDL() {
        Intent intent = new Intent();
        intent.setAction("com.whr.aidldemo.RemoteService");
        intent.setPackage(getPackageName());
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
									//TODO
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {

            }
        }, Context.BIND_AUTO_CREATE);
    }

通過實現回調 ServiceConnection可以獲得遠程服務的IBinder對象,從而調用我們定義的方法,但是這裏返回的引用是個頂層接口,無法知道我們定義的具體的能力,通常我們會想到用強轉,這裏其實已經爲我們封裝好了專門的轉換方法。

com.whr.aidldemo.IMyAidlInterface myAidlInterface = com.whr.aidldemo.IMyAidlInterface.Stub.asInterface(iBinder);

然後就可以調用我們定義好的方法進行跨進程交互了。

	        findViewById(R.id.log).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    myAidlInterface.log("click");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

打印出來的效果如下:

com.whr.aidldemo:remote I/common: pid:4021 msg:click

寫在最後

總結一下,使用 AIDL 可以讓我們非常方便的實現本來複雜的進程間通訊, AIDL 可以看成一個爲了簡化 IPC ,官方基於 Binder 機制封裝的一套框架。

而這其中的難點其實也就是集中在系統編譯 AIDL 之後,爲我們自動實現的 Binder 那部分邏輯裏面了,當然其實不用太過深究也能滿足大部分簡單的操作。

遇到的坑

原本在一開始時筆者是準備用 kotlin 進行所以代碼的編寫的,但是寫完在編譯時發生了一個問題:kotlin 代碼中的引用 AIDL 時文件資源找不到。

經過反覆 clean 、build、清理緩存後依舊存在這個問題,去搜索了一波好像也沒有別人遇到過這個問題,姑且就沒有過多研究。如果有碰到過的童鞋希望能告知一下。

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