前言:本文是跟隨書本Android開發(fā)藝術(shù)探索的學(xué)習(xí)總結(jié),雖然說自己也看了下源碼,但是還停留在Binder運作的表層,并不設(shè)計Binder深處的運行細節(jié)。簡單的說,只到AIDL這一層,并不深入。其實和網(wǎng)上大多數(shù)文章都重復(fù),所以如過你想看深層次的,就可以右上角啦。
第一部分:介紹Binder
Binder是Android我們提供的跨進程通信方式。
Binder主要涉及4大模塊,分別是Binder Client、Binder Server、ServerManager和Binder Driver。其中Binder Driver位于內(nèi)核空間,而其余三者位于用戶空間。
用網(wǎng)絡(luò)訪問來類比的話,Binder Client 類似于客戶端,Binder Server類似于于服務(wù)端,ServerManager類似于DNS服務(wù)器,而Binder Driver類似于路由器,它們的關(guān)系如圖:

Binder Client 和 Binder Server 之間的跨進程通信統(tǒng)一通過Binder Driver轉(zhuǎn)發(fā)。
具體流程是:Server在生成一個Binder實體的同時會為其綁定一個名字并將這個名字封裝成一個數(shù)據(jù)包交 Driver,如果該Binder是新的,那 Driver就會為其在內(nèi)核空間中創(chuàng)建相應(yīng)的Binder實體節(jié)點和一個對該節(jié)點的引用,并將引用傳遞給ServerManger。而ServerManager就會把該Binder的引用和名字插入數(shù)據(jù)表中,就跟DNS中存儲域名到IP地址的映射原理類似。此時我們的Client就可以通過ServerManger來獲取Binder的引用了。

底層的Binder Driver和ServerManger部分邏輯已經(jīng)由Android幫我們封裝好,所以我們只需要自己手動生成Binder Server和Binder Client就好了。而對于Binder Server服務(wù)端的生成,Android也提供了一個簡單的方式,就是AIDL。
第二部分:AIDL解析
先看看怎么使用
使用AIDL的流程可以分為服務(wù)端和客戶端兩個方面。
服務(wù)端:
- 首先用AS提供的方法創(chuàng)建一個IBookManager.aidl文件,
package io.github.hoooopa.androidart.book; //包名
import io.github.hoooopa.androidart.book.Book; //Book.java的路徑導(dǎo)入
記得還需要在app的build.gradle中添加sorceset
interface IBookManager {
注意一下Book是自定義數(shù)據(jù)類,所以還需要定義一個Book.aidl文件
List<Book> getBookList();
void addBook(in Book book);//非直接支持的類型需要 in、out、inout參數(shù)哦
}
- 然后MakeProject后拿到AS為我們生成的最重要的文件IBookManager.java。
//IBookManager.java
//把里頭N多東西先刪除了就會發(fā)現(xiàn)這個結(jié)構(gòu)很簡單,
這個IBookManager繼承于IInterface,IIterface接口里只有一個方法:IBinder asBinder();
public interface IBookManager extends IInterface{
里面有個Stub類很重要,繼承IBookManager接口 ,
public static abstract class Stub extends Binder implements IBookManager {
而Stub 里還有個內(nèi)部類 Proxy也很重要,也繼承IBookManager接口。
private static class Proxy implements IBookManager {
}
}
- 然后再定義一個服務(wù)端類BookBinder,注意繼承于上面的Stub,如下:
//BookBinder.java
public class BookBinder extends IBookManager.Stub {
@Override
public List<Book> getBookList() throws RemoteException {
return null; 在這里執(zhí)行我們服務(wù)端的具體操作
}
@Override
public void addBook(Book book) throws RemoteException {
這里也是呦
}
}
- 然后定義一個process屬性的運行在獨立進程中的Service,用于和客戶端進行聯(lián)絡(luò)交互。如下:
//AIDLService.java
public class AIDLService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new BookBinder(); 這個BookBinder就在上面↑,繼承自IBookManager.Stub
}
}
所以服務(wù)端最主要的就是3個文件,第一個是用于和客戶端聯(lián)絡(luò)的Service,第二個是執(zhí)行方法的服務(wù)端,第三個就是執(zhí)行通信的IBookManager——該文件也可以不用AS生成,可以自己手撕呦
客戶端:
客戶端就很簡單了,綁定服務(wù),然后獲取Binder,
//Activity.java中
private IBookManager IBookBinder; //IbookManager是系統(tǒng)生成的那個
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
IBookBinder = IBookManager.Stub.asInterface(iBinder);這個操作和平時綁定service的操作很類似嘛
}
@Override public void onServiceDisconnected(ComponentName componentName) {
}
};
然后在我們的Activity的onCreate中綁定服務(wù)端中的Service
bindService(new Intent("io.github.hoooopa.androidart.chapter2.AIDLService."),conn,BIND_AUTO_CREATE);
然后就是調(diào)用方法就好了
try {
IBookBinder.addBook(new Book(1,"Android"));
} catch (RemoteException e) { 調(diào)用方法一定要加try/catch
e.printStackTrace();
}
客戶端走3步,第一步new一個ServiceConnection(),獲取到IbookManager對象。第二步和遠程Service建立綁定,第三步就是調(diào)用接口方法就好了。這里注意兩點就是調(diào)用接口要加try/catch并且這個接口可能是個耗時方法。
所以我們看到不管是定義AIDL文件還是Service或者客戶端的使用,都不復(fù)雜。真正復(fù)雜的東西在系統(tǒng)為我們生成的那個Stub及其Proxy內(nèi)部類中。所以接下來瞅瞅這個內(nèi)部類是咋工作的。
Stub類與Proxy解析
從外向內(nèi)看這個IBookManager,如下:
public interface IBookMangaer extends IInterface {
public List< Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
public static abstract class Stub extends Binder implements IBookManager {}
}
把很多影響閱讀的代碼刪掉了以后,很清楚就能發(fā)現(xiàn),這就是一個接口繼承了另一個接口,然后內(nèi)部還有個抽象靜態(tài)類。另外的getBookList()和addBook()不正是之前定義的接口方法么。
所有可以在Binder中傳輸?shù)慕涌诙夹枰^承Interface這個接口,Interface接口定義如下:
就一個方法,返回一個Ibinder類型的對象
public interface IInterface {
IBinder asBinder();
}
所以IBookManger這個接口是不是異常簡單。本身2個方法,然后繼承自IInterface,總共3個方法
所以我們還是繼續(xù)往里看Stub這個類。因為之前介紹使用流程的時候也說了我們的服務(wù)端的執(zhí)行類BookBinder extends IBookManager.Stub,所以Stub類必然是重頭戲。
詳細的解釋看下面的代碼里吧
//IBookManager.java中
注:(Stub是abstract的)由于Stub繼承了IBookManager,而IBookManager繼承自Iinterface,
所以該類或其子類需要實現(xiàn)把 getBookList,addbook,asBinder()三個方法。
由于Stub實現(xiàn)了asBinder,所以繼承Stub的子類需要實現(xiàn)getBookList和addBook。__應(yīng)該是這樣的吧-。-
static abstract class Stub extends Binder implements IBookManager { //這個Binder類,繼承自IBinder。
下面的這三個static final是用來標志的
private static final String DESCRIPTOR = "io.github.hoooopa.androidart.book.IBookManager"; 這個是Binder的唯一標志
static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);這兩個整型用于標志get和add兩個方法
構(gòu)造方法,傳入的是IInterface對象和上面的標志名。
這個標志名很重要
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
//下面這個方法還記么,在我們客戶端獲取Binder實體的地方用到了。IBookManger.Stub.asInterface(service)
1. 還記得這個obj是怎么來的么。
ServiceConnection中獲取的,也就是說要了解這個obj的本質(zhì),就得去看ServiceConnection的源碼。
不過可以猜測一下,bindService是綁定服務(wù),而綁定的Service服務(wù)中的onBinder()方法返回的是
我們自定義的BandkBinder對象。所以這個obj應(yīng)該就是那個BankBinder對象。當然還需要轉(zhuǎn)換一下。
2. 另外這個轉(zhuǎn)換過程是用進程來區(qū)分的,具體的操作在obj.queryLocalInterface中實現(xiàn)。
如果BookBinder是存在于本地進程(即Service沒有開啟process),那么就返回本地的那個
如果不是,就返回下面的Stub.proxy對象。(這里有一個代理模式)
public static IBookManager asInterface(IBinder obj) {
if ((obj==null)) {//如果
return null; //就
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof IBookManager))) { //如果
return ((IBookManager)iin); //就
}
return new IBookManager.Stub.Proxy(obj);//否則,就
}
@Override public IBinder asBinder() {
return this;
}
//下面這個方法是重寫父類的,onTransact在父類中的被調(diào)用邏輯目前我看不大懂,
反正這個方法很重要就是了。
注意:這個方法是運行在服務(wù)端的Binder線程池中的。
在講AIDL的使用的地方,我們知道有一個類是專門用來執(zhí)行服務(wù)端的操作,那里是對數(shù)據(jù)的加工過程。
而數(shù)據(jù)傳輸?shù)倪^程都在Binder.java內(nèi)部中實現(xiàn)了,
下邊這個方法的工作應(yīng)該就是個如何寫入和讀取數(shù)據(jù)的過程吧。
對了這里如果返回false的話就不給鏈接,所以可以在這做權(quán)限認證
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: 這個標志很熟吧,就上面定義的標志,用來區(qū)分調(diào)用啥方法
{
data.enforceInterface(DESCRIPTOR);
List<Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: 這個也在上面定義了
{
data.enforceInterface(DESCRIPTOR);
Book _arg0;
if ((0!=data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
內(nèi)部類,出現(xiàn)上面的asInterface()方法中。表示:如果是遠程的Binder,就讓我來做
private static class Proxy implements IBookManager {}
}
看完了這些代碼,其實就知道了Stub中的邏輯并不復(fù)雜:
- 構(gòu)造器中向父類傳入自己的實例和名字。
- 然后提供一個接口給客戶端來獲取自己這個實例(具體的底層操作在父類中實現(xiàn))
- 規(guī)定數(shù)據(jù)的寫入和讀取方法(數(shù)據(jù)的傳輸過程又是被封裝在父類中)
所以可以說大多數(shù)的工作都被Binder類做了,而Binder類中的重要工作也是交給了Binder driver去中的。
還有一個內(nèi)部類Proxy再講一講。在上面的代碼中我們看到,這個類是在跨進程中才會被用到。作用的講解也放在代碼中哈。如下:
private static class Proxy implements IBookManager {
mRemote就是在Stub類中傳入的那個obj,就是位于遠程的BankBinder
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override public IBinder asBinder()
{
return mRemote;
}
public String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
注這個方法是運行與客戶端的。在內(nèi)部使用mRemote.transact來調(diào)用服務(wù)端的方法
@Override
public List< Book> getBookList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List<Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR); //Token
//調(diào)用transact方法來發(fā)起RPC(遠程過程調(diào)用)請求,同時掛起當前線程。然后服務(wù)端的onTransact方法會被調(diào)用
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
//直到RPC過程返回后,當前線程繼續(xù)執(zhí)行,并從_reply中取出返回結(jié)果;
_reply.readException();
_result = _reply.createTypedArrayList(io.github.hoooopa.androidart.book.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
//最后返回_reply中的數(shù)據(jù)
return _result;
}
這方法和上面的方法一樣,調(diào)用遠程服務(wù)端的tracnsact方法。只不過沒有返回數(shù)據(jù)
@Override public void addBook(Book book) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = 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();
}
}
}
就Proxy而言,它是運行在客戶端中的,最后調(diào)用的mRemote.transact()方法的內(nèi)部操作是位于服務(wù)端中的,具體的操作又最后會到底層中去。
所以雖然一直說Stub這個是重頭戲,但是其實這些類個文件其實也并沒有做多少事情,Binder的真正精髓還是需要在更深處,就在文章最開始的那個圖里的交互處體現(xiàn)出來啊。
AIDL就說到這——話說講到最后都迷糊了:AIDL到底是啥,就是那個AIDL以及系統(tǒng)根據(jù)AIDL生成的那個文件嗎?-。-
Binder底層的話,見下面的這個鏈接吧。
https://blog.csdn.net/zjd934784273/article/details/67068329
第三部分:Binder連接池
《Android開發(fā)藝術(shù)探索》中還提到了Binder連接池。用來解決N個AIDL就要生成N個配套Service這種尷尬的局面。
我們新建一個AIDL文件IBinderPool.aidl里面只有一個接口
interface IBinderPool{
IBinder queryBinder(int binderCode);
}
并MakeProject一下,然后用一個IBinderPoolImpl繼承IBinderPool.Stub。
//BinderPoolImpl.java
public static class BinderPoolImpl extends IBinderPool.Stub{
public BinderPoolImpl{
super();//這個super會調(diào)用父類的構(gòu)造器里的那個 this.attachInterface(this, DESCRIPTOR);方法,
//傳入的是IBinderPool.Stub自身
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException{
IBinder binder = null;
Switch(binderCode){ //根據(jù)binderCode來返回對應(yīng)的IBinder
case xxxxx : binder = new SercurityImpl();//這個Impl熟悉不和IBinderPoolImpl一樣都是繼承自各自的xxxx.Stub。
…
…
default:break;return null;
}
}
這樣我們就清楚了當客戶端調(diào)用IBinderPoolImpl的時候就可以調(diào)用 queryBinder(int binderCode)方法通過binderCode來返回特定的IBinder(Binder類implements IBinder接口)。
然后我們建立的聯(lián)絡(luò)用的Service的onBind()方法里返回上面這個new IBinderPoolImpl 就好了。而具體的操作
這樣就能返回對應(yīng)的Binder啦。然后客戶端就可以拿到各自需要的Binder去玩耍啦。
當然如果要用到項目里還需要做很多操作,比如說可以綁定死亡代理,考慮并發(fā)什么的。
具體代碼可見《Adnroid藝術(shù)探索》P112。
第四部分:注意事項
使用AIDL的時候坑比較多。
-
AIDL并不支持所有的數(shù)據(jù)類型。
AIDL文件中支持的數(shù)據(jù)類型有:基本類型,String和CharSequence,ArrayList和Map并且內(nèi)部元素都必須能夠被AIDL支持,實現(xiàn)了Parcelable接口的對象,AIDL接口本身。Serializable和Parcelable的異同:Serializable和Parcelable都能實現(xiàn)序列化,Serializable是Java提供的序列化接口,使用簡單,但是開銷比較大,因為序列化和反序列化過程都需要大量I/O操作。Parcelable是Android中的序列化方式,使用起來比較麻煩,但是效率很高。另外要注意的是Parcelable主要用于內(nèi)存序列化上,如果要把對象序列化到存儲設(shè)備或者序列化之后進行網(wǎng)絡(luò)傳輸還是建議使用Serializable。
如果AIDL中使用了自定義Parcelable對象,那么還需要新建要給同名AIDL文件,并且該對象需要顯示的import。
AIDL中除了基本數(shù)據(jù)類型,其他類型參數(shù)必須標上in、out、inout
客戶端最好不要在UI線程調(diào)用服務(wù)端的方法,因為服務(wù)端的方法可能需要執(zhí)行很久。但是服務(wù)端的方法本身是運行在線程池里的,所以Binder方法采用同步方式實現(xiàn)即可。
AIDL中使用接口的問題:這個記不太清了。先留著吧
第五部分:其他細節(jié)
-
Binder意外死亡的問題:
服務(wù)端進程可能意外停止,這時我們需要重新連接服務(wù),有兩種方式可以實現(xiàn):
方式一是使用linkToDeath給Binder設(shè)置一個死亡代理,這樣當Binder死亡時,我們就會收到通知,也就可以重新發(fā)起連接請求恢復(fù)連接。
死亡代理設(shè)置方式:聲明一個DeathRecipient對象,在方法binderDied()中重新解除之前綁定的binder代理并重新綁定遠程服務(wù)。在客戶端綁定遠程服務(wù)后,調(diào)用binder.linkToDeath()傳入DeathRecipient對象,設(shè)置死亡代理。
方式二在onServiceDisconnected中重連遠程服務(wù)。
兩者的區(qū)別在于后者在客戶端UI線程中被回調(diào),可以訪問UI,而前者不行。 -
Binder連接的權(quán)限驗證問題:
我們不希望任何人都可以連接我們的遠程服務(wù),這個時候就需要加入權(quán)限驗證功能。
有兩種方式可以實現(xiàn):方法一在onBind中進行權(quán)限驗證,方法二是在服務(wù)端的onTransact方法中進行驗證(還記得上面說的:返回false表示鏈接失敗嗎~) 文件共享的IPC方式最好不要用SharedPreference,因為系統(tǒng)內(nèi)存中會有一份SP文件的緩存,所以在多進程模式下對SP的讀寫操作就會不可靠,如果是高并發(fā)的讀寫,那么SP會有很大的幾率丟失數(shù)據(jù)
其他的進程間通信方式有文件共享、Messager、Socket、Bundle、ContentProvider。
這里面的區(qū)別或者給上別人的鏈接吧。
最后感謝《Android開發(fā)藝術(shù)探索》、《Android源碼設(shè)計模式解析與實戰(zhàn)》和1Offer同學(xué)。