- Android開發藝術探索
- 任玉剛
- 7029字
- 2019-01-10 17:37:37
2.3 IPC基礎概念介紹
本節主要介紹IPC中的一些基礎概念,主要包含三方面內容:Serializable接口、Parcelable接口以及Binder,只有熟悉這三方面的內容后,我們才能更好地理解跨進程通信的各種方式。Serializable和Parcelable接口可以完成對象的序列化過程,當我們需要通過Intent和Binder傳輸數據時就需要使用Parcelable或者Serializable。還有的時候我們需要把對象持久化到存儲設備上或者通過網絡傳輸給其他客戶端,這個時候也需要使用Serializable來完成對象的持久化,下面先介紹如何使用Serializable來完成對象的序列化。
2.3.1 Serializable接口
Serializable是Java所提供的一個序列化接口,它是一個空接口,為對象提供標準的序列化和反序列化操作。使用Serializable來實現序列化相當簡單,只需要在類的聲明中指定一個類似下面的標識即可自動實現默認的序列化過程。
private static final long serialVersionUID = 8711368828010083044L
在Android中也提供了新的序列化方式,那就是Parcelable接口,使用Parcelable來實現對象的序列號,其過程要稍微復雜一些,本節先介紹Serializable接口。上面提到,想讓一個對象實現序列化,只需要這個類實現Serializable接口并聲明一個serialVersionUID即可,實際上,甚至這個serialVersionUID也不是必需的,我們不聲明這個serialVersionUID同樣也可以實現序列化,但是這將會對反序列化過程產生影響,具體什么影響后面再介紹。User類就是一個實現了Serializable接口的類,它是可以被序列化和反序列化的,如下所示。
public class User implements Serializable { private static final long serialVersionUID = 519067123721295773L; public int userId; public String userName; public boolean isMale; ... }
通過Serializable方式來實現對象的序列化,實現起來非常簡單,幾乎所有工作都被系統自動完成了。如何進行對象的序列化和反序列化也非常簡單,只需要采用ObjectOutputStream和ObjectInputStream即可輕松實現。下面舉個簡單的例子。
//序列化過程 User user = new User(0, "jake", true); ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("cache.txt")); out.writeObject(user); out.close(); //反序列化過程 ObjectInputStream in = new ObjectInputStream( new FileInputStream("cache.txt")); User newUser = (User) in.readObject(); in.close();
上述代碼演示了采用Serializable方式序列化對象的典型過程,很簡單,只需要把實現了Serializable接口的User對象寫到文件中就可以快速恢復了,恢復后的對象newUser和user的內容完全一樣,但是兩者并不是同一個對象。
剛開始提到,即使不指定serialVersionUID也可以實現序列化,那到底要不要指定呢?如果指定的話,serialVersionUID后面那一長串數字又是什么含義呢?我們要明白,系統既然提供了這個serialVersionUID,那么它必須是有用的。這個serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數據中的serialVersionUID只有和當前類的serialVersionUID相同才能夠正常地被反序列化。serialVersionUID的詳細工作機制是這樣的:序列化的時候系統會把當前類的serialVersionUID寫入序列化的文件中(也可能是其他中介),當反序列化的時候系統會去檢測文件中的serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一致就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功反序列化;否則就說明當前類和序列化的類相比發生了某些變換,比如成員變量的數量、類型可能發生了改變,這個時候是無法正常反序列化的,因此會報如下錯誤:
java.io.InvalidClassException: Main; local class incompatible: stream classdesc serialVersionUID = 8711368828010083044, local class serial- VersionUID = 8711368828010083043。
一般來說,我們應該手動指定serialVersionUID的值,比如1L,也可以讓Eclipse根據當前類的結構自動去生成它的hash值,這樣序列化和反序列化時兩者的serialVersionUID是相同的,因此可以正常進行反序列化。如果不手動指定serialVersionUID的值,反序列化時當前類有所改變,比如增加或者刪除了某些成員變量,那么系統就會重新計算當前類的hash值并把它賦值給serialVersionUID,這個時候當前類的serialVersionUID就和序列化的數據中的serialVersionUID不一致,于是反序列化失敗,程序就會出現crash。所以,我們可以明顯感覺到serialVersionUID的作用,當我們手動指定了它以后,就可以在很大程度上避免反序列化過程的失敗。比如當版本升級后,我們可能刪除了某個成員變量也可能增加了一些新的成員變量,這個時候我們的反向序列化過程仍然能夠成功,程序仍然能夠最大限度地恢復數據,相反,如果不指定serialVersionUID的話,程序則會掛掉。當然我們還要考慮另外一種情況,如果類結構發生了非常規性改變,比如修改了類名,修改了成員變量的類型,這個時候盡管serialVersionUID驗證通過了,但是反序列化過程還是會失敗,因為類結構有了毀滅性的改變,根本無法從老版本的數據中還原出一個新的類結構的對象。
根據上面的分析,我們可以知道,給serialVersionUID指定為1L或者采用Eclipse根據當前類結構去生成的hash值,這兩者并沒有本質區別,效果完全一樣。以下兩點需要特別提一下,首先靜態成員變量屬于類不屬于對象,所以不會參與序列化過程;其次用transient關鍵字標記的成員變量不參與序列化過程。
另外,系統的默認序列化過程也是可以改變的,通過實現如下兩個方法即可重寫系統默認的序列化和反序列化過程,具體怎么去重寫這兩個方法就是很簡單的事了,這里就不再詳細介紹了,畢竟這不是本章的重點,而且大部分情況下我們不需要重寫這兩個方法。
private void writeObject(java.io.ObjectOutputStream out) throws IOException { // write 'this' to 'out'... } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { // populate the fields of 'this' from the data in 'in'... }
2.3.2 Parcelable接口
上一節我們介紹了通過Serializable方式來實現序列化的方法,本節接著介紹另一種序列化方式:Parcelable。Parcelable也是一個接口,只要實現這個接口,一個類的對象就可以實現序列化并可以通過Intent和Binder傳遞。下面的示例是一個典型的用法。
public class User implements Parcelable { public int userId; public String userName; public boolean isMale; public Book book; public User(int userId, String userName, boolean isMale) { this.userId = userId; this.userName = userName; this.isMale = isMale; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(userId); out.writeString(userName); out.writeInt(isMale ? 1 : 0); out.writeParcelable(book, 0); } public static final Parcelable.Creator<User> CREATOR = new Parcelable. Creator<User>() { public User createFromParcel(Parcel in) { return new User(in); } public User[] newArray(int size) { return new User[size]; } }; private User(Parcel in) { userId = in.readInt(); userName = in.readString(); isMale = in.readInt() == 1; book = in.readParcelable(Thread.currentThread().getContextClass- Loader()); } }
這里先說一下Parcel, Parcel內部包裝了可序列化的數據,可以在Binder中自由傳輸。從上述代碼中可以看出,在序列化過程中需要實現的功能有序列化、反序列化和內容描述。序列化功能由writeToParcel方法來完成,最終是通過Parcel中的一系列write方法來完成的;反序列化功能由CREATOR來完成,其內部標明了如何創建序列化對象和數組,并通過Parcel的一系列read方法來完成反序列化過程;內容描述功能由describeContents方法來完成,幾乎在所有情況下這個方法都應該返回0,僅當當前對象中存在文件描述符時,此方法返回1。需要注意的是,在User(Parcel in)方法中,由于book是另一個可序列化對象,所以它的反序列化過程需要傳遞當前線程的上下文類加載器,否則會報無法找到類的錯誤。詳細的方法說明請參看表2-1。
表2-1 Parcelable的方法說明

系統已經為我們提供了許多實現了Parcelable接口的類,它們都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同時List和Map也可以序列化,前提是它們里面的每個元素都是可序列化的。
既然Parcelable和Serializable都能實現序列化并且都可用于Intent間的數據傳遞,那么二者該如何選取呢?Serializable是Java中的序列化接口,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更適合用在Android平臺上,它的缺點就是使用起來稍微麻煩點,但是它的效率很高,這是Android推薦的序列化方式,因此我們要首選Parcelable。Parcelable主要用在內存序列化上,通過Parcelable將對象序列化到存儲設備中或者將對象序列化后通過網絡傳輸也都是可以的,但是這個過程會稍顯復雜,因此在這兩種情況下建議大家使用Serializable。以上就是Parcelable和Serializable和區別。
2.3.3 Binder
Binder是一個很深入的話題,筆者也看過一些別人寫的Binder相關的文章,發現很少有人能把它介紹清楚,不是深入代碼細節不能自拔,就是長篇大論不知所云,看完后都是暈暈的感覺。所以,本節筆者不打算深入探討Binder的底層細節,因為Binder太復雜了。本節的側重點是介紹Binder的使用以及上層原理,為接下來的幾節內容做鋪墊。
直觀來說,Binder是Android中的一個類,它繼承了IBinder接口。從IPC角度來說,Binder是Android中的一種跨進程通信方式,Binder還可以理解為一種虛擬的物理設備,它的設備驅動是/dev/binder,該通信方式在Linux中沒有;從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager,等等)和相應ManagerService的橋梁;從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這里的服務包括普通服務和基于AIDL的服務。
Android開發中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及進程間通信,所以較為簡單,無法觸及Binder的核心,而Messenger的底層其實是AIDL,所以這里選擇用AIDL來分析Binder的工作機制。為了分析Binder的工作機制,我們需要新建一個AIDL示例,SDK會自動為我們生產AIDL所對應的Binder類,然后我們就可以分析Binder的工作過程。還是采用本章開始時用的例子,新建Java包com.ryg.chapter_2.aidl,然后新建三個文件Book.java、Book.aidl和IBookManager.aidl,代碼如下所示。
//Book.java package com.ryg.chapter_2.aidl; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { public int bookId; public String bookName; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(bookId); out.writeString(bookName); } public static final Parcelable.Creator<Book> CREATOR = new Parcelable. Creator<Book>() { public Book createFromParcel(Parcel in) { return new Book(in); } public Book[] newArray(int size) { return new Book[size]; } }; private Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } } //Book.aidl package com.ryg.chapter_2.aidl; parcelable Book; // IBookManager.aidl package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
上面三個文件中,Book.java是一個表示圖書信息的類,它實現了Parcelable接口。Book.aidl是Book類在AIDL中的聲明。IBookManager.aidl是我們定義的一個接口,里面有兩個方法:getBookList和addBook,其中getBookList用于從遠程服務端獲取圖書列表,而addBook用于往圖書列表中添加一本書,當然這兩個方法主要是示例用,不一定要有實際意義。我們可以看到,盡管Book類已經和IBookManager位于相同的包中,但是在IBookManager中仍然要導入Book類,這就是AIDL的特殊之處。下面我們先看一下系統為IBookManager.aidl生產的Binder類,在gen目錄下的com.ryg.chapter_2.aidl包中有一個IBookManager.java的類,這就是我們要找的類。接下來我們需要根據這個系統生成的Binder類來分析Binder的工作原理,代碼如下:
//IBookManager.java /* * This file is auto-generated. DO NOT MODIFY. * Original file: E:\\workspace\\Chapter_2\\src\\com\\ryg\\chapter_2\\ aidl\\IBookManager.aidl */ package com.ryg.chapter_2.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.ryg.chapter_2.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_ 2.aidl.IBookManager"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager * interface, generating a proxy if needed. */ public static com.ryg.chapter_2.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.ryg.chapter_2.aidl. IBookManager))) { return ((com.ryg.chapter_2.aidl.IBookManager) iin); } return new com.ryg.chapter_2.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.Remote- Exception { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.ryg.chapter_2.aidl.Book> _result = this. getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.ryg.chapter_2.aidl.Book _arg0; if ((0 ! = data.readInt())) { _arg0 = com.ryg.chapter_2.aidl.Book.CREATOR.create- FromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.ryg.chapter_2.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.ryg.chapter_2.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.ryg.chapter_2.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply .createTypedArrayList(com.ryg.chapter_2.aidl. Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(com.ryg.chapter_2.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.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException; public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException; }
上述代碼是系統生成的,為了方便查看筆者稍微做了一下格式上的調整。在gen目錄下,可以看到根據IBookManager.aidl系統為我們生成了IBookManager.java這個類,它繼承了IInterface這個接口,同時它自己也還是個接口,所有可以在Binder中傳輸的接口都需要繼承IInterface接口。這個類剛開始看起來邏輯混亂,但是實際上還是很清晰的,通過它我們可以清楚地了解到Binder的工作機制。這個類的結構其實很簡單,首先,它聲明了兩個方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中所聲明的方法,同時它還聲明了兩個整型的id分別用于標識這兩個方法,這兩個id用于標識在transact過程中客戶端所請求的到底是哪個方法。接著,它聲明了一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端都位于同一個進程時,方法調用不會走跨進程的transact過程,而當兩者位于不同進程時,方法調用需要走transact過程,這個邏輯由Stub的內部代理類Proxy來完成。這么來看,IBookManager這個接口的確很簡單,但是我們也應該認識到,這個接口的核心實現就是它的內部類Stub和Stub的內部代理類Proxy,下面詳細介紹針對這兩個類的每個方法的含義。
DESCRIPTOR
Binder的唯一標識,一般用當前Binder的類名表示,比如本例中的“com.ryg. chapter_2.aidl.IBookManager”。
asInterface(android.os.IBinder obj)
用于將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,這種轉換過程是區分進程的,如果客戶端和服務端位于同一進程,那么此方法返回的就是服務端的Stub對象本身,否則返回的是系統封裝后的Stub.proxy對象。
asBinder
此方法用于返回當前Binder對象。
onTransact
這個方法運行在服務端中的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交由此方法來處理。該方法的原型為public Boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服務端通過code可以確定客戶端所請求的目標方法是什么,接著從data中取出目標方法所需的參數(如果目標方法有參數的話),然后執行目標方法。當目標方法執行完畢后,就向reply中寫入返回值(如果目標方法有返回值的話), onTransact方法的執行過程就是這樣的。需要注意的是,如果此方法返回false,那么客戶端的請求會失敗,因此我們可以利用這個特性來做權限驗證,畢竟我們也不希望隨便一個進程都能遠程調用我們的服務。
Proxy#getBookList
這個方法運行在客戶端,當客戶端遠程調用此方法時,它的內部實現是這樣的:首先創建該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List;然后把該方法的參數信息寫入_data中(如果有參數的話);接著調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;然后服務端的onTransact方法會被調用,直到RPC過程返回后,當前線程繼續執行,并從_reply中取出RPC過程的返回結果;最后返回_reply中的數據。
Proxy#addBook
這個方法運行在客戶端,它的執行過程和getBookList是一樣的,addBook沒有返回值,所以它不需要從_reply中取出返回值。
通過上面的分析,讀者應該已經了解了Binder的工作機制,但是有兩點還是需要額外說明一下:首先,當客戶端發起遠程請求時,由于當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是很耗時的,那么不能在UI線程中發起此遠程請求;其次,由于服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該采用同步的方式去實現,因為它已經運行在一個線程中了。為了更好地說明Binder,下面給出一個Binder的工作機制圖,如圖2-5所示。

圖2-5 Binder的工作機制
從上述分析過程來看,我們完全可以不提供AIDL文件即可實現Binder,之所以提供AIDL文件,是為了方便系統為我們生成代碼。系統根據AIDL文件生成Java文件的格式是固定的,我們可以拋開AIDL文件直接寫一個Binder出來,接下來我們就介紹如何手動寫一個Binder。還是上面的例子,但是這次我們不提供AIDL文件。參考上面系統自動生成的IBookManager.java這個類的代碼,可以發現這個類是相當有規律的,根據它的特點,我們完全可以自己寫一個和它一模一樣的類出來,然后這個不借助AIDL文件的Binder就完成了。但是我們發現系統生成的類看起來結構不清晰,我們想試著對它進行結構上的調整,可以發現這個類主要由兩部分組成,首先它本身是一個Binder的接口(繼承了IInterface),其次它的內部由個Stub類,這個類就是個Binder。還記得我們怎么寫一個Binder的服務端嗎?代碼如下所示。
private final IBookManager.Stub mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { synchronized (mBookList) { return mBookList; } } @Override public void addBook(Book book) throws RemoteException { synchronized (mBookList) { if (! mBookList.contains(book)) { mBookList.add(book); } } } }
首先我們會實現一個創建了一個Stub對象并在內部實現IBookManager的接口方法,然后在Service的onBind中返回這個Stub對象。因此,從這一點來看,我們完全可以把Stub類提取出來直接作為一個獨立的Binder類來實現,這樣IBookManager中就只剩接口本身了,通過這種分離的方式可以讓它的結構變得清晰點。
根據上面的思想,手動實現一個Binder可以通過如下步驟來完成:
(1)聲明一個AIDL性質的接口,只需要繼承IInterface接口即可,IInterface接口中只有一個asBinder方法。這個接口的實現如下:
public interface IBookManager extends IInterface { static final String DESCRIPTOR = "com.ryg.chapter_2.manualbinder. IBookManager"; static final int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSA- CTION + 0; static final int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1; public List<Book> getBookList() throws RemoteException; public void addBook(Book book) throws RemoteException; }
可以看到,在接口中聲明了一個Binder描述符和另外兩個id,這兩個id分別表示的是getBookList和addBook方法,這段代碼原本也是系統生成的,我們仿照系統生成的規則去手動書寫這部分代碼。如果我們有三個方法,應該怎么做呢?很顯然,我們要再聲明一個id,然后按照固定模式聲明這個新方法即可,這個比較好理解,不再多說。
(2)實現Stub類和Stub類中的Proxy代理類,這段代碼我們可以自己寫,但是寫出來后會發現和系統自動生成的代碼是一樣的,因此這個Stub類我們只需要參考系統生成的代碼即可,只是結構上需要做一下調整,調整后的代碼如下所示。
public class BookManagerImpl extends Binder implements IBookManager { /** Construct the stub at attach it to the interface. */ public BookManagerImpl() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an IBookManager interface, generating a proxy * if needed. */ public static IBookManager asInterface(IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin ! = null) && (iin instanceof IBookManager))) { return ((IBookManager) iin); } return new BookManagerImpl.Proxy(obj); } @Override public IBinder asBinder() { return this; } @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: { 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); } @Override public List<Book> getBookList() throws RemoteException { // TODO待實現 return null; } @Override public void addBook(Book book) throws RemoteException { // TODO待實現 } private static class Proxy implements IBookManager { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } @Override public IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public List<Book> getBookList() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); List<Book> result; try { data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(TRANSACTION_getBookList, data, reply, 0); reply.readException(); result = reply.createTypedArrayList(Book.CREATOR); } finally { reply.recycle(); data.recycle(); } return result; } @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(TRANSACTION_addBook, data, reply, 0); reply.readException(); } finally { reply.recycle(); data.recycle(); } } } }
通過將上述代碼和系統生成的代碼對比,可以發現簡直是一模一樣的。也許有人會問:既然和系統生成的一模一樣,那我們為什么要手動去寫呢?我們在實際開發中完全可以通過AIDL文件讓系統去自動生成,手動去寫的意義在于可以讓我們更加理解Binder的工作原理,同時也提供了一種不通過AIDL文件來實現Binder的新方式。也就是說,AIDL文件并不是實現Binder的必需品。如果是我們手寫的Binder,那么在服務端只需要創建一個BookManagerImpl的對象并在Service的onBind方法中返回即可。最后,是否手動實現Binder沒有本質區別,二者的工作原理完全一樣,AIDL文件的本質是系統為我們提供了一種快速實現Binder的工具,僅此而已。
接下來,我們介紹Binder的兩個很重要的方法linkToDeath和unlinkToDeath。我們知道,Binder運行在服務端進程,如果服務端進程由于某種原因異常終止,這個時候我們到服務端的Binder連接斷裂(稱之為Binder死亡),會導致我們的遠程調用失敗。更為關鍵的是,如果我們不知道Binder連接已經斷裂,那么客戶端的功能就會受到影響。為了解決這個問題,Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候我們就可以重新發起連接請求從而恢復連接。那么到底如何給Binder設置死亡代理呢?也很簡單。
首先,聲明一個DeathRecipient對象。DeathRecipient是一個接口,其內部只有一個方法binderDied,我們需要實現這個方法,當Binder死亡的時候,系統就會回調binderDied方法,然后我們就可以移出之前綁定的binder代理并重新綁定遠程服務:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.Death- Recipient() { @Override public void binderDied() { if (mBookManager == null) return; mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mBookManager = null; // TODO:這里重新綁定遠程Service } };
其次,在客戶端綁定遠程服務成功后,給binder設置死亡代理:
mService = IMessageBoxManager.Stub.asInterface(binder); binder.linkToDeath(mDeathRecipient, 0);
其中linkToDeath的第二個參數是個標記位,我們直接設為0即可。經過上面兩個步驟,就給我們的Binder設置了死亡代理,當Binder死亡的時候我們就可以收到通知了。另外,通過Binder的方法isBinderAlive也可以判斷Binder是否死亡。
到這里,IPC的基礎知識就介紹完畢了,下面開始進入正題,直面形形色色的進程間通信方式。