官方說明直達
何時使用AIDL
在 Android 上,一個程式通常無法訪問另一個程式的記憶體。 儘管如此,程式需要將其物件分解成作業系統能夠識別的primitives,並將物件編組成跨越邊界的物件。 編寫執行這一編組操作的程式碼是一項繁瑣的工作,因此 Android 會使用 AIDL 來處理。
簡單地說,在android上跨程式無法直接互動資料資訊,需要經過一系列轉換,讓android底層來實現傳輸。
注:只有在跨程式且服務中處理多執行緒才需要用到AIDL;如果不需要跨程式只是IPC,用Binder就可以;如果不需要多執行緒用message就好了。
方式 | 條件 | |
---|---|---|
AIDL | 需要IPC | 跨程式,多執行緒 |
Binder | 只有IPC | 跨程式,多執行緒 |
Messager | 只有IPC | 單程式沒有多執行緒 |
基本語法&簡單例項
按照google官方的介紹,要使用AIDL需要三個步驟:
- 建立 .aidl 檔案
- 實現介面
- 向客戶端公開該介面
1.建立 .aidl 檔案
新建一個專案AidlTestRemote
AndroidStudio中的目錄結構
-main
|_aidl
|_java

// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** 此處新建的是一個service介面 */
interface IRemoteService {
/** 請求這個服務的程式ID,用它來搞事情.
* int getPid();
*/
/** 這裡可以使用一些基本型別作為引數的抽象方法
* 並通過AIDL返回
*
* void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
*/
int add(int num1 , int num2);
}
複製程式碼
2.服務端實現介面
在寫好.aidl檔案後,在 androidstudio 上 make project 專案,如下圖均可以


點選後androidstudio會在如下目錄自動生成與aidl同名的java檔案。

This file is auto-generated. DO NOT MODIFY.
摺疊程式碼可以看到, IRemoteService.java 中實際是由一個Stub子類和之前定義的add方法組成。若要具體實現IRemoteService(此示例中為add(int num1, int num2)方法),則繼承已生成的Binder介面並實現從.aidl檔案繼承的方法。



3.向客戶端公開該介面
上述完成的服務端的建立,簡單的說,就是先用aidl建一個通道,宣告好客戶端可以傳送什麼,服務端可以接收並返回什麼。服務端通過service來接收客戶端通過aidl傳送來的資料,經過處理後,再通過aidl返回給客戶端。實現了雙向的通訊。
既然服務端用service來處理客戶端的資料,自然客戶端要繫結上服務端的service。
為方便起見此時在專案中,新建一個 Module 名為aidltestclient。
將服務端的 IRemoteService.aidl 複製過來,要注意目錄結構完全一致


1.繫結到服務端的 RemoteCalculator service,呼叫它的 add(int num1, int num2)方法
2.呼叫 add(int num1, int num2) 方法需要客服端傳入兩個int引數,傳參的操作需要通過IRemoteService.aidl實現
3.客戶端完成操作後要解綁service(易忽視)
4.繫結到 RemoteCalculator service
在onCreate方法中執行bindServie()方法:
private void bindServie() {
Intent intent = new Intent();
//android新版本上需要顯示發起intent,ComponentName需要傳入pkg name,cls name。注意cls name需要加上包名
intent.setComponent(new ComponentName("com.wind.fitz.aidltestremote","com.wind.fitz.aidltestremote.RemoteCalculator"));
//(Intent service, ServiceConnection conn,int flags)
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
複製程式碼
bindService的引數說明:intent不解釋;ServiceConnection 用來接收the service object when it is created and be told if it dies and restarts。簡單的說,ServiceConnection 用來接收繫結到的service的狀態並作出響應,不可為空,看下面conn的實現就很清楚;flags則是繫結服務時的可選操作,設定BIND_AUTO_CREATE可以在繫結時自動建立,也可設0.
因為 ServiceConnection 不可為空,所以:
IRemoteService iRemoteService;
……
private ServiceConnection conn = new ServiceConnection() {
//繫結上服務
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iRemoteService = IRemoteService.Stub.asInterface(service);
}
//服務斷開
@Override
public void onServiceDisconnected(ComponentName name) {
iRemoteService = null;
}
};
複製程式碼
在之前有說到,RemoteCalculator service 被繫結上時,會返回一個 mBinder
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
複製程式碼
那麼在 onServiceConnected(ComponentName name, IBinder service) 中的 IBinder service 實質上就是 RemoteCalculator service 中的 mBinder ,那麼在 RemoteCalculator service 中 private Binder mBinder = new IRemoteService.Stub(){}
,
所以這麼一操作,conn 中的 IBinder service 就是遠端服務的 service 。
此時接收一下就好了 iRemoteService = IRemoteService.Stub.asInterface(service);
(為何如此接收後面分析)
至此客戶端就拿到了遠端服務端的 service 即 iRemoteService。
客戶端想要的結果就可以這麼來獲得 int result = iRemoteService.add(num1,num2);
通過 IPC 傳遞物件
官方說明:通過 IPC 介面把某個類從一個程式傳送到另一個程式是可以實現的。 不過,您必須確保該類的程式碼對 IPC 通道的另一端可用,並且該類必須支援 Parcelable 介面。支援 Parcelable 介面很重要,因為 Android 系統可通過它將物件分解成可編組到各程式的原語。
傳遞自定型別是可以的,但必須支援 Parcelable 介面。
- 自定義類實現 Parcelable 介面。
- 實現 writeToParcel,它會獲取物件的當前狀態並將其寫入 Parcel。
- 自定義類新增一個名為 CREATOR 的靜態欄位,這個欄位是一個實現 Parcelable.Creator 介面的物件。
- 最後,建立一個宣告可打包類的 .aidl 檔案。
1.建立自定義類
新建一個 IMyBook 類繼承 Parcelable
包含name,price,year,author四個屬性。
繼承 Parcelable 介面需要實現以下三個方法
//注意拆包順序要與打包順序一致
protected IMyBook(Parcel in) {
this.name = in.readString();
this.price = in.readInt();
this.year = in.readInt();
this.author = in.readString();
}
public static final Creator<IMyBook> CREATOR = new Creator<IMyBook>() {
//拆包
@Override
public IMyBook createFromParcel(Parcel in) {
//獲取的in寫入新book物件
return new IMyBook(in);//返回的新IMyBook物件到上面拆包
}
//預設
@Override
public IMyBook[] newArray(int size) {
return new IMyBook[size];
}
};
//預設
@Override
public int describeContents() {
return 0;
}
//打包
@Override
public void writeToParcel(Parcel dest, int flags) {
//將book屬性打包
dest.writeString(name);
dest.writeInt(price);
dest.writeInt(year);
dest.writeString(author);
}
複製程式碼

IMyBook 類也需要建議對應的aidl檔案


2.建立服務端
新建 IRemote.aidl 注意導包,介面裡寫一個向list新增物件的抽象方法。

make project,生成 IMyBook.java 和 IRemote.java 後。建立 service 實現 IRemote 介面

3.客戶端繫結服務
客戶端將aidl檔案複製到同級目錄,同時 IMyBook 類也要複製到對應包下


繫結到服務


注意事項
客戶端在呼叫遠端方法時,要確保在呼叫前繫結到遠端服務!!
如下錯誤寫法:
在點選事件內繫結服務,會導致服務未繫結完成就開始呼叫 add 方法。


基本資料類
預設情況下,AIDL 支援下列資料型別:
- Java八種基本資料型別(int、char、boolean、double、float、byte、long、string) 但不支援short
- String、CharSequence
- List和Map
- Parcelable
- List 中的所有元素都必須是以上列表中支援的資料型別、其他 AIDL 生成的介面或您宣告的可打包型別。 可選擇將 List 用作“通用”類(例如,List)。另一端實際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 介面。
- Map 中的所有元素都必須是以上列表中支援的資料型別、其他 AIDL 生成的介面或您宣告的可打包型別。 不支援通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端實際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 介面。
您必須為以上未列出的每個附加型別加入一個 import 語句,即使這些型別是在與您的介面相同的軟體包中定義。
AIDL原理
先放圖

看下圖,IRemote.java 是make project後AS自動生成的。看他的結構實際就是一個 Stub 和 add 方法。
add方法就是開發者定義在IRemote.aidl中的方法。可以看到它返回一個list,丟擲RemoteException。
public java.util.List<com.wind.fitz.ipcaidl.IMyBook> add(com.wind.fitz.ipcaidl.IMyBook book) throws android.os.RemoteException;
這個 Stub 子類它繼承自 Binder 並實現IRemote(自身)的介面。
public static abstract class Stub extends android.os.Binder implements com.wind.fitz.ipcaidl.IRemote

/** Construct the stub at attach it to the interface. */在連線到介面時構建存根
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
複製程式碼
在客戶端 ServiceConnection 中通過如下方式來獲得遠端服務:
iRemote = IRemote.Stub.asInterface(service);
那麼 **.Stub.asInterface() 實際上是返回一個 Proxy
/**
* Cast an IBinder object into an com.wind.fitz.ipcaidl.IRemote interface,
* generating a proxy if needed.
*/
public static com.wind.fitz.ipcaidl.IRemote asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.wind.fitz.ipcaidl.IRemote))) {
return ((com.wind.fitz.ipcaidl.IRemote)iin);
}
return new com.wind.fitz.ipcaidl.IRemote.Stub.Proxy(obj); //a
}
複製程式碼
那麼去看 Proxy ,上面的 a 實際走到了 b ,返回一個 mRemote (Binder物件)即 IRemote 的代理給了客戶端的 iRemote。
private static class Proxy implements com.wind.fitz.ipcaidl.IRemote
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote; //b
}
複製程式碼
那麼再看客戶端呼叫服務的方法:
iRemote.add(new IMyBook("aaa",100,2018,"bb"));
已經知道客戶端的 iRemote 實際上是 IRemote.java 中的 mRemote。
iRemote.add 呼叫的是 Proxy 重寫的 add 方法
@Override public java.util.List<com.wind.fitz.ipcaidl.IMyBook> add(com.wind.fitz.ipcaidl.IMyBook book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.wind.fitz.ipcaidl.IMyBook> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); //c
_reply.readException();
_result = _reply.createTypedArrayList(com.wind.fitz.ipcaidl.IMyBook.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
複製程式碼
在重寫的 add 方法中,是一系列的序列化操作,通過 mRemote.transact 傳送給底層(上述流程在Proxy內)。 之後 Stub onTransact 接收到 Proxy 傳送來的資料。再聯絡到服務端。 不嚴謹的解釋:
client -- [ proxy - stub ] -- server