Binder學(xué)習(xí)記錄

前言:本文是跟隨書本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)系如圖:

image.png

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的引用了。

image.png

底層的Binder Driver和ServerManger部分邏輯已經(jīng)由Android幫我們封裝好,所以我們只需要自己手動生成Binder Server和Binder Client就好了。而對于Binder Server服務(wù)端的生成,Android也提供了一個簡單的方式,就是AIDL。

第二部分:AIDL解析

先看看怎么使用

使用AIDL的流程可以分為服務(wù)端和客戶端兩個方面。

服務(wù)端:

  1. 首先用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ù)哦
}
  1. 然后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 {
        }
}
  1. 然后再定義一個服務(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 {
        這里也是呦
    }
}
  1. 然后定義一個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ù)雜:

  1. 構(gòu)造器中向父類傳入自己的實例和名字。
  2. 然后提供一個接口給客戶端來獲取自己這個實例(具體的底層操作在父類中實現(xiàn))
  3. 規(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的時候坑比較多。

  1. 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。

  2. 如果AIDL中使用了自定義Parcelable對象,那么還需要新建要給同名AIDL文件,并且該對象需要顯示的import。

  3. AIDL中除了基本數(shù)據(jù)類型,其他類型參數(shù)必須標上in、out、inout

  4. 客戶端最好不要在UI線程調(diào)用服務(wù)端的方法,因為服務(wù)端的方法可能需要執(zhí)行很久。但是服務(wù)端的方法本身是運行在線程池里的,所以Binder方法采用同步方式實現(xiàn)即可。

  5. AIDL中使用接口的問題:這個記不太清了。先留著吧

第五部分:其他細節(jié)

  1. 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,而前者不行。

  2. Binder連接的權(quán)限驗證問題

    我們不希望任何人都可以連接我們的遠程服務(wù),這個時候就需要加入權(quán)限驗證功能。
    有兩種方式可以實現(xiàn):方法一在onBind中進行權(quán)限驗證,方法二是在服務(wù)端的onTransact方法中進行驗證(還記得上面說的:返回false表示鏈接失敗嗎~)

  3. 文件共享的IPC方式最好不要用SharedPreference,因為系統(tǒng)內(nèi)存中會有一份SP文件的緩存,所以在多進程模式下對SP的讀寫操作就會不可靠,如果是高并發(fā)的讀寫,那么SP會有很大的幾率丟失數(shù)據(jù)

  4. 其他的進程間通信方式有文件共享、Messager、Socket、Bundle、ContentProvider。

這里面的區(qū)別或者給上別人的鏈接吧。

https://www.sogou.com/link?url=hedJjaC291OB0PrGj_c3jHbZOvsIrdBKsJUM538-RvZw7KPKo-CjB-0PsIg9LKFipYWeeMDqtvQ.

最后感謝《Android開發(fā)藝術(shù)探索》、《Android源碼設(shè)計模式解析與實戰(zhàn)》和1Offer同學(xué)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容