一、IPC簡介
(1)IPC是Inter-Process Communication的縮寫,含義爲進程間通信或者跨進程通信,是指兩個進程之間進行數據交換的過程。
(2)ANR是Application Not Responding的縮寫,即應用無響應。主線程執行大量的耗時操作容易導致ANR現象發生。
(3)在Android中最有特色的進程間通信方式就是Binder了,通過Binder可以輕鬆地實現進程間通信。
(4)Android還支持Socket,通過Socket也可以實現任意兩個終端或者兩個進程之間的通信。
二、Android中的多進程模式
1、在Android中使用多進程只有一種方法:
就是給四大組件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process屬性。
可以在Eclipse的DDMS視圖中查看進程信息,還可以用shell來查看,命令爲:adb shell ps 或者 adb shell ps|grep com.ryg.chapter_2。
- <activity
- android:name=".MainActivity"
- android:configChanges="orientation|screenSize"
- android:label="@string/app_name"
- android:launchMode="standard" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
- <activity
- android:name=".SecondActivity"
- android:configChanges="screenLayout"
- android:label="@string/app_name"
- android:process=":remote" />
- <activity
- android:name=".ThirdActivity"
- android:configChanges="screenLayout"
- android:label="@string/app_name"
- android:process="com.ryg.chapter_2.remote" />
上面的代碼中,
(1)MainActivity沒有指定process屬性,所以它運行在默認的進程中,默認進程的進程名是包名。
(2)SecondActivity會運行在一個單獨的進程中,進程名爲「com.ryg.chapter_2:remote」,其中com.ryg.chapter_2是包名。在程序中的冒號「:」的含義是指要在當前的進程名前面附加上當前的包名,是一種簡寫的方法。而且以「:」開頭的進程屬於當前應用的私有進程,其他應用的組件不可以和它跑在同一個進程中。
(3)ThirdActivity會運行在另一個單獨的進程中,進程名爲「com.ryg.chapter_2.remote」。這是一種完整的命名方式。屬於全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。
注意點一:Android系統會爲每一個應用分配一個唯一的UID,具有相同UID的應用才能共享數據。要求兩個應用具有相同的ShareUID並且簽名相同纔可以跑在同一個進程中。在這種情況下,它們可以互相訪問對方的私有數據,比如data目錄、組件信息等,不管它們是否跑在同一個進程中。當然如果它們跑在同一個進程中,那麼除了能共享data目錄、組件信息,還可以共享內存數據,或者說它們看起來就像是一個應用的兩個部分。
2、多進程模式的運行機制
(1)多進程會帶來很多意想不到的麻煩,因爲Android爲每一個應用都分配了一個獨立的虛擬機,或者說爲每個進程都分配了一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致在不同的虛擬機中訪問同一個類的對象會產生多份副本。這樣很就容易導致數據不同步。
(2)所有運行在不同進程的四大組件,只要它們之間需要通過內存在共享數據,都會共享失敗。
(3)主要有以下四方面的問題:
1)靜態成員和單例模式完全失效。(由獨立虛擬機造成)
2)線程同步機制完全失效。(同上)
3)SharedPreferences的可靠性下降。(存在併發讀寫的問題)
4)Application會多次創建。(新的進程中又會導致進程所在的Application在新的虛擬機中再次創建)
(4)運行在同一個進程中的組件是屬於同一個虛擬機和同一個Application的,同理運行在不同進程的組件是屬於兩個不同的虛擬機和Application的。
基於上面的這些問題,因爲我們需要學習進程間通信機制!!!!!
三、IPC基礎概念介紹
當我們需要通過Intent和Binder傳輸數據時就需要使用Parcelable或者Serializeble。Serializable和Parcelable接口可以完成對象的序列化過程。還有時候我們需要把對象持久化到存儲 設備上或者通過網絡傳輸給其他客戶端,這個時候也需要Serializable來完成對象的持久化。
1、Serializable接口
(1)Serializable是Java所提供的一個序列化接口,它是一個空接口,爲對象提供標準的序列化和反序列化操作。使用Serializable來實現序列化非常簡單,只需要在類的聲明中指定一個類似下面的標識即可
自動實現默認的序列化過程。
- private static final long serialVersionUID = 8723148825838841922L;
- public class User implements Serializable{
- private static final long serialVersionUID = 8723148825838841922L;
-
- public int userId;
- public String userName;
- public boolean isMale;
- }
(2)只需要採用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();
注意點一:序列化和反序列化的對象並不是同一個對象!
(3)一般來說,我們應該收到指定serialVersionUID的值,比如1L,也可以讓Eclipse根據當前類的結構自動去生成它的hash值。當我們手動指定了它以後,就可以在很大程度上避免反序列化過程的失敗。
注意點二:靜態成員變量屬於類不屬於對象,所以不會參與序列化過程。
注意點三:用transient關鍵字標記的成員變量不參與序列化過程。
2、Parcelable接口
(1)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;
- }
-
- /*
- * 內容描述功能幾乎都是直接返回0的。
- * */
- public int describeContents() {
- return 0;
- }
-
- /*
- * 序列化由writeToParcel方法來完成,最終是通過Parcel中一系列write方法來完成的。
- * 其中flags標識有兩種值:0和1(PARCELABLE_WRITE_RETURN_VALUE)。
- * 爲1時標識當前對象需要作爲返回值返回,不能立即釋放資源,
- * 幾乎所有情況都爲0。
- * */
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(userId);
- out.writeString(userName);
- out.writeInt(isMale? 1:0);
- out.writeParcelable(book, 0);
- }
-
- /*
- * 反序列化功能是由CREATOR來完成,其內部標明瞭如何創建序列化對象和數組,
- * 並通過Parcel的一些了read方法來完成反序列化過程。
- * */
- 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];
- }
- };
-
- /*
- * Parcel內部包裝了可序列化的數據,可以在Binder中自由傳輸。
- * 從序列化後的對象中創建原始對象。
- * */
- private User(Parcel in) {
- userId = in.readInt();
- userName = in.readString();
- isMale = in.readInt() == 1;
- /*
- * 由於book是另一個可序列化對象,所以它的反序列化過程需要傳遞當前線程的上下文類加載器,
- * 否則會報無法找到類的錯誤。
- * */
- book = in.readParcelable(Thread.currentThread().getContextClassLoader());
- }
- }
(2)系統已經爲我們提供了許多實現了Parcelable接口的類,它們都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同時List和Map也可以序列化,前提是它們裏面的每個元素都是可序列化的。
3、Parcelable接口和Serializable接口的比較
(1)Serializable用起來簡單,但開銷很大,序列化和反序列化過程都需要大量的I/O操作。
(2)Parcelable是Android中的序列化方式,更適合在Android平臺上使用,用起來比較麻煩,效率很高,首選。主要用在內存序列化上。
四、Binder
1、Binder簡介
(1)Binder實現了IBinder接口。
(2)從IPC角度來說,Binder是Android中的一種跨進程通信方式。Binder還可以理解爲一種虛擬的物理設備,它的設備驅動是/dev/binder,這種通信方式在Linux中沒有。
(3)從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager,等等)和相應ManagerService的橋樑。
(4)從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
(5)AIDL即Android interface definition Language,即Android接口定義語言。
2、在分析Binder的工作原理之前,我們先補充一下Android設計模式之Proxy模式
(1)Proxy代理模式簡介
代理模式是對象的結構模式。代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。
模式的使用場景:就是一個人或者機構代表另一個人或者機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
抽象對象角色AbstarctObject:聲明瞭目標對象和代理對象的共同接口,這樣一來在任何可以使用目標對象的地方都可以使用代理對象。
目標對象角色RealObject:定義了代理對象所代表的目標對象。
代理對象角色ProxyObject:代理對象內部含有目標對象的引用,從而可以在任何時候操作目標對象;代理對象提供一個與目標對象相同的接口,以便可以在任何時候替代目標對象。代理對象通常在客戶端調用傳遞給目標對象之前或之後,執行某個操作,而不是單純地將調用傳遞給目標對象。
(2)Proxy代理模式的簡單實現
抽象對象角色:
- public abstract class AbstractObject {
- //操作
- public abstract void operation();
- }
目標對象角色:
- public class RealObject extends AbstractObject {
- @Override
- public void operation() {
- //一些操作
- System.out.println("一些操作");
- }
- }
代理對象角色:
- public class ProxyObject extends AbstractObject{
- RealObject realObject = new RealObject();//目標對象角色
- @Override
- public void operation() {
- //調用目標對象之前可以做相關操作
- System.out.println("before");
- realObject.operation(); //目標對象角色的操作函數
- //調用目標對象之後可以做相關操作
- System.out.println("after");
- }
- }
客戶端:
- public class Client {
- public static void main(String[] args) {
- AbstractObject obj = new ProxyObject();
- obj.operation();
- }
- }
(3)代理模式在Binder中的使用
直觀來說,Binder是Android中的一個類,它繼承了IBinder接口。從IPC角度來說,Binder是Android中的一種跨進程通信方式,Binder還可以理解爲一種虛擬的物理設備,它的設備驅動是/dev/binder,該通信方式在linux中沒有;從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager,etc)和相應ManagerService的橋樑;從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當你bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
Binder一個很重要的作用是:將客戶端的請求參數通過Parcel包裝後傳到遠程服務端,遠程服務端解析數據並執行對應的操作,同時客戶端線程掛起,當服務端方法執行完畢後,再將返回結果寫入到另外一個Parcel中並將其通過Binder傳回到客戶端,客戶端接收到返回數據的Parcel後,Binder會解析數據包中的內容並將原始結果返回給客戶端,至此,整個Binder的工作過程就完成了。由此可見,Binder更像一個數據通道,Parcel對象就在這個通道中跨進程傳輸,至於雙方如何通信,這並不負責,只需要雙方按照約定好的規範去打包和解包數據即可。
爲了更好地說明Binder,這裏我們先手動實現了一個Binder。爲了使得邏輯更清晰,這裏簡化一下,我們來模擬一個銀行系統,這個銀行提供的功能只有一個:即查詢餘額,只有傳遞一個int的id過來,銀行就會將你的餘額設置爲id*10,滿足下大家的發財夢。
1)先定義一個Binder接口(抽象對象角色):
- package com.ryg.design.manualbinder;
-
-
- import android.os.IBinder;
- import android.os.IInterface;
- import android.os.RemoteException;
-
-
- public interface IBank extends IInterface {
-
-
- /*
- * Binder的唯一標識符:
- * */
- static final String DESCRIPTOR = "com.ryg.design.manualbinder.IBank";
-
-
- /*
- * queryMoney方法的code標識:
- * */
- static final int TRANSACTION_queryMoney = (IBinder.FIRST_CALL_TRANSACTION + 0);
-
-
- /*
- * queryMoney方法聲明:
- * */
- public long queryMoney(int uid) throws RemoteException;
-
-
- }
2)創建一個Binder並實現上述接口:
- package com.ryg.design.manualbinder;
-
-
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.Parcel;
- import android.os.RemoteException;
-
-
- public class BankImpl extends Binder implements IBank {
-
-
- public BankImpl() {
- this.attachInterface(this, DESCRIPTOR);
- }
-
-
- /*
- * 如果在同一進程,則返回目標對象本身,
- * 如果在不同僅此,則返回代理類
- * */
- public static IBank asInterface(IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof IBank))) {
- return ((IBank) iin);
- }
- return new BankImpl.Proxy(obj);
- }
-
-
- @Override
- public IBinder asBinder() {
- return this;
- }
-
-
- /*
- * 這個onTransact方法是在目標對象角色中重寫的,
- * 在目標對象角色調用Transact方法時回調的!
- * */
- @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_queryMoney: {
- data.enforceInterface(DESCRIPTOR);
- int uid = data.readInt();
- long result = this.queryMoney(uid);
- reply.writeNoException();
- reply.writeLong(result);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
-
- /*
- * 這是正兒八經的目標對象角色的queryMoney函數:
- * */
- @Override
- public long queryMoney(int uid) throws RemoteException {
- return uid * 10l;
- }
-
-
- /*
- * 內部代理類(代理對象角色)
- * */
- private static class Proxy implements IBank {
- /*
- * 代表目標對象角色:
- * */
- private IBinder mRemote;
-
-
- /*
- * 構造函數:
- * */
- Proxy(IBinder remote) {
- >// 接收目標對象角色:
- mRemote = remote;
- }
-
-
- /*
- * 返回目標對象角色:
- * */
- @Override
- public IBinder asBinder() {
- return mRemote;
- }
-
-
- /*
- * 返回Binder唯一標識符:
- * */
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
-
-
- /*
- * 這是代理類Proxy中的queryMoney方法:
- * */
- @Override
- public long queryMoney(int uid) throws RemoteException {
- /*
- * 先創建兩個Parcel對象
- * */
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- long result;
- try {
- *
- * 在操作前向data中寫入一些數據:
- * */
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt(uid);
- /*
- * 這裏執行的mRemote.transact其實是目標對象角色的transact函數。
- * 因爲mRemote是IBinder對象,所以調用它的transact函數會回調它的onTransact方法,
- * 這個onTransact方法是在mRemote這個目標對象角色中重寫了的,哈
- * 然後要根據TRANSACTION_queryMoney的code代碼來執行相應的函數。
- * data負責傳遞信息,
- * reply負責回收信息。
- * */
- mRemote.transact(TRANSACTION_queryMoney, data, reply, 0);
- /*
- * 這裏是返回的數據。
- * */
- reply.readException();
- result = reply.readLong();
- } finally {
- reply.recycle();
- data.recycle();
- }
- return result;
- }
- }
- }
ok,到此爲止,我們的Binder就完成了,這裏只要創建服務端和客戶端,二者就能通過我們的Binder來通信了。這裏就不做這個示例了,我們的目的是分析代理模式在Binder中的使用。
我們看上述Binder的實現中,有一個叫做「Proxy」的類,它的構造方法如下:
- Proxy(IBinder remote) {
- mRemote = remote;
- }
Proxy類接收一個IBinder參數,這個參數實際上就是服務端Service中的onBind方法返回的Binder對象在客戶端重新打包後的結果,因爲客戶端無法直接通過這個打包的Binder和服務端通信,因此客戶端必須藉助Proxy類來和服務端通信,這裏Proxy的作用就是代理的作用,客戶端所有的請求全部通過Proxy來代理,具體工作流程爲:Proxy接收到客戶端的請求後,會將客戶端的請求參數打包到Parcel對象中,然後將Parcel對象通過它內部持有的Ibinder對象傳送到服務端,服務端接收數據、執行方法後返回結果給客戶端的Proxy,Proxy解析數據後返回給客戶端的真正調用者。很顯然,上述所分析的就是典型的代理模式。至於Binder如何傳輸數據,這涉及到很底層的知識,這個很難搞懂,但是數據傳輸的核心思想是共享內存。
3、我們通過一個案例來分析Binder工作原理
我們需要新建一個AIDL示例,SDK會自動爲我們生產AIDL所對應的Binder類。
(1)Book.java:這裏面沒有什麼特殊之處,爲了實現Parcelable,添加了幾個方法,上面在Parcelable部分已經介紹過了。
- package com.ryg.chapter_2.aidl;
-
-
- import android.os.Parcel;
- import android.os.Parcelable;
-
-
- /*
- * (1)它是一個表示圖示信息的類,
- * 它實現了Parcelable接口,因爲實現了Parcelable接口便可以進行序列化
- * (2)Book.aidl是Book類在ADIL中的聲明。
- * (3)IBookManager.aidl是我們定義的一個接口,裏面有兩個方法:getBookList和addBook,
- * 其中getBookList用於從遠程服務端獲取圖書列表,而addBook用於往圖書列表中添加一本書,
- * 當然這兩個方法主要是示例用,不一定要有實際意義。
- * (4)儘管Book類和IBookManager位於相同的包中,但是在IBookManager中仍然要導入Book類,
- * 這就是AIDL的特殊之處。
- * */
- public class Book implements Parcelable {
- <span style="white-space:pre"> </span>public int bookId;
- public String bookName;
-
- /*
- * 普通構造函數:
- * */
- public Book() {
- <span style="white-space:pre"> </span>
- }
-
- /*
- * 普通構造函數:
- * */
- 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);
- }
-
- /*
- * 反序列化,
- * 這個creator就是通過一個Parcle來創建一個book對象或者數組。
- * */
- 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();
- }
-
-
-
-
- @Override
- public String toString() {
- return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
- }
- }
(2)Book.aidl:它是Book在AIDL中的聲明。
- package com.ryg.chapter_2.aidl;
-
- parcelable Book;
(3)IBookManager.aidl:雖然Book類已經和IBookManager位於相同的包中,但是這裏依然需要導入Book類。這是AIDL的特殊之處。
它是一個接口,裏面有四個方法。
- package com.ryg.chapter_2.aidl;
-
- import com.ryg.chapter_2.aidl.Book;
- import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
-
- interface IBookManager {
- List<Book> getBookList();
- void addBook(in Book book);
- void registerListener(IOnNewBookArrivedListener listener);
- void unregisterListener(IOnNewBookArrivedListener listener);
- }
(4)下面我們要看一下系統爲IBookManager.aidl生產的Binder類,在gen目錄下有一個IBookManager.java的類,這就是我們要找的類。
- /*
- * This file is auto-generated. DO NOT MODIFY.
- */
- package com.ryg.chapter_2.aidl;
- /*
- * IBookManager它繼承了IInterface這個接口,同時它自己也還是個接口,
- * 所有可以在Binder中傳輸的接口都要繼承IInterface接口。
- * 首先,它聲明瞭兩個方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中所聲明的方法,
- * 同時它還聲明瞭兩個整型的id分別用於標識這兩個方法。
- * 接着,它聲明瞭一個內部類Stub,這個Stub就是一個Binder類,
- * 當客戶端和服務端都位於同一個進程時,方法調用不會走跨進程的transact過程,
- * 而當兩者位於不同進程時,方法調用需要走transact過程,
- * 這個邏輯由Stub的內部代理類Proxy來完成。
- * */
- public interface IBookManager extends android.os.IInterface
- {
- /** Local-side IPC implementation stub class. */
- /*
- * 首先這個Stub,它是一個內部類,它繼承了Binder,所以它是一個Binder,
- * 同時Stub還實現了IBookManager中的方法。
- * */
- public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager
- {
- /*
- * Binder的唯一標識符。
- * */
- 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.
- */
- /*
- * 用於將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,
- * 這種轉換過程是區分進程的,
- * 如果客戶端和服務端位於同一進程,那麼此方法返回的就是服務端的Stub對象本身,
- * 否則返回的是系統封裝後的Stub.proxy代理對象。
- * */
- 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);
- }
-
- /*
- * 此方法用於返回當前Binder對象,也就是內部類Stub。
- * */
- @Override public android.os.IBinder asBinder()
- {
- return this;
- }
-
- /*
- * 這個方法運行在服務端中的Binder線程池中,
- * 當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝後交由此方法來處理。
- * 服務端通過code可以確定客戶端所請求的目標方法是什麼,
- * 接着從data中取出目標方法所需的參數,
- * 然後執行目標方法。
- * 當目標方法執行完畢後,就向reply中寫入返回值。
- * 如果此方法返回false,那麼客戶端的請求會失敗,因此我們可以利用這個特性來做權限驗證。
- * */
- @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.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.createFromParcel(data);
- }
- else {
- _arg0 = null;
- }
- /*
- * 這句纔是調用了真正的執行過程呢
- * */
- this.addBook(_arg0);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_registerListener:
- {
- data.enforceInterface(DESCRIPTOR);
- com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;
- _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
- this.registerListener(_arg0);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_unregisterListener:
- {
- data.enforceInterface(DESCRIPTOR);
- com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;
- _arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
- this.unregisterListener(_arg0);
- reply.writeNoException();
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
- /*
- * 代理類Proxy。
- * */
- private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager
- {
- /*
- * 這個mRemote代表的就是目標對象角色,
- * */
- 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;
- }
-
- /*
- * 這個方法運行在客戶端,
- * 因爲當客戶端和服務端不在同一進程時,服務端返回代理類Proxy,所以客戶端會通過Proxy調用到代理類的getBookList方法,
- * 當客戶端遠程調用此方法時,它的內部實現是這樣的:
- * 首先創建該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List,
- * 然後把該方法的參數信息寫入_data中,
- * 接着調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起,
- * 然後服務端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,
- * 並從_reply中取出RPC過程的返回結果。
- * 最後返回_reply中的數據。
- * */
- @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();
- }
- }
-
- @Override public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException
- {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
- mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- @Override public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException
- {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
- mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- }
-
- /*
- * 用於標識方法的整型id。
- * 它們用於在transact過程總客戶端所請求的到底是哪個方法。
- * */
- static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
- static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
- static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
- static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
-
- }
-
- /*
- * 聲明瞭在IBookManager.aidl中所聲明的方法。
- * 這裏纔是真正的方法聲明。具體實現我們仍然沒有看到呢。
- * */
- 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;
- public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
- public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
- }
注意點一:上面的Book類,就是一個可以Parcelable序列化的簡單的Book類,它裏面沒有任何的方法,就是定義了一個簡單的Book類結構。
注意點二:Book.aidl的存在是因爲在IBookManager.aidl中出現的對象也必須有aidl聲明。
注意點三:在IBookManager.aidl中,對於自動生成的IBookManager.java文件,它是服務器端的代碼。當客戶端向服務端發送連接請求時,如果客戶端和服務端在同一進程中,那麼服務端就向客戶端返回Stub這個Binder對象,如果客戶端和服務端在不同進程中,那麼服務端就向客戶端返回內部類Stub的內部代理類Proxy,然後客戶端根據這個Proxy來調用Proxy內部的方法,這個Proxy內部含有服務端真正的Binder對象也就是那個內部類Stub,在客戶端調用Proxy內部的方法也就會導致調用Stub的transact方法,而Stub的transact方法又會回調它自己的onTransact方法,onTransact方法是在服務端運行的,而transact方法是在客戶端調用的,這樣就實現了客戶端調用服務端的方法了。當然這所有的傳遞過程也少不了Parcel這個數據包的協助。整個過程懂了嗎?
![](http://static.javashuo.com/static/loading.gif)
這次應該完全懂了吧,再不懂去屎吧!
4、linkToDeath和unlinkToDeath
Binder運行在服務端進程,如果服務端進程由於某些原因異常終止,這個時候我們到服務端的Binder連接斷裂,會導致我們的遠程調用失敗。Binder提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們會收到通知,這個時候我們就可以重新發起連接請求從而恢復連接。
- /*
- * 聲明這個接口就好:
- * */
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DearhRecipient(){
- // 只需要重寫這個方法就可以了。
- @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);
五、使用Messenger
1、特點:
(1)Messenger對AIDL做了封裝,使得我們可以更簡便地進行進程間通信。由於它一次處理一個請求,所以在服務端我們不考慮線程同步的問題,因爲服務端中不存在併發執行的情形。
(2)通過它可以在不同進程中傳遞Message對象,在Message中彷彿我們需要傳遞的數據,就可以輕鬆地實現數據的進程間傳遞了。
(3)有兩個構造函數,分別接收Handler對象和IBinder對象。
2、 實現一個Messenger有如下步驟:
(1)服務端進程:
首先需要在服務端創建一個Service來處理客戶端的連接請求,同時創建一個Handler並以它作爲參數來創建一個Messenger對象,然後在Service的onBind中返回這個Messenger對象底層的Binder即可。關鍵點就在於它的返回是返回給了要綁定這個服務端的客戶端,然後客戶端拿到這個Binder再去創建Messenger,再去發送Message等等。
(2)客戶端進程:
客戶端進程中,首先要綁定服務端的Service,綁定後服務端的onBind會返回一個Binder對象,然後客戶端用服務端返回的這個Binder對象創建一個Messenger,通過這個Messenger就可以向服務器端發送消息了,發送消息類型爲Message對象,如果需要服務端能夠迴應客戶端,就像和服務端一個,我們還需要創建一個Handler並創建一個新的Messenger,並把這個Messenger對象在第一次客戶端像服務端發送消息時通過Message的replyTo參數傳遞給服務端,服務端通過讀取Message中的replyTo參數就是服務端給客戶端的的Messenger,然後就可以迴應客戶端。
(3)注意點:
客戶端給服務端發送消息的時候所用的Messenger是通過綁定服務端,然後依據onBind返回的Binder對象爲參數來創建Messenger,而服務端在迴應客戶端的時候所用的Messenger是客戶端在剛剛發送消息的時候將自身創建的Messenger作爲剛剛發送消息的Message的replyTo參數傳遞給服務端的,所以在服務端直接讀取出這個Messenger。
3、舉例:客戶端像服務端發送消息,服務端迴應客戶端
(1)先看服務端代碼:
六、使用AIDL
1、對比Messenger和AIDL:
上一節講的Messenger來進行進程間的通信,可以發現,Messenger是以串行的方式處理客戶端發來的消息的,
如果大量的消息同時發送到服務端,服務端仍然只能一個個處理,如果有大量的併發請求,那麼用Messenger就不太合適了。
而且Messenger的主要作用是爲了傳遞消息,很多時候我們可能需要跨進程調用服務端的方法,這種情形用Messenger就無法實現了。
所以我們用AIDL來實現跨進程的方法調用。
AIDL也是Messenger的底層實現,因此Messenger本質上也是AIDL,只不過系統爲我們做了封裝,從而方便上層的調用而已。
2、AIDL使用的基本思想:
(0)先來放一下我們的Book.java類,它實現了Parcelable接口:
- package com.ryg.chapter_2.aidl;
-
- import android.os.Parcel;
- import android.os.Parcelable;
-
- /*
- * (1)它是一個表示圖示信息的類,
- * 它實現了Parcelable接口,
- * (2)Book.aidl是Book類在ADIL中的聲明。
- * (3)IBookManager.aidl是我們定義的一個接口,裏面有兩個方法:getBookList和addBook,
- * 其中getBookList用於從遠程服務端獲取圖書列表,而addBook用於往圖書列表中添加一本書,
- * 當然這兩個方法主要是示例用,不一定要有實際意義。
- * (4)儘管Book類和IBookManager位於相同的包中,但是在IBookManager中仍然要導入Book類,
- * 這就是AIDL的特殊之處。
- * */
- public class Book implements Parcelable {
-
- public int bookId;
- public String bookName;
-
- public Book() {
-
- }
-
- 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();
- }
-
- @Override
- public String toString() {
- return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
- }
-
- }
(1)服務端:
服務端首先要創建一個Service用來監聽客戶端的連接請求,然後創建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,最後在Service中實現這個AIDL接口即可。
(2)客戶端:
客戶端所要做的事情就稍微簡單一些,首先需要綁定服務端的Service,在綁定成功後,將服務端返回的Binder對象轉成AIDL接口所屬的類型,接着就可以調用AIDL中的方法了。
(3)AIDL接口的創建:
首先看AIDL接口的創建,如下所示,我們創建一個後綴爲AIDL的文件,在裏面聲明瞭一個接口和兩個接口方法:
這個文件的名稱是: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);
- }
在AIDL文件中,並不是所有的數據類型都是可以使用的,那麼到底AIDL文件支持哪些類型的數據呢?
基本數據類型(int、long、char、boolean、double等);
String和CharSequence;
List:只支持ArrayList,裏面每個元素都必須能夠被AIDL支持;
Map:只支持HashMap,裏面的每個元素都必須被AIDL支持,包括key和value;
Parcelable:所有實現了Parcelable接口的對象;
AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
(其中自定義的Parcelable對象和AIDL對象必須要顯示import進來,不管它們是否和當前的AIDL文件位於同一個包內)
所以上面的IBookManager.aidl文件,裏面用到了Book這個類,這個類實現了Parcelable接口並且和IBookManager位於同一個包中,但是遵守AIDL的規範,我們仍然需要顯式的import進來:import com.ryg.chapter_2.aidl.Book;
還有一個注意點:
如果AIDL文件中用到了自定義的Parcelable對象,那麼必須新建一個和它同名的AIDL文件,並在其中聲明它爲Parcelable類型。
因爲我們在IBookManager.aidl中用到了Book這個類,所以我們必須要創建Book.aidl,然後裏面添加的內容如下:
- package com.ryg.chapter_2.aidl;
-
- parcelable Book;
事實上,AIDL中每個實現了Parcelable接口的類都需要按照上面那種方式去創建相應的AIDL文件並聲明那個類爲Parcelable。
除此之外,AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out、inout。
我們需要根據實際需要去指定參數類型,不能一概使用out或者inout,因爲這在底層實現是有開銷的。
最後,AIDL接口中只支持方法,不支持聲明靜態常量,這一點區別於傳統的接口。
爲了方便AIDL的開發,建議把所有和AIDL相關的類和文件全部放入同一個包中,這樣方便我們直接複製整個包,不容易遺漏。
需要注意的是,AIDL的包結構在服務端和客戶端要保持一致,否則運行會出錯。因爲客戶端需要反序列化服務端中和AIDL接口相關的所有類,如果類的完整路徑不一樣的話,就無法成功反序列化,程序也就無法正常運行。
(4)遠程服務端Service的實現:
上面講的是如何定義AIDL接口,下面講如何實現這個接口。
- package com.ryg.chapter_2.aidl;
-
- import java.util.List;
- import java.util.concurrent.CopyOnWriteArrayList;
- import java.util.concurrent.atomic.AtomicBoolean;
-
- import android.app.Service;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.Parcel;
- import android.os.RemoteCallbackList;
- import android.os.RemoteException;
- import android.os.SystemClock;
- import android.util.Log;
-
- /*
- * 這是一個服務端Service的典型實現。
- * 首先在onCreate中初始化添加兩本圖書,
- * 然後創建了一個Binder對象並在onBind方法中返回它。
- * */
- public class BookManagerService extends Service {
-
- private static final String TAG = "BMS";
-
- /*
- * 注意這裏採用了CopyOnWriteArrayList,這個CopyOnWriteArrayList支持併發讀/寫。
- * 因爲AIDL方法是在服務端的Binder線程池中執行的,因此當多個客戶端同時連接的時候,
- * 會存在多個線程同時訪問的情形,所以我們要在AIDL方法中處理線程同步,
- * 而我們這裏直接使用CopyOnWriteArrayList來進行自動的線程同步。
- * */
- /*
- * 在AIDL中能夠使用的List只有ArrayList,但是我們這裏卻使用了CopyOnWriteArrayList(它並不是繼承子ArrayList的),
- * 但爲什麼還能工作呢?
- * 因爲AIDL中所支持的是抽象List,而List只是一個接口,
- * 因此雖然服務端返回的是CopyOnWriteArrayList,
- * 但是Binder中會按照List的規範去訪問數據並最終形成一個ArrayList傳遞給客戶端。
- * 所以我們在服務端採用CopyOnWriteArrayList是完全可以的,
- * 和此類似的還有ConcurrentHashMap。
- * */
- private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
-
- /*
- * 創建一個Binder對象,並在onBind方法中返回它。
- * 這個Binder對象繼承自IBookManager.Stub,並實現了它內部的AIDL方法,
- * 這裏主要看getBookList和addBook這兩個AIDL方法的實現,實現過程也比較簡單,
- * */
- private Binder mBinder = new IBookManager.Stub() {
-
- @Override
- public List<Book> getBookList() throws RemoteException {
- return mBookList;
- }
-
- @Override
- public void addBook(Book book) throws RemoteException {
- mBookList.add(book);
- }
- };
-
- @Override
- public void onCreate() {
- super.onCreate();
- mBookList.add(new Book(1, "Android"));
- mBookList.add(new Book(2, "Ios"));
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
- }
還需要注意的是,我們需要註冊這個服務端,並讓它運行在獨立的進程中,這樣它和客戶端的Activity不在同一個進程中,這樣就構成了進程間通信的場景:
- <service
- android:name=".aidl.BookManagerService"
- android:process=":remote" >
- </service>
(5)客戶端的實現:
客戶端比較容易實現,首先需要綁定遠程服務,綁定成功後將服務端返回的Binder對象轉換成AIDL接口,然後就可以通過這個接口去調用服務端的遠程方法了。
- package com.ryg.chapter_2.aidl;
-
-
- import java.util.List;
- import com.ryg.chapter_2.R;
- import com.ryg.chapter_2.aidl.IBookManager;
- import com.ryg.chapter_2.utils.MyConstants;
- import android.annotation.SuppressLint;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.RemoteException;
- import android.util.Log;
- import android.view.View;
- import android.widget.Toast;
-
-
- public class BookManagerActivity extends Activity {
-
-
- private static final String TAG = "BookManagerActivity";
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- /*
- * 這裏的實現方式和Messenger簡直一樣樣的,
- * 都是在綁定服務端的過程中,服務端通過onBind方法將它的Binder傳遞過來,
- * 然後在客戶端以這個傳遞來的Binder創建對應的對象
- * */
- IBookManager bookManager = IBookManager.Stub.asInterface(service);
- try {
- /*
- * 然後就可以調用相應的方法了。
- * */
- List<Book> list = bookManager.getBookList();
- Log.i(TAG, "query book list, list type:"
- + list.getClass().getCanonicalName());
- Log.i(TAG, "query book list:" + list.toString());
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
-
- public void onServiceDisconnected(ComponentName className) {
- }
- };
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_book_manager);
- Intent intent = new Intent(this, BookManagerService.class);
- /*
- * 綁定服務:
- * */
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
-
- @Override
- protected void onDestroy() {
- unbindService(mConnection);
- super.onDestroy();
- }
-
-
- }
以上就算是一個比較簡單的完整的AIDL進行IPC的過程。
3、AIDL使用過程中的一些問題,應用觀察者模式
(1)我們用觀察者模式來實現當圖書館接收到新書後,就爲申請過新書到來通知的用戶發送新書通知。我們需要提供一個AIDL接口,每個用戶都需要實現這個接口並且向圖書館申請新書的提醒功能,同時也可以取消這個功能。用AIDL接口而不用普通接口是因爲AIDL中無法使用普通接口。
(2)首先我們創建一個IOnNewBookArrivedListener.aidl文件,當服務端有新書到來時,就會通知每一個已經申請提醒功能的用戶。從程序上來說就是調用所有IOnNewBookArrivedListener對象中的onNewBookArrived方法,並把新書的對象通過參數傳遞給客戶端。
- package com.ryg.chapter_2.aidl;
-
-
- import java.util.List;
- import java.util.concurrent.CopyOnWriteArrayList;
- import java.util.concurrent.atomic.AtomicBoolean;
-
-
- import android.app.Service;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.Parcel;
- import android.os.RemoteCallbackList;
- import android.os.RemoteException;
- import android.os.SystemClock;
- import android.util.Log;
-
-
- public class BookManagerService extends Service {
-
-
- private static final String TAG = "BMS";
-
-
- private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
-
-
- private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
- // private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList =
- // new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
-
-
- /*
- * 用來保存申請了新書通知的用戶。
- * (1)這裏有一個注意點,RemoteCallbackList是系統專門提供的用於刪除跨進程listener的接口。
- * RemoteCallbackList是一個泛型,支持管理任意的AIDL接口。
- * 它的內部有一個Map結構專門用來保存所有的AIDL回調,
- * 這個Map的key是IBinder類型,value是Callback類型,如下所示:
- * ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
- * 其中Callback中封裝了真正的遠程listener。當客戶端註冊listener的時候,它會把這個listener的信息存入mCallbacks中,
- * 其中key和value分別通過下面的方式獲得:
- * IBinder key = listener.asBinder();
- * Callback value = new Callback(listener, cookie);
- * (2)注意點二:也就是說,雖然多次跨進程傳輸客戶端的同一個對象會在服務端生成不同的對象,
- * 但這些新生成的對象有一個共同點,那就是它們的底層Binder對象是同一個。也就是說key相同。
- * (3)注意點三:RemoteCallbackList在客戶端進程終止後,能夠自動移除客戶端所註冊的listener。
- * 另外RemoteCallbackList內部自動實現了線程同步的功能,
- * 所以我們使用它來註冊和解註冊時,不需要做額外的線程同步工作。
- * */
- private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
-
-
- /*
- * 服務端的Binder對象,要傳給客戶端的,讓客戶端調用裏面的方法:
- * */
- private Binder mBinder = new IBookManager.Stub() {
-
-
- /*
- * 具體的實現原來是在服務端的服務中實現的
- * */
- @Override
- public List<Book> getBookList() throws RemoteException {
- SystemClock.sleep(5000);
- return mBookList;
- }
-
-
- @Override
- public void addBook(Book book) throws RemoteException {
- mBookList.add(book);
- }
-
-
- /*
- * 第二種權限驗證功能方法:
- * */
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- <span style="white-space:pre"> </span>// 首先查看自定義權限com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE
- int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
- Log.d(TAG, "check=" + check);
- if (check == PackageManager.PERMISSION_DENIED) {
- return false;
- }
-
-
- // 然後驗證包名:
- String packageName = null;
- String[] packages = getPackageManager().getPackagesForUid(
- getCallingUid());
- if (packages != null && packages.length > 0) {
- packageName = packages[0];
- }
- Log.d(TAG, "onTransact: " + packageName);
- if (!packageName.startsWith("com.ryg")) {
- return false;
- }
-
-
- return super.onTransact(code, data, reply, flags);
- }
-
-
- /*
- * 註冊申請新書提醒的用戶:
- * */
- @Override
- public void registerListener(IOnNewBookArrivedListener listener)
- throws RemoteException {
- /*
- * 用RemoteCallbackList,key和value都是通過listener來獲取的:
- * IBinder key = listener.asBinder();
- * Callback value = new Callback(listener, cookie);
- * 這個Binder是IOnNewBookArrivedListener這個aidl的Binder,
- * 和IBookManager這個aidl的binder不是同一個啦。
- * */
- mListenerList.register(listener);
-
-
- /*
- * RemoteCallbackList並不是一個List,
- * 遍歷RemoteCallbackList必須要使用beginBroadcast和finishBroadcast來配對使用,
- * 哪怕只是爲了獲取RemoteCallbackList中的元素個數。
- * */
- final int N = mListenerList.beginBroadcast();
- mListenerList.finishBroadcast();
- Log.d(TAG, "registerListener, current size:" + N);
- }
-
-
- @Override
- public void unregisterListener(IOnNewBookArrivedListener listener)
- throws RemoteException {
- boolean success = mListenerList.unregister(listener);
-
-
- if (success) {
- Log.d(TAG, "unregister success.");
- } else {
- Log.d(TAG, "not found, can not unregister.");
- }
-
- /*
- * RemoteCallbackList並不是一個List,
- * 遍歷RemoteCallbackList必須要使用beginBroadcast和finishBroadcast來配對使用,
- * 哪怕只是爲了獲取RemoteCallbackList中的元素個數。
- * */
- final int N = mListenerList.beginBroadcast();
- mListenerList.finishBroadcast();
- Log.d(TAG, "unregisterListener, current size:" + N);
- };
-
-
- };
-
-
- @Override
- public void onCreate() {
- super.onCreate();
- mBookList.add(new Book(1, "Android"));
- mBookList.add(new Book(2, "Ios"));
- new Thread(new ServiceWorker()).start();
- }
-
-
- @Override
- public IBinder onBind(Intent intent) {
- /*
- * 我們可以在onBind方法中進行權限驗證,驗證不能通過就直接返回null。
- * 這種方法需要服務端在AndroidManifest中聲明所需的權限:
- * <permission
- * android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
- * android:protectionLevel="normal" />
- * 在客戶端AndroidManifest中這樣聲明纔可以:
- * <uses-permission
- * android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />
- * */
- int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
- Log.d(TAG, "onbind check=" + check);
- /*
- * 如果客戶端沒有使用這個權限,就會綁定失敗。
- * */
- if (check == PackageManager.PERMISSION_DENIED) {
- return null;
- }
- return mBinder;
- }
-
-
- @Override
- public void onDestroy() {
- mIsServiceDestoryed.set(true);
- super.onDestroy();
- }
-
-
- /*
- * 當有新書到來的時候,通知每一位用戶:
- * 這裏需要注意一下的是,當新書到達的時候,
- * 服務端會回調客戶端的IOnNewBookArrivedListener對象中的onNewBookArrived方法,
- * 這個方法是在客戶端的Binder線程池中執行的
- * */
- private void onNewBookArrived(Book book) throws RemoteException {
- mBookList.add(book);
- final int N = mListenerList.beginBroadcast();
- for (int i = 0; i < N; i++) {
- IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
- if (l != null) {
- try {
- l.onNewBookArrived(book);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- mListenerList.finishBroadcast();
- }
-
-
- /*
- * 我們設定每隔5m添加一本新書:
- * */
- private class ServiceWorker implements Runnable {
- @Override
- public void run() {
- // do background processing here.....
- while (!mIsServiceDestoryed.get()) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- int bookId = mBookList.size() + 1;
- Book newBook = new Book(bookId, "new book#" + bookId);
- try {
- onNewBookArrived(newBook);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
-
- }
(3)客戶端BookManagerActivity.java:
- package com.ryg.chapter_2.aidl;
-
- import java.util.List;
- import com.ryg.chapter_2.R;
- import com.ryg.chapter_2.aidl.IBookManager;
- import com.ryg.chapter_2.utils.MyConstants;
- import android.annotation.SuppressLint;
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.RemoteException;
- import android.util.Log;
- import android.view.View;
- import android.widget.Toast;
-
- public class BookManagerActivity extends Activity {
-
- private static final String TAG = "BookManagerActivity";
- private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
-
- private IBookManager mRemoteBookManager;
-
- /*
- * 當有新書到來的時候,服務端通知每一位用戶:
- * 這裏需要注意一下的是,當新書到達的時候,
- * 服務端會回調客戶端的IOnNewBookArrivedListener對象中的onNewBookArrived方法,
- * 這個方法是在客戶端的Binder線程池中執行的,
- * 因此爲了便於進行UI操作,我們需要一個Handler可以將其切換到客戶端的主線程中去執行。
- * */
- @SuppressLint("HandlerLeak")
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_NEW_BOOK_ARRIVED:
- Log.d(TAG, "receive new book :" + msg.obj);
- break;
- default:
- super.handleMessage(msg);
- }
- }
- };
-
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
- if (mRemoteBookManager == null)
- return;
- mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
- mRemoteBookManager = null;
- // TODO:榪欓噷閲嶆柊緇戝畾榪滅▼Service
- }
- };
-
- /*
- * 連接服務器,這個IBinder service就是服務器返回給我們的Binder對象。
- * */
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- /*
- * 如果客戶端和服務端在同一進程,那麼asInterface返回內部類Stub,
- * 否則返回內部類Stub的內部代理類Proxy:
- * */
- IBookManager bookManager = IBookManager.Stub.asInterface(service);
- mRemoteBookManager = bookManager;
- try {
- /*
- * 給Binder設置死亡代理:
- * */
- mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
- List<Book> list = bookManager.getBookList();
- Log.i(TAG, "query book list, list type:"
- + list.getClass().getCanonicalName());
- Log.i(TAG, "query book list:" + list.toString());
- Book newBook = new Book(3, "Android榪溳樁");
- bookManager.addBook(newBook);
- Log.i(TAG, "add book:" + newBook);
- List<Book> newList = bookManager.getBookList();
- Log.i(TAG, "query book list:" + newList.toString());
- /*
- * 申請新書提醒功能:
- * */
- bookManager.registerListener(mOnNewBookArrivedListener);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mRemoteBookManager = null;
- Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
- }
- };
-
- /*
- * 每個客戶端用戶內部都有這樣一個對象的,用來傳遞給服務端註冊新書提醒的。
- * */
- private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
-
- /*
- * 當有新書到來的時候,服務端通知每一位用戶:
- * 這裏需要注意一下的是,當新書到達的時候,
- * 服務端會回調客戶端的IOnNewBookArrivedListener對象中的onNewBookArrived方法,
- * 這個方法是在客戶端的Binder線程池中執行的,
- * 因此爲了便於進行UI操作,我們需要一個Handler可以將其切換到客戶端的主線程中去執行。
- * */
- @Override
- public void onNewBookArrived(Book newBook) throws RemoteException {
- mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)
- .sendToTarget();
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_book_manager);
- Intent intent = new Intent(this, BookManagerService.class);
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
-
- public void onButton1Click(View view) {
- Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
- new Thread(new Runnable() {
-
- @Override
- public void run() {
- if (mRemoteBookManager != null) {
- try {
- List<Book> newList = mRemoteBookManager.getBookList();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
-
- @Override
- protected void onDestroy() {
- if (mRemoteBookManager != null
- && mRemoteBookManager.asBinder().isBinderAlive()) {
- try {
- Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
- mRemoteBookManager
- .unregisterListener(mOnNewBookArrivedListener);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- unbindService(mConnection);
- super.onDestroy();
- }
-
- }
注意點一:客戶端調用遠程服務的方法,被調用的方法運行在服務端的Binder線程池中,同時客戶端線程會被掛起。這個時候如果服務端方法執行比較耗時,就會導致客戶端線程長時間的阻塞在這裏,而如果這個客戶端線程是UI線程的話,就會導致客戶端ANR。
注意點二:由於客戶端的onServiceConnected和onServiceDisconnected方法都運行在UI線程中,所以也不可以在他們裏面直接調用服務端的耗時方法,這點要尤其注意。
注意點三:由於服務端的方法本身就運行在服務端的Binder線程中,所以服務端方法本身就可以執行大量耗時操作,這個時候切記不要在服務端方法中開線程去執行異步任務。懂?就是耗時操作在服務端方法中直接執行,不要再開啓其他的線程來執行耗時操作啦。
注意點四:同理,當遠程服務端需要調用客戶端的listener中的方法時,被調用的方法也運行在Binder線程池中,只不過是客戶端的線程池,所以,我們同樣不可以在服務端中調用客戶端的耗時方法。如果非要調用耗時方法,請確保這個方法運行在非UI線程中,否則將導致服務端無法響應。
注意點五:AIDL使用方法總結:
首先建一個Service和一個AIDL接口,接着創建一個類繼承自AIDL接口中的Stub類並實現Stub中的抽象方法,在Service的onBind方法中返回這個類的對象,然後客戶端就可以綁定服務端Service,建立連接後就可以訪問遠程服務端的方法了。
七、Binder連接池
1、問題:隨着AIDL數量的增加,我們不能無限制的增加Service。
所以,我們需要減少Service的數量,將所有的AIDL放在同一個Service中去管理。
2、工作機制
每個業務模塊創建自己的AIDL接口並實現此接口,這個時候不同業務模塊之間是不能有耦合的,所有實現細節我們要單獨開來,然後向服務端提供自己的唯一標識和其對應的Binder對象。對於服務端來說,只需要一個Service就可以了,服務端提供一個queryBind而接口,這個接口能夠根據業務模塊的特徵來返回相應的Binder對象給它們,不同的業務模塊拿到所需的Binder對象後就可以進行遠程方法調用了。由此可見,Binder連接池的主要作用是將每個業務模塊的Binder請求統一轉發到遠程Service中去執行,從而避免了重複創建Service的過程。
3、舉例說明吧
(1)我們有兩個AIDL接口(ISecurityCenter和ICompute)來模擬兩個業務模塊。然後系統會爲它們兩個在gen目錄下分別生成ISecurityCenter.java和ICompute.java文件。
ISecurityCenter.aidl:(加密解密)
- package com.ryg.chapter_2.binderpool;
-
- interface ISecurityCenter {
- String encrypt(String content);
- String decrypt(String password);
- }
ICompute.aidl:
- package com.ryg.chapter_2.binderpool;
-
- interface ICompute {
- int add(int a, int b);
- }
(2)這是上面兩個AIDL接口的實現:其中ISecurityCenter.Stub和ICompute.Stub是在系統在gen目錄下自動生成的ISecurityCenter.java和ICompute.java文件中的內部類Stub。在內部類中有它們方法的聲明,在這裏我們繼承這個內部類並重寫實現這些方法。
SecurityCenterImpl.java:
- package com.ryg.chapter_2.binderpool;
-
- import android.os.RemoteException;
-
- public class SecurityCenterImpl extends ISecurityCenter.Stub {
-
- private static final char SECRET_CODE = '^';
-
- @Override
- public String encrypt(String content) throws RemoteException {
- char[] chars = content.toCharArray();
- for (int i = 0; i < chars.length; i++) {
- chars[i] ^= SECRET_CODE;
- }
- return new String(chars);
- }
-
- @Override
- public String decrypt(String password) throws RemoteException {
- return encrypt(password);
- }
-
- }
ComputeImpl.java:
- package com.ryg.chapter_2.binderpool;
-
- import android.os.RemoteException;
-
- public class ComputeImpl extends ICompute.Stub {
-
- @Override
- public int add(int a, int b) throws RemoteException {
- return a + b;
- }
-
- }
(3)爲Binder連接池創建AIDL接口IBinderPool.aidl:
- package com.ryg.chapter_2.binderpool;
-
- interface IBinderPool {
-
- /**
- * @param binderCode, the unique token of specific Binder<br/>
- * @return specific Binder who's token is binderCode.
- */
- IBinder queryBinder(int binderCode);
- }
(4)爲Binder連接池創建遠程Service並實現IBinderPool。下面是queryBinder的具體實現,當Binder連接池連接上遠程服務時,會根據不同模塊的標識即binderCode返回不同的Binder對象,通過這個Binder對象所執行的操作全部發生在遠程服務端:
- @Override
- public IBinder queryBinder(int binderCode) throws RemoteException {
- IBinder binder = null;
- switch (binderCode) {
- case BINDER_SECURITY_CENTER: {
- binder = new SecurityCenterImpl();
- break;
- }
- case BINDER_COMPUTE: {
- binder = new ComputeImpl();
- break;
- }
- default:
- break;
- }
-
- return binder;
- }
(5)遠程Service的實現就比較簡單了:以前直接返回的是服務端的Binder對象,如今在onBind中返回的是BinderPool連接池。
- package com.ryg.chapter_2.binderpool;
-
-
- import android.app.Service;
- import android.content.Intent;