8.Binder詳解

8.1 Binder 簡介。
    
    Binder,英文的名稱是別針、回形針。現實中,我們經常會用回形針把紙張別起來,而在Android中,Binder是用於進程通信,它負責把不同的進程“別”起來,使得不同進程可以一起工作。比如,在導航軟件中,我們可以控制音樂的暫停、播放。
    Binder工作再Linux層,屬於一個驅動,只是這個驅動不需要硬件,或者說,它操作的硬件是基於一小段內存。從線程的角度來說,Binder是運行在內核態,客戶端調用Binder是通過系統調用完成的。
    Binder是Android進程通信的中最常用的方式,它的原理是通過訪問共享內存來實現的,因此,Binder的效率最高,它使得各個組件可以互相通信。進程間傳輸數據時只需拷貝一次,傳統的IPC需拷貝兩次。因此使用binder可大大提高IPC通信效率。
    直觀來說,Binder是Android的一個類,實現了IBinder接口,從IPC角度說,Binder是Android中一種跨進程通信的方式,Bindr還可以理解爲一種虛擬的物理設備,設備驅動是/dev/binder,該通信方式在Linux中沒有;從Android Framework的角度上來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager、等等)和相應ManagerService的橋樑,服務端會返回一個包含了服務端業務調用的Binder對象,通過Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括了普通的服務和基於AIDL的服務。

8.2 從AIDL開始學習Binder    

下面我們先從一個AIDL開始入手這個Binder:
     
   
 // Book.java
     public class Book implements Parcelable {

    public String bookName;

    public Book( String bookName )
    {
        this.bookName = bookName;
    }

    @Override
    public int describeContents() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void writeToParcel(Parcel arg0, int arg1) {
        arg0.writeString(bookName);
    }

    public static final Parcelable.Creator< Book> CREATOR = new Creator<Book>() {

        @Override
        public Book[] newArray(int arg0) {
            // TODO Auto-generated method stub
            return new Book[arg0];
        }

        @Override
        public Book createFromParcel(Parcel arg0) {
            // TODO Auto-generated method stub
            return new Book(arg0);
        }
    };

    public Book( Parcel in )
    {
        bookName = in.readString();
    }
}

        
   
 //Book.aidl
package com.zhenfei.aidl;

parcelable Book;


//IBookManager.aidl
package com.zhenfei.aidl;

import com.zhenfei.aidl.Book;

interface IBookManager{
    List<Book> getBookList();
    void addBook( in Book book);
}



上面三個文件當中,Book.java是表示書本信息的類,實現了Parcelable接口,在AIDL中使用Book這個類,需要以AIDL的形式聲明Book,如:Book.aidl。IBookManager.aidl是我們定義的一個接口,這個接口有2個方法,是用來從遠程服務器獲取圖書列表,而addBook用於往圖書列表當中添加一本書。另外,雖然Book和IBookManager是在同一個包裏面,不過也需要手動導Book類。之後,我們會發現,在gen文件夾中,自動生成了IBookManager.java類,這個是系統生成的,現在我們從這個類開始分析Binder的工作原理:

package com.zhenfei.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.zhenfei.aidl.IBookManager
    {
        private static final java.lang.String DESCRIPTOR = "com.zhenfei.aidl.IBookManager";

        /** Construct the stub at attach it to the interface. */
        public Stub()
        {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.zhenfei.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.zhenfei.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.zhenfei.aidl.IBookManager))) {
                return ((com.zhenfei.aidl.IBookManager)iin);
            }
            return new com.zhenfei.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.zhenfei.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook:
                {
                    data.enforceInterface(DESCRIPTOR);
                    com.zhenfei.aidl.Book _arg0;
                    if ((0!=data.readInt())) {
                        _arg0 = com.zhenfei.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.zhenfei.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.zhenfei.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.zhenfei.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.zhenfei.aidl.Book.CREATOR);
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            @Override 
            public void addBook(com.zhenfei.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 {+*-999999999
                    _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.zhenfei.aidl.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.zhenfei.aidl.Book book) throws android.os.RemoteException;
}



這個類看起來是比較亂,但是,我們現在先一部分一部分的看,就會發現,這個其實是邏輯很清楚的。
首先,我們看IBookManger這個類,拋開裏面的內部類,我們就知道,這個類其實就是定義了2個接口方法,也就是getBookList(),和addBook()方法。注意的是,這個接口集成了android.os.IInterface這個接口,AIDL中,可以在Binder中傳輸的接口都需要集成IInterface接口。
    官方對於IInterface的描述如下:
    Base class for Binder interfaces. When defining a new interface, you must derive it from IInterface.
    表示:這個是Binder中的接口的基類。當我們定義一個新的接口的時候,必須要從派生這個接口,這個接口的實際上做的就是,把Binder轉爲IIterface,或者是把IInterface轉爲一個Binder對象。
    接下來看IBookManager中的Stub類,這個類是一個抽象類,因爲IBookManager的兩個方法它並沒有去實現。並且這個Stub類繼承與Binder,因此,這個類就是我們現在要討論的。
    到此,我們先不看這個類裏面的方法,我們繼續完善AIDL模塊的編寫吧。
    接下來是服務端的實現代碼:
    
public class ServerService extends Service{

    ArrayList<Book> books = new ArrayList<Book>(); 

    private Stub stub = new Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
             return books;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            books.add(book);
        }
    };

    public void onCreate() {
        super.onCreate();

        Book book1 = new Book( "第1本書");
        Book book2 = new Book( "第2本書");
        Book book3 = new Book( "第3本書");

        books.add(book1);
        books.add(book2);
        books.add(book3);

        System.out.println( "有 "+ books.size() + " 本書"); 

    };

    @Override
    public IBinder onBind(Intent arg0) {

        return stub;
    }

}

可以看到,Stub這個類的接口方法,是在服務端實現的,並且,服務端會在onBind這個方法返回這個stub對象。
下面我們看下客戶端的代碼:
   
 public class MainActivity extends Activity {

    IBookManager iBookManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Intent toNewIntent = new Intent();
        toNewIntent.setAction( "com.zhenfei.myaidl");
        bindService(toNewIntent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    public ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            System.out.println( "onServiceDisconnected className :"  + arg0.getClassName()) ;
        }

        @Override
        public void onServiceConnected(ComponentName arg0, IBinder arg1) {
            iBookManager = IBookManager.Stub.asInterface(arg1);
            System.out.println( "onServiceConnected ");

            List<Book> books;
            try {
                books = iBookManager.getBookList();
                System.out.println( "客戶端獲得到: "+ books.size() + " 本書");

            } catch (RemoteException e) {
                 e.printStackTrace();
            }
        } 
    };
}

    結合上面的代碼以及服務端的代碼,我們可以知道,服務端返回binder對象stub,然後在客戶端接收這個對象,然後,我們就是用這個對象來進行遠程通信,那麼這個是怎麼做到的?客戶端和服務端是運行在不同進程的,他們是如何獲取到這個對象的呢?這個的關鍵就在於IBookManager的Stub類:
    下面,我們從Stub類開始入手:
    Stub類的構造函數:
    我們可以看到構造函數調用了這個方法:attachInterface( this , DESCRIPTOR );,其中參數是一個常量的字符串,DESCRIPTOR 一般是用於標識Binder,一般是用這個Binder的類名錶示。
    attachInterface是Binder類中的一個方法:
    
    /**
     * Convenience method for associating a specific interface with the Binder.
     * After calling, queryLocalInterface() will be implemented for you
     * to return the given owner IInterface when the corresponding
     * descriptor is requested.
     */
    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }

    
    可以看到,其實,在本例中這個方法就是,把IBookManager的對象綁定到了這個Binder當中,並且告知了這個Binder它自己叫什麼。
    接下來往下看到了asInterface方法,這個方法的作用從輸入和輸出來說,他就是把一個Binder對象轉化爲IBookManager對象。
    接下來來研究這個代碼,我們看到如下代碼:
    android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR);
    這一行的作用是什麼?我們看下queryLocalInterface的實現,這個方法在Binder中。
    
       
 /**
     * Use information supplied to attachInterface() to return the
     * associated IInterface if it matches the requested
     * descriptor.
     */
    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }


    從上面的代碼可以看到,其實他就是匹配下Binder對象中的descriptor是否和參數一致,如果一致,就說明這個Binder就是我們想要的,因爲descriptor一樣,就說明IIterface的類型也是一樣的,那麼實際上是一樣的嗎?
    我們知道服務端和客戶端的Stub的代碼是一樣的,那麼,照常理來說,服務端的對象傳遞到客戶端的話,那麼這個queryLocalInterface總是返回的不是null,但是實際上,不同進程調用這個方法,得到的就是null,除非是相同進程調用。那就說明一件事,客戶端這邊在onServiceConnected這個方法中,參數傳遞過來的對象不是服務端的對象。
    正常大家理解的Binder機制是這樣子的:
    

    這個其實並沒有什麼問題,只是它其實是服務端進程內部的Binder機制。
    而遠程客戶端的Binder機制其實是下面這樣的:

上面這個圖稍微凌亂了一點,其實意思就是,當你在服務端進程創建一個對象的時候,Binder驅動也會創建一個Binder對象,如果你是從遠程獲取服務端的Binder,則只會返回Binder驅動的Binder對象。而如果從服務端內部進程獲取Binder對象,那麼就會返回服務端的Binder對象,這也就是爲什麼queryLocalInterface這個方法,在不同進程調用返回的內容不一樣。因爲驅動內部的Binder對象,它的descriptor不是我們指定的那個descriptor。
    Binder驅動的Binder對象和服務端的Binder對象的關聯,是通過地址映射做到的,這裏就不詳細說了(使用的是內存映射技術,其實我也不是很懂,就不胡說八道了。),這裏就認爲,Binder驅動Binder和服務端的Binder對象可以通過內核的驅動層直接互相調用就可以了。
    那好,到這裏,我們就知道,服務端的Binder對象,我們可以通過Binder驅動的IBinder對象進行訪問,但是,Binder驅動的對象很明顯不是一個Stub對象,既然不是一個Stub對象,那麼我們怎麼調用Stub類裏面的方法呢?
    我們先繼續看asInterface這個方法,可以看到,在遠程調用的情況下,返回的代碼是這樣子的:
     return new com.zhenfei.aidl.IBookManager.Stub.Proxy(obj);
      也就是說,返回的是一個Proxy對象,我們看Proxy類的代碼,可以知道,Proxy是實現了IBookManager的接口方法,因此,在遠程客戶端,調用的getBookList()方法,其實是調用Proxy對象的getBookList方法,Proxy在構造函數中,已經保存了Binder驅動的IBinder對象,前面我們已經知道了這個對象是可以映射到服務端的Binder對象,但是它不是一個IBookManager接口的對象,那麼,Proxy是如何訪問服務端的Binder對象的IBookManager的接口方法呢?
    Proxy的getBookList代碼是這樣子的:
              
   <span style="white-space:pre">		</span>android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.zhenfei.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.zhenfei.aidl.Book.CREATOR);
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;



    下面來一行行地解釋,首先_data 和_reply 是從Parcel中申請的而不是客戶端這邊創建的,申請的Parcel可以讓我們讀取到不同進程的數據,接着往下看,
        _data.writeInterfaceToken(DESCRIPTOR); 
    這一行是用來標註遠程服務的名稱,這個不是必須要的,因爲我們調用的remote已經確保我們或得到了遠程的Binder引用。
    接下來往下看,看到了:
            mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
    Binder驅動的IBinder對象調用這個方法,是調用哪個transact方法呢?調用的是服務端的transact方法,前面我們說過了Binder驅動的IBinder對象它內存映射的是服務端的Binder的對象的地址,而方法也是在內存中有地址的,我們調用的就是服務端的Binder對象的方法了,那麼服務端的transact方法就是Stub方法了,那我們就往下閱讀服務端的transact代碼:
    
      
   @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.zhenfei.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook:
                {
                    data.enforceInterface(DESCRIPTOR);
                    com.zhenfei.aidl.Book _arg0;
                    if ((0!=data.readInt())) {
                        _arg0 = com.zhenfei.aidl.Book.CREATOR.createFromParcel(data);
                    }
                    else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

         data.enforceInterface 這個方法應該是驗證writInterfaceToken是否正確。(我不確定- - )
       
  <span style="white-space:pre">	</span> java.util.List<com.zhenfei.aidl.Book> _result = this.getBookList();
         reply.writeNoException();
         reply.writeTypedList(_result);


        這就是服務端獲取Book的列表,並且寫入Parcel中,然後寫入到reply包中,我們前面說過了,reply是Parcel數據,他可以在不同進程中傳遞數據,我們前面學過,知道Parcel序列化數據以後,在不同進程中傳遞,因此,在客戶端的IBinder對象,我們就可以從reply獲取到Book的列表了。(Parcel機制在這邊就不詳細討論,之後有空會繼續寫,Parcel機制設計的native層更多一點)0
        因此,總結Proxy#getBookList()
        這個方法運行在客戶端,當客戶端遠程調用此方法時,它的內部實現是這樣子的:首先創建該方法所需要的輸入型Parcel對象_data,輸出型Parcel對象_reply和返回值對象對象List;然後把該方法的參數信息寫入_data中(如果有參數的話),接着transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起,然後服務端的onTransact方法會被調用,直到RPC過程返回後,當前的線程繼續執行,並且從_reply中取出RPC過程的返回結果,最後返回_reply的數據。這裏面有個TRANSACTION_getBookList 和TRANSACTION_addBook 這幾個常量,作用很明顯,在此就不贅述。
        然後我們看,Proxy#addBook,
        這個方法調用的流程和getBookList差不多,就是一些細節不一樣,addBook是客戶端添加數據到服務端,所以調用parcel.writeString方法來寫入數據。這裏要注意的是,我們在aidl文件中,addBook( in Book book)是這樣子的,前面的in是代表參數的方向,不同的方向,如:in、out、inout生成的代碼是不一樣的。下面簡單介紹一下in、out、inout的區別,參考的文章是:http://hold-on.iteye.com/blog/2026138
    如果client不需要傳輸數據給server,client只需要處理經過server處理過後的數據,

    那麼 client 和 server 都爲 out 

    如果client只需要傳輸數據給server,而不需要處理返回的數據,

    那麼client和server都爲 in

    如果client需要傳輸數據給server,而且需要處理返回的數據,

    則client和server都爲 inout


    好了,在這邊Binder就先介紹到這裏了,如果之後有對C++層和內核層的Binder機制進行學習,我會繼續寫下來。

     下面是本文測試Demo的地址:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章