Android AIDL 從入門到精通

AIDL 是 Android 特有的 IPC 程序間通訊方式
AIDL 的寫法其實和繫結服務的程式碼差不多,IBander 也是 android 預設提供的一個 AIDL 介面
需要注意的是 5.0 之後,不能隱式啟動 service,不能想以前一樣定義 action 來啟動服務了,尤其是不是跨應用啟動服務,這也算是一種安全上的考慮
若是想非常詳細的瞭解 AIDL ,請看慕課網的科普視訊
- AIDL-小白成長記
AIDL 寫法
-
使用 Android 提供的方式生命一個 AIDL 介面
然後在這個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 通訊類出來:
位置在:

系統會在幫我們創建出一個單獨的 aidl 包出來,裡面放我們宣告的 AIDL 類,注意不是實現類
詳細的程式碼,有點長,系統幫我們新增的程式碼都是進行 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 類
- 在 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(); }
- 在 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 序列化型別資料的傳輸

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">img</figcaption>
AIDL 方法種若要傳遞物件型別,物件型別需要實現 Parcelable 序列化介面。Parcelable 的原理就是把大的資料型別物件,打散成一個個系統能支援的基本資料型別資料,然後集中打個包傳遞過去,到目標程序再組合成物件型別。這個過程也叫打包,拆包

系統幫我們生成的 AIDL 物件,裡面一個 Stub 型別的內部類,Stub 裡面又有一個 Proxy 型別的內部類

IPC 通訊的核心方法就在於期中的 transact 和 onTransact 方法
-
transact 會呼叫系統底層 IPC 去傳遞資料
-
onTransact 會接受系統底層 IPC 傳遞過來的資料
我們跟著程式碼來看一下:
- 宣告一個 AIDL 類,內部有一個叫 banZheng 的方法
interface IBanZheng { void banZheng(); }
- 獲取遠端程序代理
@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 底層的通訊
- 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 執行緒執行
- Stub 的 onTransact 方法是服務端核心,服務端在 onTransact 方法種接受到客服端傳過來的引數,然後計算,再把結果寫會去,客戶端才能收到結果,注意服務端的 binder 是在系統的 binder 執行緒池中執行的,不要我們自己再起執行緒了。
AIDL 遠端是非同步方法
不知道大家有沒有想過,遠端若是耗時方法的話,會不會卡客戶端執行緒,我們來測試一下,在兩端分別列印日誌,標記關鍵節點時間,遠端延遲3秒返回資料,大家看一下結果就能清楚了

客戶端

服務端
看日誌很清楚了吧,客戶端調 AIDL 方法可是會卡主執行緒的, 所以我們需要注意啊,遠端若是耗時的話,我們需要在客戶端點開執行緒,再進行 AIDL 遠端通訊
我們現在知道了客戶端的 AIDL 方法是非同步任務會卡主執行緒,那麼大家就不想知道服務端的方法是怎麼執行的嗎
系統有句話這麼說:
服務端的 binder 方法執行在系統的 AIDL 執行緒池裡
換句話說,AIDL 在服務端已經跑在單獨的執行緒裡了,不用我們自己開執行緒了,這裡我測試了下,列印服務端啟動時所線上程和 binder 執行任務時所線上程

image.png
看日誌就清楚了把,系統對於 AIDL 在服務端是有優化的,自動開執行緒池
AIDL 雙向通訊
AIDL 是單項通訊的,假設我們實現了 AIDL A -> B 程序的通訊,那麼在 A binder 聯通的時刻把 A 實現的 AIDL.Stub 傳遞給 B ,B 就能通過這個傳過來的 AIDL.Stub 實現 B -> A 的通訊了,AIDL.Stub 物件可以直接 IPC 傳遞的
參考例子可以看:
- Android AIDL SERVICE 雙向通訊 詳解
binder 如何理解
先看圖:

binder 和 AIDL 一樣都是 android 的 IPC 通訊機制,不同於 unix 其他的 IPC ,binder 對每個程序的 UID 支援非常好,安全性高
至於我們在使用時感覺 binder 不是誇程序的,那是錯覺。android 4大元件都是有宣告週期的,什麼時候 應該怎麼執行都是由 ActivityManageService 控制的,看上面的圖應該知道 binder 是元件與 ActivityManageService 通訊的,若 Activity 與 Service 在同一個程序,那麼記憶體共享,傳遞的資料可以給過去,這就是我們常見的與 Service 的通訊。
若 Activity 與 Service 不在同一個程序,記憶體不能共享,那麼資料就得通過 android 系統特有的 AIDL IPC 通道先序列化然後過去再反序列化,AIDL 的意思就在於跨程序傳遞資料了
詳細的大家請看:
- Android Binder機制淺析
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); }
參考自:
- Android:學習AIDL,這一篇文章就夠了(下)
使用 Messenger,handle 實現 IPC
Messenger 預設實現了 ibinder ,是對 AIDL 的封裝,大體的使用過程如下,我就不貼程式碼了,看程式碼的請看:
- Android中的Service:Binder,Messenger,AIDL(2)
-
服務端實現一個Handler,在 onBind 時 return mMessenger.getBinder() 返回給自客戶端用來通訊
-
客戶端接受 messager 物件 Messenger mService = new Messenger(service);
-
通過 Messenger 傳送訊息 mService.send(msg);
-
客戶端也給服務端提供一個 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,inout麼?
最後我說一下,用 AIDL 雙向通訊的話不用 in,out 來實現,連官方也不是用 in,out 來實現的,其次我沒發現 in,out 的應用場景,暫時作為知識點了解