《Android開發藝術探索》讀書筆記 (2) 第2章 IPC機制

2.1 Android IPC簡介

(1)任何一個操做系統都須要有相應的IPC機制,Linux上能夠經過命名通道、共享內存、信號量等來進行進程間通訊。Android系統不只可使用了Binder機制來實現IPC,還可使用Socket實現任意兩個終端之間的通訊。java

2.2 Android中的多進程模式

(1)經過給四大組件指定android:process屬性就能夠開啓多進程模式,默認進程的進程名是包名packageName,進程名以:開頭的進程屬於當前應用的私有進程,其餘應用的組件不能夠和它跑在同一個進程中,而進程名不以:開頭的進程屬於全局進程,其餘應用經過ShareUID方法能夠和它跑在同一個進程中。android

android:process=":xyz" //進程名是 packageName:xyz
android:process="aaa.bbb.ccc" //進程名是 aaa.bbb.ccc

 

(2)Android系統會爲每一個應用分配一個惟一的UID,具備相同UID的應用才能共享數據。兩個應用經過ShareUID跑在同一個進程中是有要求的,須要這兩個應用有相同的ShareUID而且簽名相同才能夠。 在這種狀況下,它們能夠相互訪問對方的私有數據,好比data目錄、組件信息等,無論它們是否跑在同一個進程中。若是它們跑在同一個進程中,還能夠共享內存數據,它們看起來就像是一個應用的兩個部分。
(3)android系統會爲每一個進程分配一個獨立的虛擬機,不一樣的虛擬機在內存分配上有不一樣的地址空間,因此不一樣的虛擬機中訪問同一個類的對象會產生多個副本。
(4)使用多線程容易形成如下幾個問題:
1.靜態成員和單例模式徹底失效;
2.線程同步機制徹底失效:不管鎖對象仍是鎖全局對象都沒法保證線程同步;
3.SharedPreferences的可靠性降低:SharedPreferences不支持併發讀寫;
4.Application會屢次建立:當一個組件跑在一個新的進程的時候,系統要在建立新的進程的同時分配獨立的虛擬機,應用會從新啓動一次,也就會建立新的Application。運行在同一個進程中的組件是屬於同一個虛擬機和同一個Application。
同一個應用的不一樣組件,若是它們運行在不一樣進程中,那麼和它們分別屬於兩個應用沒有本質區別。git

2.3 IPC基礎概念介紹

(1)Serializable接口是Java中爲對象提供標準的序列化和反序列化操做的接口,而Parcelable接口是Android提供的序列化方式的接口。
(2)serialVersionUId是一串long型數字,主要是用來輔助序列化和反序列化的,原則上序列化後的數據中的serialVersionUId只有和當前類的serialVersionUId相同纔可以正常地被反序列化。
serialVersionUId的詳細工做機制:序列化的時候系統會把當前類的serialVersionUId寫入序列化的文件中,當反序列化的時候系統會去檢測文件中的serialVersionUId,看它是否和當前類的serialVersionUId一致,若是一致就說明序列化的類的版本和當前類的版本是相同的,這個時候能夠成功反序列化;不然說明版本不一致沒法正常反序列化。通常來講,咱們應該手動指定serialVersionUId的值。
1.靜態成員變量屬於類不屬於對象,因此不參與序列化過程;
2.聲明爲transient的成員變量不參與序列化過程。
(3)Parcelable接口內部包裝了可序列化的數據,能夠在Binder中自由傳輸,Parcelable主要用在內存序列化上,能夠直接序列化的有Intent、Bundle、Bitmap以及List和Map等等,下面是一個實現了Parcelable接口的示例github

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;
}

//「內容描述」,若是含有文件描述符返回1,不然返回0,幾乎全部狀況下都是返回0
public int describeContents() {
return 0;
}

//實現序列化操做,flags標識只有0和1,1表示標識當前對象須要做爲返回值返回,不能當即釋放資源,幾乎全部狀況都爲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();
}

}

 

(4)Binder是Android中的一個類,它實現了IBinder接口。從IPC角度看,Binder是Android中一種跨進程通訊的方式;Binder還能夠理解爲虛擬的物理設備,它的設備驅動是/dev/binder;從Framework層角度看,Binder是ServiceManager鏈接各類Manager和相應的ManagerService的橋樑;從Android應用層來講,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,經過這個Binder對象,客戶端就能夠獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
在Android開發中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及進程間通訊,較爲簡單;而Messenger的底層實際上是AIDL,正是Binder的核心工做機制。
(5)aidl工具根據aidl文件自動生成的java接口的解析:首先,它聲明瞭幾個接口方法,同時還聲明瞭幾個整型的id用於標識這些方法,id用於標識在transact過程當中客戶端所請求的究竟是哪一個方法;接着,它聲明瞭一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端都位於同一個進程時,方法調用不會走跨進程的transact過程,而當二者位於不一樣進程時,方法調用須要走transact過程,這個邏輯由Stub內部的代理類Proxy來完成。
因此,這個接口的核心就是它的內部類Stub和Stub內部的代理類Proxy。 下面分析其中的方法:
1.asInterface(android.os.IBinder obj):用於將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,這種轉換過程是區分進程的,若是客戶端和服務端是在同一個進程中,那麼這個方法返回的是服務端的Stub對象自己,不然返回的是系統封裝的Stub.Proxy對象。
2.asBinder:返回當前Binder對象。
3.onTransact:這個方法運行在服務端中的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會經過系統底層封裝後交由此方法來處理。
這個方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)
服務端經過code能夠知道客戶端請求的目標方法,接着從data中取出所需的參數,而後執行目標方法,執行完畢以後,將結果寫入到reply中。若是此方法返回false,說明客戶端的請求失敗,利用這個特性能夠作權限驗證(即驗證是否有權限調用該服務)。
4.Proxy#[Method]:代理類中的接口方法,這些方法運行在客戶端,當客戶端遠程調用此方法時,它的內部實現是:首先建立該方法所須要的參數,而後把方法的參數信息寫入到_data中,接着調用transact方法來發起RPC請求,同時當前線程掛起;而後服務端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply中取出RPC過程的返回結果,最後返回_reply中的數據。數組

若是搞清楚了自動生成的接口文件的結構和做用以後,實際上是能夠不用經過AIDL而直接實現Binder的,主席寫的示例代碼緩存

(6)Binder的兩個重要方法linkToDeathunlinkToDeath
Binder運行在服務端,若是因爲某種緣由服務端異常終止了的話會致使客戶端的遠程調用失敗,因此Binder提供了兩個配對的方法linkToDeathunlinkToDeath,經過linkToDeath方法能夠給Binder設置一個死亡代理,當Binder死亡的時候客戶端就會收到通知,而後就能夠從新發起鏈接請求從而恢復鏈接了。
如何給Binder設置死亡代理呢?
1.聲明一個DeathRecipient對象,DeathRecipient是一個接口,其內部只有一個方法bindeDied,實現這個方法就能夠在Binder死亡的時候收到通知了。網絡

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mRemoteBookManager == null) return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:這裏從新綁定遠程Service
}
};

 

2.在客戶端綁定遠程服務成功以後,給binder設置死亡代理多線程

mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);

 

2.4 Android中的IPC方式

(1)使用Bundle:Bundle實現了Parcelable接口,Activity、Service和Receiver都支持在Intent中傳遞Bundle數據。併發

(2)使用文件共享:這種方式簡單,適合在對數據同步要求不高的進程之間進行通訊,而且要妥善處理併發讀寫的問題。
SharedPreferences是一個特例,雖然它也是文件的一種,可是因爲系統對它的讀寫有必定的緩存策略,即在內存中會有一份SharedPreferences文件的緩存,所以在多進程模式下,系統對它的讀寫就變得不可靠,當面對高併發讀寫訪問的時候,有很大概率會丟失數據,所以,不建議在進程間通訊中使用SharedPreferences。ide

(3)使用Messenger:Messenger是一種輕量級的IPC方案,它的底層實現就是AIDL。Messenger是以串行的方式處理請求的,即服務端只能一個個處理,不存在併發執行的情形,詳細的示例見原書。

(4)使用AIDL
大體流程:首先建一個Service和一個AIDL接口,接着建立一個類繼承自AIDL接口中的Stub類並實現Stub類中的抽象方法,在Service的onBind方法中返回這個類的對象,而後客戶端就能夠綁定服務端Service,創建鏈接後就能夠訪問遠程服務端的方法了。
1.AIDL支持的數據類型:基本數據類型、StringCharSequenceArrayListHashMapParcelable以及AIDL
2.某些類即便和AIDL文件在同一個包中也要顯式import進來;
3.AIDL中除了基本數據類,其餘類型的參數都要標上方向:inout或者inout
4.AIDL接口中支持方法,不支持聲明靜態變量;
5.爲了方便AIDL的開發,建議把全部和AIDL相關的類和文件所有放入同一個包中,這樣作的好處是,當客戶端是另外一個應用的時候,能夠直接把整個包複製到客戶端工程中。
6.RemoteCallbackList是系統專門提供的用於刪除跨進程Listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口,由於全部的AIDL接口都繼承自IInterface接口。

(5)使用ContentProvider
1.ContentProvider主要以表格的形式來組織數據,而且能夠包含多個表;
2.ContentProvider還支持文件數據,好比圖片、視頻等,系統提供的MediaStore就是文件類型的ContentProvider;
3.ContentProvider對底層的數據存儲方式沒有任何要求,能夠是SQLite、文件,甚至是內存中的一個對象都行;
4.要觀察ContentProvider中的數據變化狀況,能夠經過ContentResolverregisterContentObserver方法來註冊觀察者;

(6)使用Socket
Socket是網絡通訊中「套接字」的概念,分爲流式套接字和用戶數據包套接字兩種,分別對應網絡的傳輸控制層的TCP和UDP協議。

2.5 Binder鏈接池

(1)當項目規模很大的時候,建立不少個Service是不對的作法,由於service是系統資源,太多的service會使得應用看起來很重,因此最好是將全部的AIDL放在同一個Service中去管理。整個工做機制是:每一個業務模塊建立本身的AIDL接口並實現此接口,這個時候不一樣業務模塊之間是不能有耦合的,全部實現細節咱們要單獨開來,而後向服務端提供本身的惟一標識和其對應的Binder對象;對於服務端來講,只須要一個Service,服務端提供一個queryBinder接口,這個接口可以根據業務模塊的特徵來返回相應的Binder對象給它們,不一樣的業務模塊拿到所需的Binder對象後就能夠進行遠程方法調用了。
Binder鏈接池的主要做用就是將每一個業務模塊的Binder請求統一轉發到遠程Service去執行,從而避免了重複建立Service的過程。
(2)做者實現的Binder鏈接池BinderPool的實現源碼,建議在AIDL開發工做中引入BinderPool機制。

2.6 選用合適的IPC方式

img

OK,本章結束,謝謝閱讀。

相關文章
相關標籤/搜索