Android中的Binder詳解
Binder簡介
由於Binder在Android的資訊傳輸中佔有比較重要的作用,所以把對Binder的分析單獨出一篇文章來記錄一下。
什麼是Binder
Binder,翻譯為粘合劑,在Android程序間通訊相關的知識中經常出現。一般來說對Binder的解釋通常有以下幾種:
- Binder是Android中的一個類,實現了IBinder介面。
- Binder是Android獨有的一種跨程序通訊方式
- Binder是一種虛擬的物理裝置,可以用來連通客戶端與服務端
借用大神 Carson_Ho的一張圖來表示的話是下面這樣的:
結合上圖,大家應該可以對Binder有了一個較為清晰的定義了。
Binder的使用場景
Binder主要用在Service,包括AIDL和Messenger,其中普通Service中的Binder不涉及程序間的通訊,所以較為簡單。Messenger的底層實現是AIDL,所以分析AIDL中的Binder就能夠幫助我們理解Binder的工作原理了。
Binder結構分析
AIDL和Binder的關係
寫完AIDL檔案之後,系統會在Build時生成一個繼承IInterface介面的java檔案。這個檔名和對應的AIDL檔名相同。在這個檔案中,有一個內部類Stub,這個類就是Binder。
所以可以認為AIDL是為了幫助系統生成對應的Binder檔案。
生成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
然後在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中,正確的目錄結構如下: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,在跨程序通訊中個,它會是客戶端的代理方法。Binder內方法詳細分析
首先看看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
物件。然後看看客戶端代理Stub.Proxy中的方法
這裡主要的方法就是
addBook
和getBookList
方法,其實這兩個方法還是有部分相似之處的,這裡先分析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
方法的過程和上述幾乎一致,大家可以在最後面的完整程式碼中對照檢視。最後看IBookManager中的方法
除去上面的兩個內部類以外,剩餘的程式碼就只有兩個抽象的介面了,也就是IBookManager.aidl中定義的介面方法。這兩方法沒啥說的,主要用來被Stub和Stub.Proxy來繼承或實現的。
上述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; }
相關文章: