1. 程式人生 > >Android中的Binder詳解

Android中的Binder詳解

Binder簡介

由於Binder在Android的資訊傳輸中佔有比較重要的作用,所以把對Binder的分析單獨出一篇文章來記錄一下。

  1. 什麼是Binder

    Binder,翻譯為粘合劑,在Android程序間通訊相關的知識中經常出現。一般來說對Binder的解釋通常有以下幾種:

    • Binder是Android中的一個類,實現了IBinder介面。
    • Binder是Android獨有的一種跨程序通訊方式
    • Binder是一種虛擬的物理裝置,可以用來連通客戶端與服務端

    借用大神 Carson_Ho的一張圖來表示的話是下面這樣的:

    這裡寫圖片描述

    結合上圖,大家應該可以對Binder有了一個較為清晰的定義了。

  2. Binder的使用場景

    Binder主要用在Service,包括AIDL和Messenger,其中普通Service中的Binder不涉及程序間的通訊,所以較為簡單。Messenger的底層實現是AIDL,所以分析AIDL中的Binder就能夠幫助我們理解Binder的工作原理了。

Binder結構分析

  1. AIDL和Binder的關係

    寫完AIDL檔案之後,系統會在Build時生成一個繼承IInterface介面的java檔案。這個檔名和對應的AIDL檔名相同。在這個檔案中,有一個內部類Stub,這個類就是Binder。

    所以可以認為AIDL是為了幫助系統生成對應的Binder檔案。

  2. 生成Binder的AIDL檔案

    接下來我們寫一個AIDL檔案,AIDL的檔案內容在下面的程式碼區域,如果想了解AIDL的整個流程,可以參考這篇文章

    // 檔名是:Book.aidl
    package com.wscq.aidltest.aidl;
    
    //aidl中用到了實現了序列化的類Book,所以這裡需要申明一下
    parcelable Book;
    // 檔名是:IBookManager.aidl
    package com.wscq.aidltest.aidl;
    import com.wscq.aidltest.aidl.Book;
    import com.wscq.aidltest.aidl.IOnNewBookArrivedListener;
    
    interface IBookManager {
       List<Book> getBookList();
       void
    addBook(in Book book); }

    然後在java目錄下需要有一個實現了Parcelable介面的Book.java

    public class Book implements Parcelable {
       public int bookId;
       public String bookName;
    
       public Book(int bookId, String bookName) {
           this.bookId = bookId;
           this.bookName = bookName;
       }
    //省略get、set、Parcelable和toString方法
    }    

    上述檔案寫完以後,在AndroidStudio中會立即生成對應的Binder檔案,位置在工程目錄下/build/generated/source/aidl/debug/中。具體的路徑如圖所示:

    這裡寫圖片描述

    如果沒有可以build一下,報錯的話需要再檢查一遍aidl是否書寫正確或者放置的路徑是否正確。在AndroidStudio中,正確的目錄結構如下:

    這裡寫圖片描述

  3. Binder的總體結構

    在獲取到對應的Binder檔案後,我們先來看一下Binder的整體結構:

    public interface IBookManager extends IInterface {
    //宣告內部類Stub,這個就是一個Binder類。
       public static abstract class Stub extends Binder implements IBookManager {
        //Binder的唯一標識,一般用當前Binder的類名錶示
           private static final String DESCRIPTOR = "com.wscq.aidltest.aidl.IBookManager";
    
           //...
    
           //客戶端的代理類Proxy
           private static class Proxy implements IBookManager {
               //...
           }
        //這兩個整型的ID用於標識在跨程序呼叫中。客戶端到底呼叫的是哪個方法
           static final int TRANSACTION_getBookList = (FIRST_CALL_TRANSACTION + 0);
           static final int TRANSACTION_addBook = (FIRST_CALL_TRANSACTION + 1);
       }
    //宣告兩個方法,也就是IBookManager.aidl中的方法
       public List<Book> getBookList() throws RemoteException;
       public void addBook(Book book) throws RemoteException;
    }

    這個類繼承了IInterface介面,同時這個類也是一個介面。這個介面申明瞭兩個方法,也就是IBookManager.aidl中的方法。然後聲明瞭一個內部類Stub。這個Stub就是一個Binder類。在Stub內部還有個代理類Proxy,在跨程序通訊中個,它會是客戶端的代理方法。

  4. Binder內方法詳細分析

    1. 首先看看Stub中的各個方法

      除去構造方法以外,Stub中的方法還有asBinder()asInterface()onTransact方法。在這三個方法之外,還有上文提到過的,兩個靜態ID,用來標識客戶端呼叫的方法。

      其中asBinder()方法相當於一個get方法,用來返回當前的Binder物件,這個程式碼比較簡單,我們略過。

      接下來我們看一下onTransact()方法,這個方法執行在服務端,會通過code來分發具體要執行的方法。方法引數中的各個值代表的意義可以看下面註釋:

      /**
      * 執行在服務端中的Binder執行緒池中,當客戶端發起跨程序請求時,
      * 遠端請求會通過系統底層封裝後交由此方法處理
      *
      * @param code  通過此code可以確定客戶端所請求的目標方法是什麼
      * @param data  目標方法中所需要的引數
      * @param reply 目標方法執行完後,向reply中寫入返回值(若有)
      * @param flags 啟動方式,這裡並沒有使用
      * @return false 表示請求失敗, true表示請求成功
      * @throws android.os.RemoteException
      */
      @Override
      public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        //根據code來分發,執行對應的方法
        switch (code) {
              //...
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
      }

      然後我們看下asInterface()方法:

      public static com.wscq.aidltest.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.wscq.aidltest.aidl.IBookManager))) {
            return ((com.wscq.aidltest.aidl.IBookManager) iin);
        }
        return new com.wscq.aidltest.aidl.IBookManager.Stub.Proxy(obj);
      }

      這個方法會判斷當前服務端和客戶端是否處於同一程序中。如果處於同一個程序中,會返回同一個Stub物件本身,如果處於不同的程序會返回封裝後的客戶端代理類Stub.proxy。這個方法會在客戶端呼叫,用來獲取一個IBookManager物件。

    2. 然後看看客戶端代理Stub.Proxy中的方法

      這裡主要的方法就是addBookgetBookList方法,其實這兩個方法還是有部分相似之處的,這裡先分析getBookList方法

      public java.util.List<com.wscq.aidltest.aidl.Book> getBookList() throws android.os.RemoteException {
        //客戶端的引數合計,也就是Stub中onTransact的data引數
        android.os.Parcel _data = android.os.Parcel.obtain();
        //輸出型物件,也就是Stub中onTransaction的reply引數
        android.os.Parcel _reply = android.os.Parcel.obtain();
        //返回值物件
        java.util.List<com.wscq.aidltest.aidl.Book> _result;
        try {
            //寫入引數到_data中
            _data.writeInterfaceToken(DESCRIPTOR);
            //發起遠端請求,當前執行緒會暫時掛起
            mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
            //獲取服務端返回資訊
            _reply.readException();
            //從_reply中讀取資訊,構造_result
            _result = _reply.createTypedArrayList(com.wscq.aidltest.aidl.Book.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        //最後返回_result
        return _result;
      }

      概括的來說,當客戶端調動此方法時,會建立服務端的引數_data和接受返回的_reply以及返回值物件_result,然後把引數資訊寫入_data中。這裡開始呼叫transact方法發起遠端請求,同時當前執行緒掛起,當Stub的onTransact執行完後,再次回到當前方法,從_reply中取值,構造_result,然後返回_result

      addBook方法的過程和上述幾乎一致,大家可以在最後面的完整程式碼中對照檢視。

    3. 最後看IBookManager中的方法

      除去上面的兩個內部類以外,剩餘的程式碼就只有兩個抽象的介面了,也就是IBookManager.aidl中定義的介面方法。這兩方法沒啥說的,主要用來被Stub和Stub.Proxy來繼承或實現的。

  5. 上述Binder的完整程式碼

    這裡附上完整的Binder物件,方便大家進行一些整體的研究:

    public interface IBookManager extends android.os.IInterface {
       /**
        * Local-side IPC implementation stub class.
        */
       public static abstract class Stub extends android.os.Binder implements com.wscq.aidltest.aidl.IBookManager {
           //Binder的唯一標識,一般用當前列名錶示
           private static final java.lang.String DESCRIPTOR = "com.wscq.aidltest.aidl.IBookManager";
    
           /**
            * Construct the stub at attach it to the interface.
            */
           public Stub() {
               this.attachInterface(this, DESCRIPTOR);
           }
    
           /**
            * 用於將服務端的Binder物件轉換成客戶端所需的AIDL介面型別的物件,這個轉換過程是區分程序的,
            * 如果客戶單服務端處於同一程序,此方法返回服務端的Stub,否則返回的是系統封裝後的Stub.proxy物件
            *
            * @param obj 服務端的Binder物件
            * @return 客戶端所需的AIDL介面物件
            */
           public static com.wscq.aidltest.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.wscq.aidltest.aidl.IBookManager))) {
                   return ((com.wscq.aidltest.aidl.IBookManager) iin);
               }
               return new com.wscq.aidltest.aidl.IBookManager.Stub.Proxy(obj);
           }
    
           /**
            * 用於返回當前的Binder物件
            *
            * @return 當前binder物件
            */
           @Override
           public android.os.IBinder asBinder() {
               return this;
           }
    
           /**
            * 執行在服務端中的Binder執行緒池中,當客戶端發起跨程序請求時,
            * 遠端請求會通過系統底層封裝後交由此方法處理
            *
            * @param code  通過此code可以確定客戶端所請求的目標方法是什麼
            * @param data  包含目標方法所需要的引數
            * @param reply 目標方法執行完後,回想reply中寫入返回值(若有)
            * @param flags
            * @return false表示請求失敗, true表示請求成功
            * @throws android.os.RemoteException
            */
           @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.wscq.aidltest.aidl.Book> _result = this.getBookList();
                       reply.writeNoException();
                       reply.writeTypedList(_result);
                       return true;
                   }
                   case TRANSACTION_addBook: {
                       data.enforceInterface(DESCRIPTOR);
                       com.wscq.aidltest.aidl.Book _arg0;
                       if ((0 != data.readInt())) {
                           _arg0 = com.wscq.aidltest.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.wscq.aidltest.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;
               }
    
               /**
                * @return 返回_reply中的資料
                * @throws android.os.RemoteException
                */
               @Override
               public java.util.List<com.wscq.aidltest.aidl.Book> getBookList() throws android.os.RemoteException {
                   //客戶端的引數合計,也就是Stub中onTransact的data引數
                   android.os.Parcel _data = android.os.Parcel.obtain();
                   //輸出型物件,也就是Stub中onTransaction的reply引數
                   android.os.Parcel _reply = android.os.Parcel.obtain();
                   //返回值物件
                   java.util.List<com.wscq.aidltest.aidl.Book> _result;
                   try {
                       //寫入引數到_data中
                       _data.writeInterfaceToken(DESCRIPTOR);
                       //發起遠端請求,當前執行緒會暫時掛起
                       mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                       //獲取服務端返回資訊
                       _reply.readException();
                       //從_reply中讀取資訊
                       _result = _reply.createTypedArrayList(com.wscq.aidltest.aidl.Book.CREATOR);
                   } finally {
                       _reply.recycle();
                       _data.recycle();
                   }
                   return _result;
               }
    
               /**
                * 和上面的方法類似,不過由於沒有返回值,所以不需要從_reply中取出返回值
                *
                * @param book 要新增的書籍資訊
                * @throws android.os.RemoteException
                */
               @Override
               public void addBook(com.wscq.aidltest.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.wscq.aidltest.aidl.Book> getBookList() throws android.os.RemoteException;
    
       public void addBook(com.wscq.aidltest.aidl.Book book) throws android.os.RemoteException;
    }

相關文章: