寫在前面:
本文摘自《Android開發藝術探索》。
什麼是Binder?
Binder是Android中的一個類,實現了IBinder接口。
從IPC(進程間通信)角度講,Binder是Android中的一種跨進程通信方式,Binder可以理解爲一種虛擬的物理設備,它的驅動是/dev/binder,該通信方式在Linux中沒有;
從Android Framework角度講,Binder是ServiceManager連接各種Manger(ActivityManager、WindowManager,等等)和相應ManagerService的橋樑;
從Android 應用層將,Binder是客戶端和服務端進行通信的媒介,當bindService時,服務器會返回一個包含服務業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
什麼時候使用Binder?
Binder主要使用在Service中,包括AIDL和Messenger(Messenger的底層其實是AIDL)。
什麼是AIDL?
AIDL:Android Interface Definition Language,安卓接口定義語言。
Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。Learn
more>>
通俗地講,AIDL爲我們提供一種快速實現Binder的工具。(後續會說到)
AIDL文件代碼如下:
// Book.aidl
package com.cooffee.studypro.aidl;
parcelable Book;
// IBookManager.aidl
package com.cooffee.studypro.aidl;
import com.cooffee.studypro.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
系統自動幫我們生成IBookManager接口的java代碼文件,代碼結構如下:
interface IBookManager extends IInterface {
abstract class Stub implements IBookManager {
asInterface() {...};
asBinder() {...};
onTransact() {...};
abstract class Proxy implements IBookManager {
asBinder() {...};
getInterfaceDescriptor() {...};
getBookList() {...};
addBook() {...};
}
}
getBookList(); // 抽象方法
addBook(); // 抽象方法
// 兩個方法標記ID的聲明...
}
下面詳細介紹針對這兩個類的每個方法的定義:
DESCRIPTOR:
Binder的唯一標識,一般用當前Binder的類名標識,比如“com.example.aidl.IBookManager”。
asInterface(android.os.IBinder obj):
將服務器端的Binder對象轉換成客戶端所需的AIDL接口類型的對象。
該轉換過程是區分進程的:
如果客戶端和服務端位於統一進程,此方法返回服務端的Stub對象本身;
否則返回的是系統封裝的Stub.proxy對象。
asBinder():
用於返回當前Binder對象。
onTransact():
此方法運行在服務器端的Binder線程池,當客戶端發起跨進程請求時,遠程請求會通過系統低層封裝後交由次方法來處理。
原型:
public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。
code:服務器通過code,確定請求的目標方法是什麼。
data:從data中取出目標方法所需的參數(如果目標方法有參數),然後執行目標方法。(感覺像:方法執行的入口)
reply:向reply寫入返回值(如果請求的方法有返回值的話)。(感覺像:方法執行的出口)
注意:如果此方法返回false,那麼客戶端的請求會失敗,因此我們利用這個特性來做權限驗證,畢竟我們也不希望隨便一個進程都能遠程調用我們的服務。
Proxy#getBookList:
此方法運行在客戶端,當客戶端遠程調用此方法時,其內部實現如下:
創建該方法的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List;
把該方法的參數信息寫入_data(如果有參數);
調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;
服務器端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply中取出RPC過程的返回結果;
返回reply中的數據。
Proxy#addBook:
執行過程與getBookList過程相同,但是該方法沒有返回值。
說明一下:
1. 當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是耗時的,不能在UI線程發起此遠程請求
2. 由於服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該採用同步的方式實現,因爲它已經運行在一個線程中了。
Binder的工作機制
從上圖我們可以看出,實現Binder的工作機制中並沒有出現我們當初建立的AIDL文件,之所以提供AIDL文件,是爲了方便系統爲我們生成代碼(IBookManager接口及其內部類)。
如果我們客戶端遠程請求工程中,中途斷掉了怎麼辦?
Binder運行在服務端進程中,如果服務端進程由於某種原因異常終止,這個時候我們到服務端的Binder連接斷裂(稱之爲Binder死亡),會導致我們的遠程調用失敗。而且我們不知道Binder連接是什麼時候斷裂的,那麼客戶端的功能會受到影響。
Binder提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以爲Binder設置一個死亡代理,當Binder死亡時,我們會收到通知,這個時候我們就可以重新發起連接請求從而恢復連接。
首先,聲明一個DeathRecipient對象。DeathRecipient是一個接口,其內部只有一個方法binderDied,我們需要實現這個方法,當Binder死亡的時候,系統就會回調binderDied方法,然後我們就可以移出之前綁定的binder代理並重新綁定遠程服務:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager - null;
// TODO: 重新綁定遠程service
}
};
其次,在客戶端綁定遠程服務成功後,給binder設置死亡代理:
mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
其中linkToDeath的第二個參數是個標記位,我們直接設爲0即可。
另外,Binder的方法isBinderAlive也可以判斷Binder是否死亡。
附錄:
AIDL文件生成IBookManager接口代碼
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Volumes/J_Eric/J_workRoom/AndroidStudio/StudyPro/app/src/main/aidl/com/cooffee/studypro/aidl/IBookManager.aidl
*/
package com.cooffee.studypro.aidl;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.cooffee.studypro.aidl.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.cooffee.studypro.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.cooffee.studypro.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.cooffee.studypro.aidl.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.cooffee.studypro.aidl.IBookManager))) {
return ((com.cooffee.studypro.aidl.IBookManager)iin);
}
return new com.cooffee.studypro.aidl.IBookManager.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_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.cooffee.studypro.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.cooffee.studypro.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.cooffee.studypro.aidl.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.cooffee.studypro.aidl.IBookManager
{
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.cooffee.studypro.aidl.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.cooffee.studypro.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.cooffee.studypro.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.cooffee.studypro.aidl.Book book) 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 ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.cooffee.studypro.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.cooffee.studypro.aidl.Book book) throws android.os.RemoteException;
}