一、前言
之前學習了實名Binder《實名Binder》和AIDL的基本使用《AIDL的使用》,本篇文章在上兩篇的基礎上繼續學習匿名Binder,並結合aidl中的例子理解Binder在應用層面上具體是如何通信的,文中分析所用到的代碼有些是前兩篇文章中,這裏就不在重複粘貼了。
二、匿名Binder
平常開發過程中,通過aidl和binderService方式獲取的Binder是不會註冊到SM中的,這種Binder就是匿名Binder,匿名Binder要依賴於實名Binder進行傳遞,比如Service中的onBind方法返回的Binder對象是需要通過AMS來傳遞給客戶端的(具體的過程請參考Service的綁定過程《Service的綁定過程》)。
在之前的aidl例子中,RemoteService的onBind方法返回了一個LocalBinder對象,它繼承並實現了IBookManager.Stub,這個對象就是一個匿名的Binder對象,當客戶端binderService的時候,會調用到RemoteService的onBind方法,其返回值LocalBinder對象會通過AMS發佈出去,最終會通過ServiceConnection的onServiceConnected方法將LocalBinder信息(注意是信息,不是LocalBinder對象本身,因爲這個過程是客戶端和AMS進行跨進程通信的過程,在經過Binder驅動的時候,不能直接傳遞java對象,而是轉換成C/C++可識別的結構體進行傳遞數據的)回調給客戶端,客戶端的onServiceConnected拿到LocalBinder信息後,在通過Stub的asInterface方法獲取LocalBinder在本地的代理對象,由這個過程可以知道,LocalBinder這個匿名Binder是需要依賴於AMS這個實名Binder進行傳遞的。下面結合之前的aidl例子梳理下Binder的通信過程是怎麼樣的。
三、結合AIDL分析Binder的通信
從AIDL文件自動生成的IBookManager.java類中可以知道,其核心功能的實現在Stub和Stub的內部類Proxy兩個類中。
- Stub: Stub繼承了Binder又實現了IBookManager接口,所以Stub既是個Binder類又具備客戶端所需要的業務能力。
- Proxy: Proxy的構造方法中會拿到一個Binder代理對象,它對Binder代理對象進行了包裝,同時Proxy又實現了IBookManager接口,所以它具備Binder的能力也具備客戶端所需要的業務能力。
我們在客戶端中綁定服務的時候,會在連接的回調方法onServiceConnected中調用Stub的asInterface方法獲取IBookManager類型的一個對象,然後用這個對象去調用IBookManager裏面的業務方法。下面就以調用IBookManager裏面的addBook方法爲例來跟蹤一下整個調用過程,首先先看下ServiceConnection 的代碼:
//聲明IBookManager類型的對象
private IBookManager mIBookManager;
//創建連接
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//關鍵代碼1,獲取IBookManager類型的對象
mIBookManager = IBookManager.Stub.asInterface(service);
//關鍵代碼2,打印出mIBookManager的真實類型
if (mIBookManager instanceof IBookManager.Stub) {
Log.e("znh", "mIBookManager instanceof IBookManager.Stub");
}
Log.e("znh", "mIBookManager.getClass().getName():" + mIBookManager.getClass().getName());
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
由關鍵代碼1可知,通過Stub的asInterface方法獲取到了一個IBookManager 類型的對象,然後使用這個對象調用IBookManager 裏的業務方法,由於這個對象不是我們自己new出來的,所以它的真實類型我們就不好直接判定,那麼這個對象的真實類型是什麼呢,我們又是怎樣調用這個對象裏的方法的呢,這裏要分兩種情況進行討論:
1、客戶端和服務端在同一個進程
在這種情況下,我們先通過關鍵代碼2的log打印直觀的看一下mIBookManager的真實類型,下面附上打印結果:
mIBookManager instanceof IBookManager.Stub
mIBookManager.getClass().getName():com.znh.aidl.server.RemoteService$LocalBinder
從上面的打印結果可知,mIBookManager的真實類型就是服務端的LocalBinder對象本身,而LocalBinder繼承了Stub,所以它也是Stub類型的。當我們調用mIBookManager的addBook方法的時候,就是直接調用Stub裏的addBook方法,Stub裏的addBook方法的具體實現是由RemoteService的內部類LocalBinder完成的,所以最終調用的是服務端中LocalBinder的addBook方法。這個調用過程不涉及進程間通信,是一個常規調用過程。
上面通過log打印的方式直觀的看出了mIBookManager的真實類型和調用過程,下面根據相關代碼深入分析一下,首先先看下Stub的asInterface方法和asBinder方法:
Stub的asInterface方法:
/**
* Cast an IBinder object into an com.znh.aidl.server.IBookManager interface,
* generating a proxy if needed.
*/
public static com.znh.aidl.server.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//關鍵代碼3,如果客戶端和服務端是同一個進程,條件成立,直接返回服務端本地對象
if (((iin != null) && (iin instanceof com.znh.aidl.server.IBookManager))) {
return ((com.znh.aidl.server.IBookManager) iin);
}
//關鍵代碼4,如果客戶端和服務端不是一個進程,就創建一個服務端本地對象的代理對象
return new com.znh.aidl.server.IBookManager.Stub.Proxy(obj);
}
Stub的asBinder方法:
//返回當前Binder對象
@Override
public android.os.IBinder asBinder() {
return this;
}
由asInterface方法的代碼可知,如果客戶端和服務端在同一個進程,那麼該方法的參數就是LocalBinder的真身,是在RemoteService的onBinder方法裏返回的LocalBinder對象,在關鍵代碼3處,經過強轉就直接返回該真身,那麼此時調用LocalBinder的addBook方法是怎樣一個過程呢,經過分析可知,LocalBinder繼承了Stub,也就是說LocalBinder是Stub的實現類,通過asBinder方法可知,返回的當前Binder對象this就是Stub對象本身,也就是Stub的具體實現類服務端的LocalBinder本地對象,那麼就可以直接調用Stub裏面的addBook方法,不會走transact和onTransact邏輯,這個調用過程不跨進程。
2、客戶端和服務端不在同一個進程
在這種情況下,我們還是先通過關鍵代碼2的log打印直觀的看一下mIBookManager的真實類型,下面附上打印結果:
mIBookManager.getClass().getName():com.znh.aidl.server.IBookManager$Stub$Proxy
從上面的打印結果可知,此時mIBookManager的真實類型是Stub的內部類Proxy,另外通過Stub的asInterface方法中的關鍵代碼4處也可看出,該方法返回的是Proxy類型的對象。
此時asInterface方法的參數不再是服務端的LocalBinder本地對象,而是LocalBinder的一個代理對象。這點可以從Proxy的asBinder中看出:
Proxy的asBinder相關方法:
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
//返回遠程的Binder代理對象
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
那麼此時調用mIBookManager的addBook方法是怎樣一個過程呢,經過上面的分析可知,mIBookManager是Proxy類型的,那麼就會調用Proxy類裏面的addBook方法,Proxy的addBook方法如下:
Proxy的addBook方法:
/**
* 添加圖書
*/
@Override
public void addBook(com.znh.aidl.server.Book book) throws android.os.RemoteException {
//關鍵代碼5,創建攜帶參數信息的對象
android.os.Parcel _data = android.os.Parcel.obtain();
//關鍵代碼6,創建攜帶返回值信息的對象
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//關鍵代碼7,調用LocalBinder代理對象的transact發起遠程調用
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
- 通過關鍵代碼5可知,會創建一個Parcel 類型的對象_data用來攜帶客戶端調用方法時的參數信息。
- 通過關鍵代碼6可知,會創建一個Parcel 類型的對象_reply 用來攜帶服務端給客戶端的返回值。
- 通過關鍵代碼7可知,mRemote就是LocalBinder的代理對象,這個代理對象會調用它的transact方法發起遠程請求,在transact方法中會調用服務端LocalBinder的本地對象的onTransact方法,由於LocalBinder的onTransact方法是繼承Stub的,所以最終調用的是Stub的onTransact方法。
- 這個方法是運行在客戶端的,當客戶端發起該方法的遠程調用時,客戶端當前線程會被掛起,直到服務端返回結果,客戶端當前線程纔會繼續運行,所以這是一個同步過程。服務端的方法執行很可能是耗時操作,所以發起遠程調用時儘量不要在客戶端的主線程中,以免出現ANR。
在上面的遠程調用中,會調用到服務端Stub的onTransact方法,下面分析一下Stub的onTransact方法:
Stub的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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.znh.aidl.server.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
//關鍵代碼8,真正的處理addBook的邏輯
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.znh.aidl.server.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.znh.aidl.server.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
該方法是運行在服務端的Binder線程池中的,服務端通過code來匹配客戶端要調用的是哪個方法,在關鍵代碼8處可知TRANSACTION_addBook是客戶端調用addBook方法的code值。匹配到相應的方法後,就從data中取出該方法需要的參數信息,然後進行邏輯處理,最終將處理結果放到reply中返回給客戶端。這樣整個調用過程就結束了。
總之,如果客戶端和服務端在同一個進程中,客戶端要調用服務端某個對象的方法是直接獲取到服務端的本地對象進行操作的。如果客戶端和服務端不在同一個進程中,那麼客戶端要調用服務端某個對象的方法,會從SM中獲取到服務端該對象的一個代理對象進行操作的。