《Android開發藝術探討》之 Android IPC 介紹
IPC是 Inter-Proscess Communication的縮寫,含義爲進程間的通訊或者跨進程通訊,是指兩個進程之間進行數據交換的過程。按操作系統的中的描述,線程是CPU調度最小的單元,同時線程是一種有限的系統資源,而進程是指一個執行單元,在PC和移動設備上指一個程序或者一個應用。一個進程可以包含多個線程,因此進程和線程是包含於被包含的關係。
IPC的使用場景就必須提到多進程,只有面對多進程這種場景下,才需要考慮進程間通訊。多進程的情況分爲兩種:第一種是一個應用因爲某些原因自身需要採用多進程模式來實現,原因有很多,應用特殊原因需要運行的單獨的進程中,或者爲了加大一個應用可使用內存所以需要通過多進程來獲取多分內存空間。另外一種情況是:當前應用需要向其他應用獲取數據,由於是兩個應用,所以必須採取跨進程方式來獲取所需要數據。
Android中的多進程模式
開啓Android多進程模式很簡單,就是給四大組件(Activity,Service,Receiver,ContentProvider)在AndroidMenifest中指定android:process屬性。另外還有一種非常規的做法,那就是通過JNI在native層去fork一個新的進程。
給process指定多進程有兩種不同的形式
:remote
進程名以 “:”的含義是指要在進程名前面附加上當前的包名,這個進程屬於當前應用的私有進程,其他應用不可以和他跑在同一個進程。
com.xxx.xxx
這種屬於全局進程,其他應用可以通過ShareUID方式可以和它跑在同一個進程,我們都知道系統會爲每個應用分配一個唯一的UID,具有相同UID的應用才能共享數據。兩個應用通過ShareUID跑在同一個進程,是需要相同的ShareUID並且簽名相同纔可以。不管它們是不是跑在同一個進程中,具有相同ShareUID的它們可以訪問對方的私有數據,如:data目錄、組件信息等。當然如果是在同一個進程中,除了data目錄、組件信息還能共享內存數據。
多進程運行機制
我們知道Android爲每一個應用分配了一個獨立的虛擬機,或者說爲每一個進程都分配了一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致在不同的虛擬機訪問同一個類的對象會產生多分副本。
所有運行在不同進程中的四大組件,只要它們之間需要通過內存來共享數據,都會共享失敗,這也是多進程所帶來的主要影響,一般來說,使用多進程會造成如下幾方面的問題。
靜態成員和單例模式完全失效(都是不同的虛擬機)
線程同步機制完全失效(都不是同一塊內存了)
SharePreferences 的可靠性下降(底層通過XML去執行操作的,併發很可能出問題,甚至併發讀、寫都有可能出問題)
Application會多次創建(當一個組件跑在一個新的進程中,由於系統要在創建新的進程同時分配獨立的虛擬機,所以這個過程其實就是啓動一個應用的過程,因此係統又把這個應用重新啓動了一遍,既然都重新啓動了,那麼自然會創建新的Application)
IPC基礎概念介紹
Serializable接口
Serializable是Java所提供的一個序列號接口,它是一個空接口,爲對象提供標準的序列化和反序列化操作。使用Serializable相當簡單,只需要實現Serializable接口並聲明一個serialVersionUID,其實這個serialVersionUID也不是必需的,如果不聲明這個serialVersionUID也是可以實現序列化的,但是這將會對反序列化過程產生影響。
//序列化
User user = new User(“xia”,”123455”);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“cache.txt”));
out.write(user);
out.close();
//反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(“cache.txt”));
User newUser = (User)in.readObject();
in.close();
serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的數據中serialVersionUID只有和當前類serialVersionUID相同才能夠正常的被反序列化。serialVersionUID的詳細工作機制是這樣的:序列化的時候系統會把當前類的serialVersionUID寫入序列化的文件中(也可能是其他中介),但反序列化的時候會去檢測文件中的serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一致就說明序列化的版本和當前版本是相同的,這個時候可以成功的反序列化,否則就說明當前類和序列化的類相比發生了某些變換,比如成員變量的數量、類型發生了改變,這個時候無法正常反序列化。
一般來說,我們應該手動指定serialVersionUID的值,如1L,也可以根據自身結構自動去生成它的hash值,這樣序列化和反序列化時兩者的serialVersionUID是相同的。如果不指定serialVersionUID的值,反序列化時當前類有所改變,比如增加或者刪除了某些成員變量,那麼系統就會重新計算當前類型的hash值並把它賦值給serialVersionUID,這個時候當前類的serialVersionUID就和序列化數據中的serialVersionUID不一致,於是反序列化失敗,程序就會出現crash。所以避免反序列化過程的失敗。比如當版本升級後,我們很可能刪除了某個成員變量也可能增加了一些新的成員變量,這個時候序列化過程仍然能夠成功,程序可以最大限度地恢復數據,相反,如果不指定serialVersionUID的話,程序則會掛掉。當然我們還要考慮另外一種情況,如果類的結構發生了非常規性的改變,比如修改了類名,修改了成員變量的類型,這個時候儘管serialVersionUID驗證通過,但是反序列化還是會失敗,因爲類結構有了毀滅性的改變,根本無法從老版本的數據中還原出一個新的類結構對象。
靜態成員變量屬於類不屬於對象,所以不會參與序列化過程,其次用transient關鍵字標記的成員變量不參與序列化配置。
- Parceable 接口
Parceable也是一個接口,只有實現這個接口,一個類的對象就可以實現序列化並可以通過Intent和Binder傳遞。
public class User implements Parcelable {
public int UserId;
public String userName;
public boolean isMale;
protected User(Parcel in) {
//從序列化後的對象中創建原始對象
UserId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
//從序列化後的對象中創建原始對象
return new User(in);
}
@Override
public User[] newArray(int size) {
//創建指定長度的原始對象數組
return new User[size];
}
};
@Override
public int describeContents() {
/**
返回當前對象的內容描述。如果含有文件描述符,返回1,否則返回0,幾乎所有情況都返回0
*/
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
/**將當前對象寫入序列化結構中,其中flags,標識有0或1
爲1時標識當前對象需要作返回值返回,不能立即釋放資源,幾乎所有情況 都爲0**/
dest.writeInt(UserId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
}
}
Parcel內部包裝了可序列化的數據,可以在Binder中自由傳輸,從上述代碼中可以看出,在序列化過程中需要實現的功能有序列化、反序列化和內部描述序列化功能由writeParcel方法完成,最終是通過Parcel中的一系列write方法來完成的。反序列化功能由CREATOR來完成,其內部標明瞭如何創建序列化對象和數組,並通過Parcel一系列read方法來完成反序列化過程;內容描述功能由describeContents來完成,幾乎所有情況下這個方法都應該返回0,僅噹噹前對象中存在文件描述符時,此方法返回1.系統已經提供了許多實現Parcelable接口的類,它們都是可以直接序列化的,如:Intent、Bundle、Bitmap等,同時List 和 Map也可以序列化,前提時它們裏面每個元素都是可序列化的。
既然Parcelable 和Serializable 都可以用於Intent間的數據傳遞,那麼如何選擇了。
- Serializable是Java中的序列化接口,其使用起來簡單但是開銷大,序列化和反序列化過程都需要大量的 I/O操作。
- Parcelable是Android中的序列化方式,更適用於在Android平臺上,它的缺點就是用起來稍微麻煩,但效率很高,這是Android推薦方式,因此,首選Parcelable。但通過Parcelable將對象序列化到存儲設備中或將對象序列化後通過網絡傳輸也都是可以的,但是這個過程會稍顯複雜,因此這種情況下建議使用Serializable。
- Binder
Binder是一個非常複雜,這裏只是介紹下Binder的使用及上層實現原理。
Binder是Android中的一個類,它實現了IBinder的接口。從IPC角度來說,Binder是Android中一種跨進程的通訊方式,Binder還可以理解爲一種虛擬物理設備,它的設備驅動是 /dev/binder,該通訊方式在Linux中沒有;從Android Framework,角度來說,Binder是ServiceManger連接各種Manger(ActivityManger 、WindowManger,等等)和相應的MangeSrervice的橋樑;從Android應用層來說,
Binder是客戶端和服務端進行通訊的媒介,當bindSrervice的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以用獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。普通Srervice中的Binder不涉及進程間通信,下面通過AIDL來分析Binder的工作過程。
//Book.java
public class Book implements Parcelable{
int id;
String type;
public Book(int id, String type) {
this.id = id;
this.type = type;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", type='" + type + '\'' +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.type);
}
protected Book(Parcel in) {
this.id = in.readInt();
this.type = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
```java
// Book.aidl
package com.example.xiahao.myapplication;
parcelable Book;
// IBookManager.aidl
package com.example.xiahao.myapplication;
// Declare any non-default types here with import statements
import com.example.xiahao.myapplication.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的特殊之處。builde的項目,系統爲我們在gen目錄下生產IBookManage.java的類,接下來我們需要根據這個系統生成的IBookManag類來分析Binder的工作原理
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/xiahao/Documents/WorkSpace/AndroidStudioProjects/MyApplication/app/src/main/aidl/com/example/xiahao/myapplication/IBookManager.aidl
*/
package com.example.xiahao.myapplication;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.xiahao.myapplication.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.xiahao.myapplication.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.xiahao.myapplication.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.xiahao.myapplication.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) &&
(iin instanceof com.example.xiahao.myapplication.IBookManager))) {
return ((com.example.xiahao.myapplication.IBookManager) iin);}
return new com.example.xiahao.myapplication.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.example.xiahao.myapplication.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.xiahao.myapplication.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.xiahao.myapplication.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
com.example.xiahao.myapplication.IOnNewBookArrivedListener _arg0;
_arg0 = com.example.xiahao.myapplication.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.unegisterListener(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.xiahao.myapplication.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.example.xiahao.myapplication.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.example.xiahao.myapplication.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.xiahao.myapplication.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.example.xiahao.myapplication.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.example.xiahao.myapplication.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.xiahao.myapplication.Book book) throws android.os.RemoteException;
}
可以看到根據IBookManager.aidl系統爲我們生成了IBookManager.java這個類,它繼承了IInterface這個接口,同時它自己也還是個接口,所以可以在Binder中傳輸的接口都需要繼承IInterface接口。
首先,它聲明瞭兩個方法getBookList 和 addBook ,這就是我們在IBookManger.aidl中所聲明的方法,同時它還聲明瞭兩個整數的id分別用於標識這兩個方法,這兩個id用標識在transact過程客戶端請求的到底是哪個方法。接着,還聲明瞭一個內部類Stub,這個Stub就是一個Biner類,當客戶端和服務端都位於同一個進程中,方法調用不會走跨進程的transact過程,而當兩者位於不同的進程中,方法需要走transact過程,這個邏輯由Stub的內部代理類 Proxy來完成。所以這個接口的實現核心就是它的內部類Stud和Stub的內部代理類 Proxy。
DESCRIPTOR
Binder的唯一標識,一般用於當前的Binder的類名錶示,比如本例中的 “com.example.xiahao.IBookManger”
asInterface(android.os.IBinder obj)
用於將服務端的Binder對象轉換成客戶端所需要的AIDL接口的類型對象,這種轉換時區分進程的,如果客戶端和服務端位於同一進程,那麼此方法返回的就是服務端的Stub對象本身,否則返回的時系統封裝後的Stub.proxy對象。
asBinder
此方法用於返回當前的Binder對象
onTransact
這個方法運行在服務端的Binder線程池中,當客戶端發起跨進程請求的時,遠程請求會通過系統底層封裝後交由此方法來處理。該方法的原型爲
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
服務端通過code可以確定客戶端請求的目標方法是什麼,接着從data中取出目標方法所需要的參數(如果目標方法有參數的話),然後執行目標方法,當目標方法執行完畢後,就向reply中寫入返回值(如果目標方法有返回值的話),onTransact方法的執行過程就是這樣的。需要注意的時,如果此方法返回false,那麼客戶端的請求就會失敗,因此我們可以利用這個特性來做權限驗證,畢竟我們也不希望隨便一個進程都能遠程調用我們的服務。
Proxy#getBookList
這個方法運行在客戶端,當客戶端調用此方法時,它的內部實現是這樣的:首先創建該方法所需要的的輸入型Prcel對象 _data、輸出型Prcel對象 _reply和返回值對象List;然後把該方法的參數信息寫入 _data中(如果有參數的話);接着調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;然後服務端onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並 _reply中取出RPC過程的返回結果;最後返回 _reply中的數據。
Proxy#getBookList
這個方法運行在客戶端,它的執行過程和getBookList是一樣的,addBook沒有返回值,所以他不需要從 _reply中取出返回值。
注意:當客戶端發起遠程請求時,由於當前線程會被掛起直至服務器返回數據,所以如果一個遠程的方法是很耗時的話,那麼不能再UI線程中發起次遠程請求;其次,由於服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該採用同步的方式實現,因爲它已經運行在一個線程中了。爲了更好的說明Binder,下面給出一個工作機制的圖:
這裏寫圖片描述
接下來,介紹下Binder的兩個很重要的方法 linkTodeath 和 unlinkTodeath,如果服務端的Binder連接斷裂 (稱之爲 Binder 死亡),會導致我們遠程調用失敗。更爲關鍵的時,如果我們不知道Binder的連接已經斷裂,那麼客戶端的功能就會受到影響。爲此我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候我們就可以給Binder設置一個死亡代理,這個時候就可以重新發起連接請求從而恢復連接。
聲明一個IBinder.DeathRecipient對象,IBinder.DeathRecipient是一個接口,其內部只有一個binderDied,我們需要實現這個方法,當binder死亡的時候,系統就會回調binderDied方法,然後我們就可以移除之前綁定的binder代理並重新綁定遠程服務:
//銷燬代理類,重啓服務
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG, “binder: deed”);
mIBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
mIBinderPool = null;
connectBinderPoolService();
}
};
在客戶端綁定遠程服務成功後,給binder設置死亡代理
mIBinderPool = IBinderPool.Stub.asInterface(iBinder);
try {
mIBinderPool.asBinder().linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
其中linkDeath的第二個參數是個標記位,我們直接設爲0即可。經過上面的兩個步驟就給我們的binder設置了死亡代理,當binder死亡的時候我們就可以收到通知了。另外Binder的方法isBinderAlive也可以判斷Binder是否死亡。
Android中的IPC方式
使用Bundle
四大組件中的三大組件(Activity、Service、Receiver)都是支持Intent中傳遞Bundle數據的,由於Bundle實現了Pracel接口,所以它可以很方便的在不同的進程間傳輸。基於這一點,當我們調用了另一個進程中的Activity、Service、Receiver時,我們就可以在Bundle中附加我們需要傳輸給遠程進程的信息並通過Intent發生出去。當然,被傳輸的數據必須能夠被序列化,比如基本類型,實現了Pracelable、Serialzable接口以及一些Android支持的特殊對象。具體可以看Bundle這個類!
文件共享
兩個進程通過讀/寫同一個文件交換數據,比如A進程把數據寫入文件,B進程通過讀取這個文件來獲取數據。由於Android系統基於Linux,所以併發讀/寫文件沒有限制性,甚至兩個線程對同一個文件進行寫操作都是允許的,儘管這會出現問題,除了交換基本信息之外,我們可以序列化一個對象到文件系統中的同時從另一個進程恢復這個對象。文件共享的侷限性是,併發讀/寫問題,如果併發讀/寫,讀出的數據可能不是最新的,如果是併發寫的話就更嚴重了。
SharePreferences也屬於文件的一種,但是由於系統對它的讀/寫有一定的緩存策略,即在內存中會有一份SharePreferences文件的緩存,因此在多進程的情況下,系統對它的讀/寫變得不可靠,當面對高併發讀/寫數據就很有很大機率丟失數據,不建議在進程間通信中使用SharePreferences。
Messenger
通過Messenger可以在不同進程中傳遞Message對象,在Message中放入我們需要傳入的數據,就可以實現數據的進程間傳遞了。Messenger是一種輕量級的IPC方案,它的底層實現是AIDL,並對AIDI做了封裝,使得可以很方便的進行進程間通信。同時,由於處理一個請求,因此在服務端我們不用考慮線程同步的問題。
服務端相關代碼
private static final String TAG = “MessengerService”;
private static class MessengerServiceHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_CLIENT:
Log.i(TAG, "server form client\t" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null, Constants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "收到消息,我是服務端!");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerServiceHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
在AndroidManifest 配置服務 android:process=”:remote”
- 客戶端
private Messenger mMessenger;
private static final String TAG = “MainActivity”;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message message = Message.obtain(null, Constants.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "我是客戶端");
message.setData(bundle);
//當客戶端發送消息的時候,需要把接受服務端回覆的Messenger通過Message的replytTo參數傳遞給服務端。
message.replyTo = mGetReplyMessenger;
try {
mMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_SERVICE:
Log.i(TAG, "receive msg from Service\t" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
}
```
在Messenger中進行數據傳遞必須將數據放入Message中,而Messenger和Message都是實現了Parcelable接口,因此可以跨進程傳輸。實際上:通過Messenger來傳輸Message,Message中能使用的載體只有what、arg1、arg2、Bundle已經replyTo。Message的另一個字段object在同一進程中是很實用的,但是在進程間通信時候,非系統的Parcelable對象無法通過object字段來傳輸。但可以實用Bundle,Bundle可以支持大量的數據類型。
使用AIDL
用Messenger來進行進程間通信時是以串行的方式處理客戶端發來的消息,如果大量的消息同時發送到服務端,服務端仍然只能一個個處理,如果大量的併發請求,那麼用Messenger就不太合適了。同時Messenger的作用主要是爲了傳遞消息,很多時候我們可能需要跨進程調用服務端方法,這個時候我們就可以用AIDL了。
大致步驟如下:
服務端
服務端首先要創建一個Service用來監聽客戶端的連接,然後創建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,最後在Service中實現這個AIDL文件即可。
客戶端
首先綁定服務端的Service,綁定成功後,將服務端返回的Binder對象轉成AIDL接口所屬的類型,接着就可以調用AIDL中的方法。
AIDL接口的創建
// IBookManager.aidl
package com.example.xiahao.myapplication;
// Declare any non-default types here with import statements
import com.example.xiahao.myapplication.Book;
import com.example.xiahao.myapplication.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
在AIDL文件中,並不是所有的類型都支持的,支持的類型如下:
- 基本數據類型(int 、long 、char、 boolean 、double等);
- string 和CharSequence
- List 只支持ArrayList,裏面每個元素必須能夠被AIDL支持
- Map 只支持HashMap ,裏面每個元素必須能夠被AIDL支持,包括key 和value
- Parcelable:所有實現了Parcelable接口的對象
- AIDL:所有AIDL接口本身也可以在AIDL文件中使用。
以上6中類型,其中自定義Parcelable對象和AIDL文件必須顯示的import進來,不管是否和當前的AIDL位於同一文件中。另外,如果AIDL用到了自定義的Parcelable對象必須新建一個和它同名的的AIDL文件,上面我們用到了Book,所以必須創建Book.aidl.
// Book.aidl
package com.example.xiahao.myapplication;
parcelable Book;
爲了方便開發,建議把所以AIDL相關類和文件全部放入同一包中,當客戶端是另外一個應用時,我們可以直接把整個包複製到客戶端工程中。後面會給出一個書上的例子:具體包含,基本的AIDL調用,註冊解註冊,權限驗證,斷開重連,binder連接池一個服務處理多個AIDL的調用。
- 使用ContentProvide
ContentProvide專門用來應用之間的通訊,和Messenger一樣,ContentProvide底層也是Binder,雖然底層Binder但使用要比AIDL簡單多,因爲系統幫我們做了封裝。
- 使用Socket
通過Socket進行進程間的通訊,它分爲流式套接字和用戶數據套接字兩種,分別對應網絡協議層中的TCP和UDP協議。TCP是面向連接的協議,提供穩定的雙向的通訊功能,TCP的建立需要經過 “三次握手”才能完成,爲提供穩定的的數據傳輸功能,其本身提供了超時重連機制,因此具有很高的穩定性。而UDP是無連接的,提供不穩定的單向的通訊功能,當然UDP也可以實現雙向通訊功能。在性能上,UDP具有更好的效率,其缺點就是不保證數據一定能夠正確傳輸,尤其是在網絡擁塞的情況下。
- 選用合適的IPC方式
給出書中的一張表格《Android開發藝術探討》
名稱 優點 缺點 適用場景
Bundle 簡單易用 只能傳輸Bundle支持的數據類型 四大組件的進程間通信
文件共享 簡單易用 不適合高併發場景,並且無法做到進程間的即時通訊 無併發訪問情形,交換簡單的數據實時性不高的場景
AIDL 功能強大,支持一對多併發通信,支持實時通信 使用稍複雜,需要處理好線程同步 一對多通信且RPC需要
Messenger 功能一般,支持一對多串行通信,支持實時通信 不能很好的處理高併發情形,不支持RPC,數據通過Message進行傳輸,因此只能傳輸Bundle支持的數據類型 低併發的一對多即時通訊,無RPC需要,或者無需返回結果的RPC需求
ContentProvider 在數據訪問功能很強大,支持一對多併發數據共享,可通過call方法擴展其他操作 可以理解爲受約束的AIDL,主要提供數據的CRUD操作 一對多的進程間的數據共享
Socket 功能強大,可以通過網絡傳輸字節流,並支持一對多併發實時通信 實現細節稍微有點繁瑣,不支持直接的RPC 網絡數據交換