Binder上層原理淺析
1. IBinder簡介
Binder實現IBinder接口,IBinder是一個接口,代表一種跨進程傳輸的能力,實現此接口,就可以將此對象進行跨進程傳遞。
根據IBinder的源碼介紹及分析,可以瞭解很多信息(譯自IBinder的源碼介紹並總結):
1. IBinder是高性能、輕量級進程間通信的基本接口,該接口定義了與遠程對象通信的協議。不能直接實現此接口,而要繼承自它的實現類Binder
。
2. IBinder主要API是transact()和Binder的onTransact(), 這兩個方法進行發送和接收Binder對象,調用trasact的返回數據會在onTransact回調後返回。
場景:調用IBinder的transact()
給IBinder對象發送請求,然後經過Binder的Binder.onTransact()
得到調用,遠程操作的目標得到回調。
IBinder的transact方法:
/**
* Perform a generic operation with the object.
*
* @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
* @param data Marshalled data to send to the target. Must not be null.
* If you are not sending any data, you must create an empty Parcel
* that is given here.
* @param reply Marshalled data to be received from the target. May be
* null if you are not interested in the return value.
* @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
*/
public boolean transact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException;
與他對應的Binder的onTransact方法:
/**
* Default implementation is a stub that returns false. You will want
* to override this to do the appropriate unmarshalling of transactions.
*
* <p>If you want to call this, call transact().
*/
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {}
兩個方法的參數一致,參數具體意義是什麼?
- code:要執行的動作,類似於Handler的what,自定義Code值需要在
IBinder.FIRST_CALL_TRANSACTION
(0x00000001)和IBinder.LAST_CALL_TRANSACTION
(0x00ffffff)之間。其中IBinder內部定義了部分code:
- PING_TRANSACTION:表示要調用
pingBinder()
- DUMP_TRANSACTION:獲取Binder內部狀態
- SHELL_COMMAND_TRANSACTION:執行一個Shell命令
- INTERFACE_TRANSACTION:詢問接收方的接口描述符號
- TWEET_TRANSACTION:發送推文給目標對象
- LIKE_TRANSACTION
- PING_TRANSACTION:表示要調用
- data:傳入的數據,傳入的數據不能爲null,如果沒有要傳遞的數據,也要創建一個空對象
- reply:返回的數據,如果回調未返回數據,可以爲空
- flags:額外的標誌
- 0:普通的遠程通信過程
- FLAG_ONEWAY:單向通信,意味着client的
transact()
單向調用立即返回,而不用等待被叫者的結果。 僅在調用者和被調用者處於不同的進程時適用。
注意:IBinder的transact()是同步方法,被調用後會一直阻塞直到Binder.onTransact()調用完成後纔會返回結果。
3. 通過transact()發送的數據是一個Parcel
對象,Parcel
中保存了數據以及描述數據的元數據,元數據在緩存區中保持了 IBinder 對象的引用,這樣不同進程都可以訪問同一個數據。
元數據用於管理緩衝區中的IBinder對象的引用,確保當IBinder寫入Parcel對象發送到另一個進程時,另一個進程發送同一個IBinder回到原來的進程,原進程接收的IBinder對象和開始發送的是同一個引用。
跨進程傳輸後引用未發生改變,使得IBinder/Binder對象在跨進程通信時可以作爲唯一標誌(用作token或其他目的)
4. 系統在每個進程中都有一個處理事物的線程池,這些線程用於調度其他進程對當前進程的跨進程訪問。
比如說進程 A 對進程 B 發起 IPC 時,A 中調用 transact()
的線程會阻塞。B 中的事物線程池收到 A 的 IPC,調用目標對象的 Binder.onTransact()
方法,然後返回帶結果的 Parcel。一旦接收到結果,A 中阻塞的線程得以繼續執行。
5. Binder支持進程間的遞歸調用。
比如,進程 A 向進程 B 發起 IPC,而進程 B 在其 Binder.onTransact()
中又用 transact()
向進程 A 發起 IPC,那麼進程 A 在等待它發出的調用返回的同時,也會響應 B 的調用,對調用的對象執行 Binder.onTransact() 方法。
6. 跨進程通信時,IBinder提供三種方法來檢測遠程進程是否可用
- transact():在其進程不再存在的IBinder上調用它,則transact()方法將引發RemoteException異常。
- pingBinder():如果遠程進程不再存在,則會返回false。
- linkToDeath():可以使用linkToDeath()方法向IBinder註冊一個DeathRecipient,當其所在的進程消失時將調用它。
/**
* Check to see if the object still exists.
*
* @return Returns false if the
* hosting process is gone, otherwise the result (always by default
* true) returned by the pingBinder() implementation on the other
* side.
*/
public boolean pingBinder();
/**
* Register the recipient for a notification if this binder
* goes away. If this binder object unexpectedly goes away
* (typically because its hosting process has been killed),
* then the given {@link DeathRecipient}'s
* {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
* will be called.
*
* <p>You will only receive death notifications for remote binders,
* as local binders by definition can't die without you dying as well.
*
* @throws RemoteException if the target IBinder's
* process has already died.
*
* @see #unlinkToDeath
*/
public void linkToDeath(DeathRecipient recipient, int flags)
throws RemoteException;
/**
* Interface for receiving a callback when the process hosting an IBinder
* has gone away.
*
* @see #linkToDeath
*/
public interface DeathRecipient {
public void binderDied();
}
2. Binder 代碼分析
在上篇: Binder : AIDL的實踐中AIDL自動生成了接口文件,分析一下生成的代碼:
生成的接口IUserManager繼承自IInterface,並聲明瞭在IUserManager.aidl中聲明的方法
根據接口描述分析:
- IInterface:Binder的基類接口,Binder通信中當定義一個新接口,必需繼承自IInterface。
- IInterface.asBinder:返回值IBinder,檢索與此接口關聯的Binder對象。 您必須使用此方法進行簡單轉換,以便代理對象可以返回正確的結果。
IUserManager聲明瞭一個內部類Stub,Stub繼承自Binder實現IUserManager,是一個Binder類
Stub內部類實現了IUserManager接口和接口的方法,並定義了幾個int類型的整數,整數主要用於標識在transact和onTransact中客戶端請求的到底是什麼方法。
內部類Stub聲明一個內部代理類Proxy
代理類Proxy主要完成方法實現及transact調用過程。
分析: 接口的核心實現是內部類Stub和Stub的內部代理類Proxy,着重分析兩個類的方法含義。
DESCRIPTOR
private static final java.lang.String DESCRIPTOR = "com.gqq.binderaidl.IUserManager";
Binder的唯一標識,一般用當前Binder的類名錶示,類似於Token。
asInterface(android.os.IBinder obj)
/**
* Cast an IBinder object into an com.gqq.binderaidl.IUserManager interface,
* generating a proxy if needed.
*/
public static com.gqq.binderaidl.IUserManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
// 嘗試檢索此Binder對象接口的本地實現。
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.gqq.binderaidl.IUserManager))) {
return ((com.gqq.binderaidl.IUserManager) iin);
}
return new com.gqq.binderaidl.IUserManager.Stub.Proxy(obj);
}
用於將服務端的Binder對象轉換爲客戶端所需要的AIDL接口類型的對象,轉換是區分進程的,如果在同一個進程,則返回服務端的Stub本身,否則返回的是系統封裝後的Stub.proxy對象,根據queryLocalInterface
可以得知,如果返回null,必須要實例化代理類調用transact(),所以返回Stub.proxy對象。
asBinder
@Override
public android.os.IBinder asBinder() {
return this;
}
返回當前的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: {}
case TRANSACTION_basicTypes: {}
case TRANSACTION_addUser: {}
case TRANSACTION_getUserList: {}
}
return super.onTransact(code, data, reply, flags);
}
方法運行在服務端的Binder線程池中,當客戶端發起請求,遠程請求會通過底層封裝後交給此方法處理,此方法完成後將結果返回給發起請求者。
- 根據code確定客戶端請求的是什麼目標方法。
- 從data中取出目標方法所需要的參數,如果有參數的話,然後執行目標方法。
- 目標方法執行完畢後,向reply中寫入返回值(如果目標方法有返回值的話)
- 如果此方法返回false,客戶端請求會失敗,可以利用這個特性做權限驗證,來避免隨便一個進程都可以調用我們的服務。
Proxy#addUser()、getUserList()
兩個方法都運行在客戶端,大致實現:
- 創建該方法需要的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象list
- 把該方法的參數信息寫入_data中(如果有參數的話)
- 調用transact()方法發起RPC(遠程過程調用)請求,同時線程掛起,等待服務端onTransact調用及RPC過程返回,當前線程繼續執行,並從reply中取出返回結果,最後返回_replay的數據,沒有返回值,就不返回。
注意:客戶端發起遠程請求時,當前線程會掛起直到服務端進程返回數據,所以如果一個遠程方法是耗時的操作,那麼不能在UI線程中發起此遠程請求。
工作流程圖:
創建的aidl文件只是爲了幫助生成接口,也可以不借助aid,自己創建接口。
在進程間通訊時兩個重要的方法:linkToDeath
和unlinkToDeath
上述有介紹說linkToDeath可以檢測服務端進程是否異常,如果斷裂,導致遠程調用失敗,而客戶端未知,功能會受影響。爲了解決這個問題,所以提供了兩個配對的方法:linkToDeath
和unlinkToDeath
,通過linkToDeath
可以給Binder設置一個死亡代理,當Binder死亡時,我們會收到通知。
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (userManager == null) {
return;
}
Log.i("TAG", "binderDied");
userManager.asBinder().unlinkToDeath(deathRecipient, 0);
userManager = null;
// 重新綁定遠程Service
bindUserService();
}
};
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
userManager = IUserManager.Stub.asInterface(service);
// 設置死亡代理
try {
service.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
Log.i("TAG", "RemoteException");
e.printStackTrace();
}
}
}
也可以通過isBinderAlive判斷Binder是否死亡:
userManager.asBinder().isBinderAlive();
以上僅針對Java層進行的分析,有問題請指正~源碼分析原理還需要繼續努力!
推薦資料: