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
繼承了 IInterface
,Android 進階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
的幾個關鍵內容介紹:
建構函式
- 呼叫了
attachInterface()
方法 - 將一個描述符、特定的 IInterface 與當前 Binder 繫結起來,這樣後續呼叫 queryLocalInterface 就可以拿到這個
- 需要建立一個
DESCRIPTOR
,一般是類的具體路徑名,用於唯一表示這個 IInterface
- 呼叫了
asInterface()
- 將
IBinder
轉換為IMyAidl
,這用於返回給客戶端 - 不在一個程序的話,客戶端持有的是一個代理
- 將
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
型別的代理
- 將 Binder 轉成 IInterface 的
Proxy
,IInterface
型別的代理,包括:
- 介面定義方法的偽實現,實際呼叫的是真正的 Binder 的方法
一句話總結:AIDL 幫我們生成了 Binder
和 跨平臺介面的轉換類 Stub
,以及在不同程序時,客戶端拿到的代理 Proxy
。
現在回去看一下 AIDL 的使用,就會多了些理解。
AIDL 的使用回顧
服務端
使用時先在另一個程序的 Service 中實現 AIDL 生成檔案中的 Stub
類,然後在 onBind()
中返回:
結合上面的分析,可以知道,我們在服務端例項化的是 Stub
的實體,它既是 Binder
也是 IInterface
。在其中實現了介面定義的方法,然後在 onBind()
中返回自己。
客戶端
在 Activity 中使用 bindService()
繫結服務,然後再回調中,呼叫 Stub.asInterface()
將拿到的遠端 Binder 轉換為定義的介面,跨程序的話這裡拿到的實際是代理介面:
然後就可以呼叫 Service 中方法了。
小結
根據上面的分析,我們可以看到,AIDL 幫我們做了以下幾件事:
- 根據定好的介面生成不同程序都可以共同訪問的介面類
- 在介面類中提供了 Binder 和介面的共同載體
Stub
- 在
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 執行緒池中的,因此可以直接使用同步的方式實現。