Android AIDL最簡單易懂的使用與解析(2)
在上篇文章 Android AIDL最簡單易懂的使用與解析(1)中,我們學會了如何編寫一個簡單的 AIDL 來進行跨程序通訊,本著知其然更要知其所以然的道理,在這一篇中我們就來具體看看 AIDL幫我們生成了一套基於 Binder 的怎樣的介面吧~
我們先來看看testAIDL的結構:
可以看到,testAIDL是一個介面,繼承了 IInterface,而IInterface 就是程序間通訊定義的通用介面,我們在服務端實現介面,客戶端呼叫介面,就可以實現跨程序通訊。
package android.os; /** * Base class for Binder interfaces. When defining a new interface, * you must derive it from IInterface. */ public interface IInterface { /** * Retrieve the Binder object associated with this interface. * You must use this instead of a plain cast, so that proxy objects * can return the correct result. */ public IBinder asBinder(); }
IInterface 裡定義了一個 asBinder()方法,這個方法可以返回當前介面關聯的 Binder 物件。
testAIDL 中還包含了我們宣告的三個抽象方法,和一個非常重要的 靜態抽象類Stub。
Stub 繼承了 Binder,同時實現了testAIDL,所以說它就是Binder與介面的共同載體,在一些操作後,使得我們宣告的方法能基於Binder 上執行,從而擁有著跨程序通訊的能力。
Stub
來看看Stub的結構:
可以看到其實很簡單,就是4個常量,一個 Proxy 靜態內部類和幾個方法,現在我們結合程式碼來逐一分析:
/** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.sheep.testAIDL { //用於標識testAIDL中定義的不同方法,在onTransact()中進行區分 static final int TRANSACTION_addJRS = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_reduceJRS = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_getJRSList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); //唯一標識,一般即為該AIDL.java的完整路徑 private static final java.lang.String DESCRIPTOR = "com.sheep.testAIDL"; /** * Construct the stub at attach it to the interface. */ //無參構造方法,attachInterface的作用就是將當前的IInterface、testAIDL識別符號與當前Binder繫結起來 public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.sheep.testAIDL interface, * generating a proxy if needed. */ //傳入一個IBinder物件,將其轉換成testAIDL物件 //obj.queryLocalInterface(DESCRIPTOR)該方法即為傳入的IBinder物件,通過唯一識別符號在本地查詢是否有符合的介面IInterface,如果沒有,則返回null //如果通過queryLocalInterface()方法找不到,那麼就會返回當前靜態內部類Proxy代理 //總結一下就是:如果不是跨程序,那麼就會通過傳入的IBinder,直接返回,如果是跨程序,則返回其代理物件 public static com.sheep.testAIDL asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.sheep.testAIDL))) { return ((com.sheep.testAIDL) iin); } return new com.sheep.testAIDL.Stub.Proxy(obj); } //因為繼承了IInterface,則需重寫此方法,返回當前介面對應的Binder物件 @Override public android.os.IBinder asBinder() { return this; } //當呼叫IBinder.transact()給一個IBinder物件傳送請求時,最終就會通過Binder.onTransact()得到呼叫,通過不同的code,分別進行處理 //這個過程既可以在同一個程序中進行,也可以跨程序進行,其parcel物件,即是對傳入與返回的資料進行序列化 //需要注意一點:IBinder.transact() 方法是同步的,它被呼叫後一直到 Binder.onTransact() 呼叫完成後才返回。 @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_addJRS: { data.enforceInterface(DESCRIPTOR); com.sheep.zk.test.JRS _arg0; if ((0 != data.readInt())) { _arg0 = com.sheep.zk.test.JRS.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addJRS(_arg0); reply.writeNoException(); return true; } case TRANSACTION_reduceJRS: { data.enforceInterface(DESCRIPTOR); com.sheep.zk.test.JRS _arg0; if ((0 != data.readInt())) { _arg0 = com.sheep.zk.test.JRS.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.reduceJRS(_arg0); reply.writeNoException(); return true; } case TRANSACTION_getJRSList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.sheep.zk.test.JRS> _result = this.getJRSList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); } //當不在一個程序時,asInterface()返回的代理 private static class Proxy implements com.sheep.testAIDL {} }
邏輯很簡單,大家結合註釋與程式碼耐心看一遍,相信都能明白。
這裡有一點補充說明下:我們都知道 obj.queryLocalInterface(DESCRIPTOR)的作用是通過該AIDL的唯一識別符號來查詢本地是否有符合的介面IInterface,如果沒有則返回 Stub.Proxy(obj)的代理物件,這裡的區別就在於呼叫 asInterface() 該方法時是同一程序還是跨程序。
舉個簡單的例子:比如我們Activity a與Service b進行繫結,在Service中,我們建立一個mIBinder物件:
private IBinder mIBinder=new testAIDL.Stub() { @Override public void addJRS(JRS jrs) throws RemoteException { } @Override public void reduceJRS(JRS jrs) throws RemoteException { } @Override public List<JRS> getJRSList() throws RemoteException { return mJRS; } };
在onBinder()方法中將其返回:
@Nullable @Override public IBinder onBind(Intent intent) { return mIBinder; }
這時,我們在Activity a中bind Service b,則在 onServiceConnected方法中,便能得到 b中傳入的IBinder物件,將其打印出來:
private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("sheep",service.toString()); //連線後拿到 Binder,轉換成 AIDL,在不同程序會返回個代理 myAIDL=testAIDL.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { myAIDL=null; } };
此時,如果a與b在同一程序中,列印結果為:
E/sheep: [email protected]
如果a與b不在同一程序,則列印結果為:
E/sheep: [email protected]
由上可知,同進程時,在onServiceConnected接收到的是testAIDL.Stub()型別,所以此時queryLocalInterface()能返回mOwner物件(也就是 attachInterface() 方法中傳入的其本身),則呼叫asInterface返回的是Stub物件,其實就是在onBind中返回的mBinder;跨程序呼叫時,onSericeConnected中接收到的service為android.os.BinderProxy型別,所以此時queryLocalInterface()返回null,則呼叫asInterface返回的是Stub.Proxy物件。
我們簡單總結下 Stub類的方法:
- 無參構造方法,呼叫attachInterface(),將當前的IInterface、testAIDL識別符號與當前Binder繫結起來,後續queryLocalInterface()就可以使用其進行判斷並返回;
- onTransact()方法,當呼叫IBinder.transact()給一個IBinder物件傳送請求時,最終就會通過Binder.onTransact()得到呼叫,通過不同的code,分別進行處理;
- asInterface()方法,傳入一個IBinder物件,將其轉換成testAIDL物件,若是跨程序操作,則返回的是傳入IBinder物件的代理Proxy。
Proxy
我們最後來看下Stub類中的靜態內部類Proxy:
private static class Proxy implements com.sheep.testAIDL {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
} @Override
public android.os.IBinder asBinder() {
return mRemote;
}
/**
* 傳參時除了Java基本型別以及String,CharSequence之外的型別都需要在前面加上定向tag。AIDL中的定向 tag 表示了在跨程序通訊中資料的流向,
* 其中 in 表示資料只能由客戶端流向服務端, out 表示資料只能由服務端流向客戶端,而 inout 則表示資料可在服務端與客戶端之間雙向流通。
* 其中,資料流向是針對在客戶端中的那個傳入方法的物件而言的。in 為定向 tag 的話表現為服務端將會接收到一個那個物件的完整資料,
* 但是客戶端的那個物件不會因為服務端對傳參的修改而發生變動;out 的話表現為服務端將會接收到那個物件的引數為空的物件,
* 但是在服務端對接收到的空物件有任何修改之後客戶端將會同步變動;inout 為定向 tag 的情況下,服務端將會接收到客戶端傳來物件的完整資訊,
* 並且客戶端將會同步服務端對該物件的任何變動。
*/
@Override
public void addJRS(com.sheep.zk.test.JRS jrs) 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 ((jrs != null)) {
_data.writeInt(1);
jrs.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addJRS, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void reduceJRS(com.sheep.zk.test.JRS jrs) 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 ((jrs != null)) {
_data.writeInt(1);
jrs.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_reduceJRS, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<com.sheep.zk.test.JRS> getJRSList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.sheep.zk.test.JRS> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getJRSList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.sheep.zk.test.JRS.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
就是一個簡單的動態代理,主要就是對資料進行Parcel 序列化與反序列化的處理,以便進行跨程序操作,處理資料後,直接就呼叫實際的IBinder物件來處理。
綜上所述,我們可知AIDL實際上就是根據我們定好的介面,幫我們自動生成了可跨程序的介面,Binder與介面的共同載體Stub以及IPC操作時客戶端拿到的代理類Proxy。
通過對以上程式碼的學習理解,現在我們完全可以手動編寫Binder啦~還有一點需要注意,當我們進行IPC請求時,當前執行緒會掛起直到服務端返回,所以需要注意若會發生耗時操作則需要在客戶端內的單獨執行緒呼叫服務,而在服務端Binder方法的呼叫是執行在Binder執行緒池中的。