跟面試官講Binder(二)之關於AIDL的認識
面試官開口說:“聽你剛才所說,在Android系統中,都是利用Binder來進行程序間通訊的,那我怎麼聽說,還有利用AIDL來實現程序間通訊的呢?”。
其實,AIDL只是一種描述性語言,其全稱是Android Interface Definition Language,即介面定義語言,利用ADT,我們可將自定義的AIDL檔案轉化成Java程式碼,而這些程式碼就能夠來進行程序間通訊(IPC)。為什麼這些程式碼就能夠進行IPC呢?那是因為,這些程式碼就是定義了Binder機制中作為服務端的Binder物件和客戶端中用的Proxy物件。
在前面的文章中,我們講過,作為服務端的服務,會在本執行緒中建立一個Binder物件,並將其引用傳遞給Binder驅動,而客戶端通過Binder驅動獲得對應的Binder物件的引用時,其獲得的其實是一個Proxy物件,然後其通過這個Proxy物件去跟驅動,再由驅動去跟服務端的Binder物件進行通訊。
而通過AIDL生成的程式碼,我們就可以從程式碼的層面來幫助我們更好地理解關於Binder機制的作用了。
看來,面試變成了上機操作。。。
我們還是先寫一份AIDL檔案,定義一個介面和對應的方法。
首先,我為什麼是定義介面呢?仔細想想,其實在不同的程序中進行通訊,無非就是想使用彼此的服務,而服務的一個非常好的表現形式就是對介面程式設計,也就是說,我們只需要知道服務提供的名稱是什麼,需要什麼引數,而具體服務端怎麼實現,我們並不關心。當服務端改變了實現,它也只需要保持介面的一致性,就不會影響客戶端的使用。
package com.lms.aidl; import java.util.List; import com.lms.aidl.Bean; interface ITestService { List<Bean> getBean(); void addBean(in Bean bean); }
看起來很像Java中的介面嘛。
利用ADT外掛,在Eclipse中會生成如下的java程式碼
/* * This file is auto-generated. DO NOT MODIFY. * Original file: ...\\AidlServer\\src\\com\\lms\\aidl\\ITestService.aidl */ package com.lms.aidl; public interface ITestService extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.lms.aidl.ITestService { private static final java.lang.String DESCRIPTOR = "com.lms.aidl.ITestService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.lms.aidl.ITestService interface, * generating a proxy if needed. */ public static com.lms.aidl.ITestService asInterface( android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.lms.aidl.ITestService))) { return ((com.lms.aidl.ITestService) iin); } return new com.lms.aidl.ITestService.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_getBean: { data.enforceInterface(DESCRIPTOR); java.util.List<com.lms.aidl.Bean> _result = this.getBean(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBean: { data.enforceInterface(DESCRIPTOR); com.lms.aidl.Bean _arg0; if ((0 != data.readInt())) { _arg0 = com.lms.aidl.Bean.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBean(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.lms.aidl.ITestService { 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 java.util.List<com.lms.aidl.Bean> getBean() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.lms.aidl.Bean> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBean, _data, _reply, 0); _reply.readException(); _result = _reply .createTypedArrayList(com.lms.aidl.Bean.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBean(com.lms.aidl.Bean bean) 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 ((bean != null)) { _data.writeInt(1); bean.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBean, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public java.util.List<com.lms.aidl.Bean> getBean() throws android.os.RemoteException; public void addBean(com.lms.aidl.Bean bean) throws android.os.RemoteException; }
我們來仔細看這份程式碼,可以看到,就是定義了三個類:
1)介面ITestService和其對應的方法
2)Stub類
3)Proxy類
其中,Stub類是一個抽象類,繼承了Binder類,並且實現了ITestService。其實這就是在我們定義服務端服務時需要去實現的Binder類,也就是說,當我們建立一個服務,並且希望這個服務能夠被跨程序使用的話,我們就可以在我們的服務中去實現這樣一個Stub類,在其定義的方法中去實現對應的邏輯,這個我在下一篇文章中會給出對應的例子。
而在Stub類中呢,又定義了一個Proxy類,同樣也實現了ITestService介面,所以對於客戶端程序來說,其與Proxy通訊,感覺就會像跟伺服器端的Binder類通訊一樣,因為兩邊暴露出來的方法都是一樣的,這也是設計模式中代理模式的一個非常典型的應用。
而實際上Proxy類則是通過一個IBinder型別的mRemote物件來跟驅動進行互動,並將對應的資料資訊通過驅動與服務端的程序進行互動,而Android的Binder類,其實也是實現了IBinder介面,如下:
public class Binder implements IBinder {
/*
* Set this flag to true to detect anonymous, local or member classes
* that extend this Binder class and that are not static. These kind
* of classes can potentially create leaks.
*/
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Binder";
private int mObject;
所以在這裡,Proxy類中,又可以將mRemote物件當成服務端的Binder物件來對待,跟mRemote物件通訊就相當於跟服務端的Binder物件通訊了。
說了這麼多次服務端的是用Stub類,也即Binder物件,客戶端的是使用Proxy類,雖然Proxy類中也是利用mRemote這個Binder介面,那麼,在具體的程序中,比如就是A程序,它是怎麼去判斷拿哪個物件呢?到底是拿Stub類呢,還是拿Proxy類呢?
這一點,我們也可以從上面這份程式碼中看出來哦!面試官好像感覺有點意思的樣子呢!!!!
我們在Stub類中可以看到如下方法:
/**
* Cast an IBinder object into an com.lms.aidl.ITestService interface,
* generating a proxy if needed.
*/
public static com.lms.aidl.ITestService asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.lms.aidl.ITestService))) {
return ((com.lms.aidl.ITestService) iin);
}
return new com.lms.aidl.ITestService.Stub.Proxy(obj);
}
這是將一個IBinder介面的物件轉變成我們定義的介面物件,在這裡,我們可以發現,當某程序去將某IBinder介面物件轉化成介面時,其會先去利用IBinder物件的queryLocalInterface方法去獲取有沒有本地的介面物件,也即Stub物件,如果沒有的話,它就會建立一個Proxy物件?為什麼會這樣呢?因為找不到的話,就說明那個Binder物件並不是在本程序內,那就是要進行程序間通訊,那你是異程序,當然要建立一個Proxy物件,我就是這麼理解的,雖然說起來很繞,很暈,但我就覺得這樣想應該是對的。
或者,我們再看進去Binder類中的queryLocalInterface方法,
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
這個descriptor是我們建立這份程式碼裡自動生成的一個常量,其實就是指定了當前服務的描述符。如果相同,就會返回mOwner,而mOwner就是實現這個介面的服務的一個例項,其是在attachInterface方法中定義的,如下:
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
而我們從Stub的建構函式中可以看到,owner就是我們實現的某個Stub物件,對吧。
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
不僅如此,Stub類中,還給定義的介面方法定義了識別符號,如下:
static final int TRANSACTION_getBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
利用這些識別符號,再通過onTransact方法去獲取驅動中的資料,就能夠知道客戶端是想要使用服務端哪個方法了。
啊!差點忘了,onTransact方法是幹什麼的?
其實呀,從上面AIDL生成的這份程式碼中,我們就可以看到,onTransact方法其實主要就是在使用者空間和核心空間中進行資料的交換,也就是實現程序間資料的互動,從而來通知彼此應該要幹什麼事。
傳遞的資料都在Parcel引數data和reply中,關於這些,我覺得無非就是資料互動所定的協議和規範,讀取順序之類的東西,好像真要講,我得去多多補充知識才能講得清楚。
太晚了,面試官都睡著了,這打呼的聲音。。。。
稍微總結一下,AIDL檔案,其實是一份輔助的檔案,因為有了ADT的存在,其才能發揮作用,因為ADT能夠根據我們定義的AIDL檔案,生成對應的Stub類和Proxy類等Binder機制相關的程式碼。
所以,對於一些大牛來說,完全可以不需要AIDL檔案,直接就可以寫出ADT生成的這些程式碼,並實現程序間的通訊,所以AIDL,只是簡化IPC開發的一個小工具而已,其實跟IPC本身並沒有什麼關係。
當然也就不能說利用AIDL來實現IPC,最多隻能說,利用AIDL和ADT來實現Binder機制,從而實現IPC。