1. 程式人生 > >Android 進階9:程序通訊之 AIDL 解析

Android 進階9:程序通訊之 AIDL 解析

讀完本文你將瞭解:

Android 進階7:程序通訊之 AIDL 的使用 中我們使用 AIDL 實現了跨程序的通訊,但是不清楚 AIDL 幫我們做了什麼。

AIDL 的本質是簡化我們 IPC 開發,它使用的是 Binder 機制,於是在上篇文章 Android 進階8:程序通訊之 Binder 機制淺析 中我們簡單瞭解了 Binder 機制的流程。

有了前面的鋪墊,這篇文章我們就一起來看看 AIDL 究竟幫我們做了什麼。

AIDL

前面我們講了,在使用 AIDL 編寫 IPC 程式碼時,我們只需要編寫簡單的 介面 aidl 檔案:

這裡寫圖片描述

Make Project 後系統就會幫我們生成 Java 檔案:

這裡寫圖片描述

AIDL 生成檔案分析

AIDL 幫我們生成內容:

這裡寫圖片描述

程式碼如下:

這裡寫圖片描述

可以看到,生成的介面 IMyAidl 繼承了 IInterfaceAndroid 進階8:程序通訊之 Binder 機制淺析 中我們介紹了,IInterface 是程序間通訊定義的通用介面。

同時 IMyAidl 中也包含了我們在 aidl 檔案中宣告的兩個方法。

除此外,IMyAidl 中還包括一個抽象類 Stub,它是一個 Binder,實現了 IMyAidl 介面:

Stub

這裡寫圖片描述

程式碼如下:

public static abstract class Stub extends android
.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl {
//唯一標識,一般為完整路徑 private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl"; /** * 將當前介面與 Binder 繫結 */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * 將一個 IBinder 轉換為 IMyAidl,如果不在一個程序就建立一個代理 */
public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } //拿著標識從本地查詢介面 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) { return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin); } //查不到就返回代理 return new net.sxkeji.shixinandroiddemo2.IMyAidl.Stub.Proxy(obj); } //覆蓋 IInterface 的方法,獲取當前介面對應的 Binder 物件 @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_addPerson: { //執行 addPerson 方法 data.enforceInterface(DESCRIPTOR); net.sxkeji.shixinandroiddemo2.bean.Person _arg0; if ((0 != data.readInt())) { //反序列化傳入的資料 _arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } //呼叫 addPerson 方法,這個方法的實現是在服務端 this.addPerson(_arg0); reply.writeNoException(); return true; } case TRANSACTION_getPersonList: { data.enforceInterface(DESCRIPTOR); java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result = this.getPersonList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); } //不在一個程序時返回的代理 private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...} //用於 onTransact 方法的兩個 code,分別標識要進行的操作 static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); }

Stub 的幾個關鍵內容介紹:

  1. 建構函式
    • 呼叫了 attachInterface() 方法
    • 將一個描述符、特定的 IInterface 與當前 Binder 繫結起來,這樣後續呼叫 queryLocalInterface 就可以拿到這個
    • 需要建立一個 DESCRIPTOR,一般是類的具體路徑名,用於唯一表示這個 IInterface
  2. asInterface()
    • IBinder 轉換為 IMyAidl ,這用於返回給客戶端
    • 不在一個程序的話,客戶端持有的是一個代理
  3. onTransact()
    • Binder 關鍵的處理事物方法
    • 根據傳入的 code,呼叫本地/服務端的不同方法

其中可以看到,在不同程序時返給客戶端的是代理。

Proxy

這裡寫圖片描述

程式碼:

private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {
    private android.os.IBinder mRemote;    //代理的遠端 IBinder

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

    //獲取代理的 Binder
    @Override
    public android.os.IBinder asBinder() {
        return mRemote;
    }

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

    /**
     * 代理嘛,處理下資料後直接呼叫實際 Binder 來處理
     */
    @Override
    public void addPerson(net.sxkeji.shixinandroiddemo2.bean.Person person) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((person != null)) {
                _data.writeInt(1);
                person.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            //呼叫遠端
            mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }

    @Override
    public java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> getPersonList() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

AIDL 生成的內容小結

  • IInterface 型別的介面,包括:
    • Stub 抽象類
    • aidl 介面定義的操作方法
  • Stub ,是一個 Binder,同時也是一個 IInterface,包括:
    • 將 Binder 轉成 IInterface 的 asInterface() 方法
    • 處理排程的 onTransact() 方法
    • 用於在 onTransact() 中標識要進行的操作的兩個標誌
    • 一個 IInterface 型別的代理
  • ProxyIInterface 型別的代理,包括:
    • 介面定義方法的偽實現,實際呼叫的是真正的 Binder 的方法

一句話總結:AIDL 幫我們生成了 Binder 和 跨平臺介面的轉換類 Stub,以及在不同程序時,客戶端拿到的代理 Proxy

現在回去看一下 AIDL 的使用,就會多了些理解。

AIDL 的使用回顧

服務端

使用時先在另一個程序的 Service 中實現 AIDL 生成檔案中的 Stub 類,然後在 onBind() 中返回:

這裡寫圖片描述

結合上面的分析,可以知道,我們在服務端例項化的是 Stub 的實體,它既是 Binder 也是 IInterface。在其中實現了介面定義的方法,然後在 onBind() 中返回自己。

客戶端

在 Activity 中使用 bindService() 繫結服務,然後再回調中,呼叫 Stub.asInterface() 將拿到的遠端 Binder 轉換為定義的介面,跨程序的話這裡拿到的實際是代理介面:

這裡寫圖片描述

然後就可以呼叫 Service 中方法了。

小結

根據上面的分析,我們可以看到,AIDL 幫我們做了以下幾件事:

  1. 根據定好的介面生成不同程序都可以共同訪問的介面類
  2. 在介面類中提供了 Binder 和介面的共同載體 Stub
  3. Stub 中建立了代理類,用於對映呼叫實際介面實現

有了 AIDL,我們編寫跨程序操作就變得十分簡單,我們只需要關注業務介面的實現即可。

手動寫個 Binder

我們可以模仿 AIDL 建立的檔案,手動寫個 Binder 來加深下理解。

首先是定義跨程序介面,實現 IInterface

在其中定義要跨程序做的操作,以及標識這兩個操作的 code:

public interface IMyAidlDiy extends IInterface {
    static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    public void addPerson(Person person) throws RemoteException;

    public List<Person> getPersonList() throws RemoteException;
}

然後在其中建立這個介面與對應 Binder 的轉換類 Stub

既然要兩頭討好,那它就需要繼承 Binder 的同時實現前面定義的介面,同時提供 Binder 和介面轉換的方法,以及作為介面處理事物的方法:

public static abstract class Stub extends Binder implements IMyAidlDiy {

    private static final String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.activity.ipc.IMyAidlDiy";

    public Stub() {
        attachInterface(this, DESCRIPTOR);
    }

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

    public static IMyAidlDiy asInterface(IBinder binder){
        if (binder == null){
            return null;
        }

        IInterface localInterface = binder.queryLocalInterface(DESCRIPTOR);
        if (localInterface != null && localInterface instanceof IMyAidlDiy){
            return (IMyAidlDiy) localInterface;
        }else {
            return new Stub.Proxy(localInterface);
        }
    }

    @Override
    protected boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {
        switch (code){
            case TRANSACTION_addPerson:
                data.enforceInterface(DESCRIPTOR);
                Person _arg0;
                if (data.readInt() != 0){
                    _arg0 = Person.CREATOR.createFromParcel(data);  //反序列化引數
                }else {
                    _arg0 = null;
                }
                this.addPerson(_arg0);
                reply.writeNoException();
                return true;
            case TRANSACTION_getPersonList:
                data.enforceInterface(DESCRIPTOR);
                List<Person> personList = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(personList);
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

}

最後建立代理介面,在不同程序中,客戶端持有的是代理

它的作用就是偽裝成真的 Binder,實際被呼叫時將資料處理成 Parcel,然後讓被代理的 Binder 去處理:

private static class Proxy implements IMyAidlDiy {
    private IBinder mRemote;

    public Proxy(final IBinder obj) {
        mRemote = obj;
    }

    public java.lang.String getInterfaceDescriptor() {  //偽裝的和真的 Binder 名一樣
        return DESCRIPTOR;
    }

    @Override
    public void addPerson(final Person person) throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();

        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if (person != null) {
                _data.writeInt(1);
                person.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(TRANSACTION_addPerson, _data, _reply, 0);  //這裡呼叫實際的實現
            _reply.readException();

        } finally {
            _data.recycle();
            _reply.recycle();
        }
    }

    @Override
    public List<Person> getPersonList() throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        List<Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

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

自己寫完實現對整個 Binder 實現的跨程序呼叫流程是否理解更深了呢,這裡用《Android 開發藝術探索》的一張圖總結下:

這裡寫圖片描述

需要注意的是,客戶端在發起遠端請求時,當前執行緒會被掛起直到服務端返回,因此儘量不要在 UI 執行緒發起遠端請求。

而在服務端,Binder 方法是執行在 Binder 執行緒池中的,因此可以直接使用同步的方式實現。

Thanks