AIDL 從入門到精通

什么是 RPC 框架:- 很簡單,就是用來實現遠程調用,進程間通信的,不同的系統由不同的實現,android 這里的實現叫 Binder

不同的系統有不同的實現,但是原理基本都相同,理解起來很簡單:

即將調用的方法(接口名、方法名、參數類型、參數)序列化之后發送到遠端,在遠端反序列化之后調用接口的實現類的方法(接口主要是為了使用動態代理),2 端的實現類通過內部的通信實現諸如發射參數,實現等待,接受返回值等遠程跨進程操作。所以我們在實現RPC框架的時候需要選擇合適的序列化與反序列化方式


AIDL 是 Android 特有的 IPC 進程間通訊方式

AIDL 的寫法其實和綁定服務的代碼差不多,IBander 也是 android 默認提供的一個 AIDL 接口

需要注意的是 5.0 之后,不能隱式啟動 service,不能想以前一樣定義 action 來啟動服務了,尤其是不是跨應用啟動服務,這也算是一種安全上的考慮

若是想非常詳細的了解 AIDL ,請看慕課網的科普視頻

AIDL 寫法

  1. 使用 Android 提供的方式生命一個 AIDL 接口


    Snip20171024_3.png

然后在這個AIDL 接口中聲明業務需要的方法

// IBanZheng.aidl
package com.bloodcrown.bcremoteservice.aldl;

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

interface IBanZheng {

    void banZheng();

    /**
     * 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);
}

這就是系統幫我們創建的 AIDL 接口,在里面我寫了一個 banZheng() 的方法,剩下的都是系統的事了,Android 系統會根據我們聲明的這個 AIDL 接口創建一個相關的 IPC 通訊類出來:

位置在:


Snip20171024_4.png

系統會在幫我們創建出一個單獨的 aidl 包出來,里面放我們聲明的 AIDL 類,注意不是實現類


Snip20171024_6.png

詳細的代碼,有點長,系統幫我們添加的代碼都是進行 ipc通訊的

package com.bloodcrown.bcremoteservice.aldl;
// Declare any non-default types here with import statements

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

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

        /**
         * Cast an IBinder object into an com.bloodcrown.bcremoteservice.aldl.IBanZheng interface,
         * generating a proxy if needed.
         */
        public static com.bloodcrown.bcremoteservice.aldl.IBanZheng asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.bloodcrown.bcremoteservice.aldl.IBanZheng))) {
                return ((com.bloodcrown.bcremoteservice.aldl.IBanZheng) iin);
            }
            return new com.bloodcrown.bcremoteservice.aldl.IBanZheng.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_banZheng: {
                    data.enforceInterface(DESCRIPTOR);
                    this.banZheng();
                    reply.writeNoException();
                    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;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.bloodcrown.bcremoteservice.aldl.IBanZheng {
            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 void banZheng() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_banZheng, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            /**
             * 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);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_banZheng = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void banZheng() throws android.os.RemoteException;

    /**
     * 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;

其實我們主要看這個類:

 public static abstract class Stub extends android.os.Binder implements com.bloodcrown.bcremoteservice.aldl.IBanZheng

和我們在綁定服務時,在服務中寫的用戶返回的內部類一樣不一樣,都是繼承 Binder 類,實現我們自己聲明的接口,然后我們使用也是使用這個stub 類,我們的目的就是讓系統幫我們創建出這個 stub 類

  1. 在 service 中使用這個 stub 類

現在我們寫的內部類直接繼承這個 stub 即可

 class MediaIBander extends IBanZheng.Stub {

        @Override
        public void banZheng() throws RemoteException {
            Log.d(TAG, "辦證啦...");
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }

然后在綁定生命周期函數內返回這個內部類

@Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "remoteService - onBind...");
        return new MediaIBander();
    }
  1. 在 activity 中接受數據,轉換對象類型
 public class MyRemoteServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBanZheng = IBanZheng.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

AIDL 深入理解

AIDL 是 android 系統 IPC 進程間通訊協議,但是記住僅僅只是 android ,換個平臺就不是 AIDL 了。

android 中每個進程都有自己獨立的虛擬機 JVM , 每個JVM 的內存時獨立的,所以進程間通信依靠傳遞對象引用是不行的,因為內存是不連續的, A進程 的內存中有的對象,你把對象引用給到 B進程內存里面可是沒這個對象的,所以 google 就提供了 AIDL

AIDL 是一個橋


進程1 的請求會通過 AIDL 發送給系統,系統根據請求標識找到進程2,把進程1 的請求交給進程2去處理,同理進程2處理完后把結果通過 AIDL 再發送給進程1

AIDL 只支持基本數據類型,集合,Parcelable 序列化類型數據的傳輸


AIDL 方法種若要傳遞對象類型,對象類型需要實現 Parcelable 序列化接口。Parcelable 的原理就是把大的數據類型對象,打散成一個個系統能支持的基本數據類型數據,然后集中打個包傳遞過去,到目標進程再組合成對象類型。這個過程也叫打包,拆包


系統幫我們生成的 AIDL 對象,里面一個 Stub 類型的內部類,Stub 里面又有一個 Proxy 類型的內部類

IPC 通信的核心方法就在于期中的 transact 和 onTransact 方法

  • transact 會調用系統底層 IPC 去傳遞數據
  • onTransact 會接受系統底層 IPC 傳遞過來的數據

我們跟著代碼來看一下:

  1. 聲明一個 AIDL 類,內部有一個叫 banZheng 的方法
interface IBanZheng {

    void banZheng();
}
  1. 獲取遠程進程代理
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBanZheng = IBanZheng.Stub.asInterface(service);
        }

上面這是客服端獲取遠程服務的 binder ,我們跟進去看看

        public static com.bloodcrown.bcremoteservice.aldl.IBanZheng asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.bloodcrown.bcremoteservice.aldl.IBanZheng))) {
                // 是同一個進程,返回 Stub 對象本身
                return ((com.bloodcrown.bcremoteservice.aldl.IBanZheng) iin);
            }
            // 不是同一個進程,返回遠程進程的代理
            return new com.bloodcrown.bcremoteservice.aldl.IBanZheng.Stub.Proxy(obj);
        }

這個方法是 Stub 的方法,我們可以看到,當服務端和客戶端不再同一個進程時,其實我們拿到的只是遠程進程的代理類,這個代理類會幫我們進程 IPC 底層的通訊

  1. Proxy 調用底層通訊方法
            @Override
            public void banZheng() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    // 先把數據打包成可以通過 IPC 通訊傳遞
                    _data.writeInterfaceToken(DESCRIPTOR);
                    // 調用底層 IPC 方法傳遞數據(此時線程會掛起,也就是卡線程了)
                    mRemote.transact(Stub.TRANSACTION_banZheng, _data, _reply, 0);
                    // 等待遠程返回結果(此時線程會激活,重新執行下面任務)
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

Proxy 實現了我們聲明的 AIDL 接口,所以我們掉遠程 binder 的方法時就是調用的 Proxy 的相關方法,我們看上面的方法,先把數據序列化打包,再調用底層的 transact 方法進行 IPC 通訊,然后等待遠程返回結果,這里會卡住線程,所以客戶端的 IPC 請求最好在非 UI 線程執行

  1. Stub 的 onTransact 方法是服務端核心,服務端在 onTransact 方法種接受到客服端傳過來的參數,然后計算,再把結果寫會去,客戶端才能收到結果,注意服務端的 binder 是在系統的 binder 線程池中執行的,不要我們自己再起線程了。

AIDL 遠程是異步方法

不知道大家有沒有想過,遠端若是耗時方法的話,會不會卡客戶端線程,我們來測試一下,在兩端分別打印日志,標記關鍵節點時間,遠端延遲3秒返回數據,大家看一下結果就能清楚了

客戶端
服務端

看日志很清楚了吧,客戶端調 AIDL 方法可是會卡主線程的, 所以我們需要注意啊,遠端若是耗時的話,我們需要在客戶端點開線程,再進行 AIDL 遠程通信

我們現在知道了客戶端的 AIDL 方法是異步任務會卡主線程,那么大家就不想知道服務端的方法是怎么運行的嗎

系統有句話這么說:

服務端的 binder 方法運行在系統的 AIDL 線程池里

換句話說,AIDL 在服務端已經跑在單獨的線程里了,不用我們自己開線程了,這里我測試了下,打印服務端啟動時所在線程和 binder 執行任務時所在線程

看日志就清楚了把,系統對于 AIDL 在服務端是有優化的,自動開線程池


AIDL 雙向通訊

AIDL 是單項通訊的,假設我們實現了 AIDL A -> B 進程的通信,那么在 A binder 聯通的時刻把 A 實現的 AIDL.Stub 傳遞給 B ,B 就能通過這個傳過來的 AIDL.Stub 實現 B -> A 的通訊了,AIDL.Stub 對象可以直接 IPC 傳遞的

參考例子可以看:


binder 如何理解

先看圖:


binder 和 AIDL 一樣都是 android 的 IPC 通信機制,不同于 unix 其他的 IPC ,binder 對每個進程的 UID 支持非常好,安全性高

至于我們在使用時感覺 binder 不是夸進程的,那是錯覺。android 4大組件都是有聲明周期的,什么時候 應該怎么運行都是由 ActivityManageService 控制的,看上面的圖應該知道 binder 是組件與 ActivityManageService 通信的,若 Activity 與 Service 在同一個進程,那么內存共享,傳遞的數據可以給過去,這就是我們常見的與 Service 的通信。

若 Activity 與 Service 不在同一個進程,內存不能共享,那么數據就得通過 android 系統特有的 AIDL IPC 通道先序列化然后過去再反序列化,AIDL 的意思就在于跨進程傳遞數據了

詳細的大家請看:


AIDL 解讀補充

AIDL 跨進程通信的核心 Proxy ,大家看構造方法,注意 remote 就是那個遠程 Service,remote 恰恰就是 IBinder 對象,所以 IBinder 才是 android 中 IPC 通訊的基石

private static class Proxy implements com.lypeer.ipcclient.BookManager {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        //此處的 remote 正是前面我們提到的 IBinder service
        mRemote = remote;
    }
 
    @Override
    public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {
        //省略
    }

    @Override
    public void addBook(com.lypeer.ipcclient.Book book) throws android.os.RemoteException {
        //省略
    }
    //省略部分方法
}

我們在客戶端抓換 binder 為指定接口時系統的操作,判斷 binder 在本進程有沒有實例,就是客戶端和服務端是不是在一個進程,是的話就返回對象,queryLocalInterface 方法就是干的這事,不在一個進程的話,就需要 proxy 代理了,proxy 代理了 AIDL 實現的接口方法里的數據序列化,反序列化,至于 IPC 通信靠的還是 binder 去實現

public static com.lypeer.ipcclient.BookManager asInterface(android.os.IBinder obj) {
    //驗空
    if ((obj == null)) {
        return null;
    }
    //DESCRIPTOR = "com.lypeer.ipcclient.BookManager",搜索本地是否已經
    //有可用的對象了,如果有就將其返回
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.lypeer.ipcclient.BookManager))) {
        return ((com.lypeer.ipcclient.BookManager) iin);
    }
    //如果本地沒有的話就新建一個返回
    return new com.lypeer.ipcclient.BookManager.Stub.Proxy(obj);
}

我們在客戶端調用 AIDL 的方法,過程是下面這樣走的,核心就是調起 IBinder 類型的 mRemote 對象的 transact 方法,transact 方法就會進行跨進程通信了

@Override
public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {
    //很容易可以分析出來,_data用來存儲流向服務端的數據流,
    //_reply用來存儲服務端流回客戶端的數據流
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<com.lypeer.ipcclient.Book> _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //調用 transact() 方法將方法id和兩個 Parcel 容器傳過去
        mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
        _reply.readException();
        //從_reply中取出服務端執行方法的結果
        _result = _reply.createTypedArrayList(com.lypeer.ipcclient.Book.CREATOR);
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    //將結果返回
    return _result;
}

然后在服務端的 onTransact 方法接受遠程數據,反序列化出來,執行操作過程,然后通過 reply.writeString 把結果寫回去

@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_getBooks: {
            //省略
            return true;
        }
        case TRANSACTION_addBook: {
            //省略
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

參考自:


使用 Messenger,handle 實現 IPC

Messenger 默認實現了 ibinder ,是對 AIDL 的封裝,大體的使用過程如下,我就不貼代碼了,看代碼的請看:

  1. 服務端實現一個Handler,在 onBind 時 return mMessenger.getBinder() 返回給自客戶端用來通信
  2. 客戶端接受 messager 對象 Messenger mService = new Messenger(service);
  3. 通過 Messenger 發送消息 mService.send(msg);
  4. 客戶端也給服務端提供一個 Messenger 就能實現雙向通信了

AIDL中的 in,out,inout

什么是 in,out,inout ,是 AIDL 聲明參數在進程2端作用域的標記,看下面這個 AIDL 接口

// BookManager.aidl
package com.lypeer.ipcclient;
import com.lypeer.ipcclient.Book;

interface BookManager {    
    //保證客戶端與服務端是連接上的且數據傳輸正常
    List<Book> getBooks();

    //通過三種定位tag做對比試驗,觀察輸出的結果
    Book addBookIn(in Book book);
    Book addBookOut(out Book book);
    Book addBookInout(inout Book book);
}

AIDL 的參數默認是 in 的,一般我們也不寫

  • in : 參數在服務端的任何變化不會映射到客戶端
  • out:服務端接受的是一個 空內容的變量對象,在 服務端 對這個變量的任何修改都會映射到客戶端
  • inout: 服務端接受的是客戶端傳過來的參數有內容,在 服務端 對這個變量的任何修改都會映射到客戶端,而客戶端的修改不會映射到服務端

詳細可以看:

最后我說一下,用 AIDL 雙向通訊的話不用 in,out 來實現,連官方也不是用 in,out 來實現的,其次我沒發現 in,out 的應用場景,暫時作為知識點了解


參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容