一個讀書的小Tips:如果對原書內容不是很了解的小伙伴,可以先看讀書筆記,心中有個概要,然后再細讀原書;
學習內容:開篇介紹了Android中的多進程概念已經多進程開發中常見的注意事項,接著介紹了Android序列化和Binder,以及詳細介紹了Bundle、文件共享、AIDL、Messenger、ContentProvider和Socket這幾種進程間通訊;還講解了如何使用Binder連接池來達到多個AIDL共用一個Service。最后講解了各種進程間通訊的優缺點和適用場景。接下來 咱們開始吧
2.1 Android IPC簡介
- IPC為進程間通訊,兩個進程之間進行數據交換的過程。
- IPC不是Android所獨有的,任何一個操作系統都有對應的IPC機制。Windows上通過剪切板、管道、油槽等進行進程間通訊。Linux上通過命名空間、共享內容、信號量等進行進程間通訊。Android中沒有完全繼承于Linux,有特色的進程間通訊方式是Binder,還支持Socket。
- 使用場景:由于某些原因應用自身需要采用多進程模式來實現,或者為了加大一個應用可使用的內存,因為Android對當個應用可使用的最大內存做了限制。
2.2 Android中的多進程模式
- 同一個應用,通過給四大組件指定android:process屬性,就可以開啟多進程模式。
- 進程名以":"開頭的屬于當前應用的私有進程,其他應用的組件不可以和他跑在同一個進程里面。而進程名不以":"開頭的進程屬于全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。兩個應用可以通過ShareUID跑在同一個進程并且簽名相同,他們可以共享data目錄、組件信息、共享內存數據。
- 多進程通訊的問題:
- 靜態成員和單例模式完全失效。
- 線程同步機制完全失效。
- SharedPreferences的可靠性下降
- Application會多次創建
問題1、2原因是因為進程不同,已經不是同一塊內存了;
問題3是因為SharedPreferences不支持兩個進程同事進行讀寫操作,有一定幾率導致數據丟失;
問題4是當一個組件跑在一個新的進程中,系統會為他創建新的進程同時分配獨立的虛擬機,所有這個過程其實就是啟動一個應用的過程,,因此相當于系統又把這個應用重新啟動了一遍,Application也是新建了。
實現跨進程通訊有很多方式共享文件、SharedPreferences、基于Binder的Messenger和AIDL、Socket等。
2.3 IPC基礎概念介紹
2.3.1 Serializable接口
- Serializable是Java所提供的一個序列化接口
- serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數據中的serialVersionUID要和當前類的serialVersionUID相同才能正常的序列化。
- 靜態成員變量屬于類不屬于對象,所以不會參加序列化過程;其次用transient關鍵字標明的成員變量也不參加序列化過程。
- 重寫如下兩個方法可以重寫系統默認的序列化和反序列化過程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
2.3.2 Parcelable接口
- Android中特有的序列化方式,效率相對Serializable更高,占用內存相對也更少,但使用起來稍微麻煩點。
public class Book implements Parcelable {
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
public int code;
public String name;
public Book(int code, String name) {
this.code = code;
this.name = name;
}
protected Book(Parcel in) {
code = in.readInt();
name = in.readString();
}
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(code);
dest.writeString(name);
}
}
- 序列化功能由writeToParcel方法來完成,最終通過Parcel中的一系列write方法完成的。反序列化功能由CREATEOR來完成,其內部標明了如何創建序列號對象和訴諸,并通過Parcel的一系列read方法來完成反序列化過程。內容描述功能由describeContents方法來完成,幾乎所有情況都返回0,只有當前對象存在文件描述符時,才返回1。
2.3.3 Binder
- 從IPC的角度來說,Binder是Android的一種跨進程的通訊方式;Binder也可以理解為是一種虛擬額物理設備,他的設備驅動是/dev/binder;從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager、等等)和ManagerService的橋梁;從Android應用層來說,Binder是客戶端與服務端通訊的媒介。在Android開發中,Binder主要用于Service中,包括AIDL和Messenger,其中普通的Service的Binder不涉及進程間通訊;而Messenger的底層其實就是AIDL。
- 系統會根據AIDL文件生成同名的.java類;首先聲明與AIDL文件相對應的幾個接口方法,還申明每個接口方法相對應的整形id來做為標識,這幾個id在transact過程中標識客戶端請求的到底是什么方法。接著會聲明一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端處于同一個進程的時候,方法調用不會走transact過程,處于不同進程時,方法調用會走transact過程,這個邏輯由Stub的內部代理類Proxy來完成。所以核心實現在于它的內部類Stub和Stub的內部代理類Proxy,下面分析其中的方法:
- DESCRIPTOR Binder的唯一標識,一般用當前Binder的類名表示。
- asInterface(android.os.IBinder obj) 將服務端的Binder對象轉換成客戶端所需要的AIDL接口類型的對象;如果客戶端和服務端位于相同進程,那么此方法返回的就是服務端Stub對象本身,否則返回系統封裝后的Stub.proxy對象。
- asBinder 用于返回當前的Binder對象
- onTransact 運行在服務端的Binder線程池中,當客戶端發起跨進程通訊時,遠程請求會通過系統底層封裝交由此方法處理。
public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
服務端通過code確認客戶端請求的目標方法是什么,接著從data中取出目標方法所需的參數(如果有),然后執行目標方法。當目標方法執行完后,向reply中寫入返回值(如果有)。如果方法返回值為false,那么服務端的請求會失敗,利用這個特性我們可以來做權限驗證。
5.Proxy#[Method] 代理類中的接口方法。首先創建該方法所需要的輸入型參數Parcel對象_data和輸出型參數Parcel對象_reply,然后把參數寫入_data中,接著調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;然后服務端的onTransace方法會被調用直到RPC過程返回后,當前線程繼續執行,并從_reply中去除RPC過程的返回結果。
- Binder的兩個重要方法linkToDeath和unlinkToDeath。通過linkToDeath可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,然后就可以重新發起連接請求。聲明一個DeathRecipient對象,DeathRecipient是一個接口,其內部只有一個方法binderDied,實現這個方法后就可以在Binder死亡的時候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:接下來重新綁定遠程Service
}
}
在客戶端綁定遠程服務成功后,給Binder設置死亡代理
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
- 當清楚AIDL接口文件的結構和作用后,是可以不通過AIDL而直接實現Binder來進行跨進程通訊的。細節請參考原書以及隨書代碼
2.4 Android的IPC方式
- 使用Bundle:由于Binder實現了Parcelable接口,所以可以方便的在不同進程中傳輸;Activity、Service和Receiver都支持在Intent中傳遞Bundle數據。
- 使用文件共享:兩個進程通過讀/寫一個文件來交換數據;適合對數據同步要求性不高的場景;并要避免并發寫這種場景或者處理好線程同步問題。SharedPreferences是個特例,雖然也是文件的一種,但系統在內存中有一份SharedPreferences文件的緩存,因此在多線程模式下,系統對他的讀/寫就變得不可靠,高并發讀寫SharedPreferences有一定幾率會丟失數據,因此不建議在多進程通訊時采用SharedPreferences。
- 使用Messenger:Messenger是輕量級的IPC方案,底層實現是AIDL,他對AIDL進行了封裝,Messenger 服務端是以串行的方式來處理客戶端的請求的,不存在并發執行的情形。
- 使用AIDL:服務端首先創建一個Service用來監聽客戶端的連接請求,然后創建一個AIDL文件,將暴露給客戶端的接口在AIDL文件中聲明,最后在Service中實現這個AIDL接口即可。客戶端首先綁定服務端的Service,綁定成功后,將服務端返回的Binder對象轉化成AIDL接口所屬的類型,調用相對應的AIDL中的方法。
- AIDL支持的數據類型:
基本數據類型;
String、CharSequence;
List 只支持ArrayList,里面的元素必須都能被AIDL所支持;
Map 只支持HashMap,里面的元素(key和value)必須都能被AIDL所支持;Parcelable 所有實現了Parcelable接口的對象;
AIDL 所有AIDL接口本身也可以在AIDL文件中使用。 - 自定義的Parcelable對象和AIDL對象必須顯示的import進來(即使在同一個包)。
- 除了基本數據類型,需要用inout表示輸入輸出型參數。
- 為了方便AIDL開發,建議把所有和AIDL相關的類和文件都放在同一個包中,好處在于,當客戶端是另一個應用的時候,我們可以直接把整個包復制到客戶端工程中去。
- RemoteCallbackList是系統專門提供用于刪除跨進程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因為所有AIDL接口都繼承自android.os.IInterface接口。
- 需注意AIDL客戶端發起RPC過程的時候,客戶端的線程會掛起,如果是UI線程發起的RPC過程,如果服務端處理事件過長,就會導致ANR。
- 使用ContentProvider
ContentProvider是Android專門用于不同應用之間進行數據共享的方式,天生適合跨進程通訊,底層同樣采用Binder實現。
ContentProvider主要以表格的形式來組織數據,可以包含多個表;ContentProvider支持普通文件,甚至可以采用內存中得一個對象來進行數據存儲。
通過ContentProvider的notifyChange方法來通知外界當前ContentProvider中的數據已經發生改變。 - 使用Socket
Socket也被稱為“套接字”,是網絡通訊中得概念,分為流式套接字和用戶數據報套接字兩種,分別對應網絡的傳輸控制層中得TCP和UDP協議。
以上內容詳細的代碼示例請參照原書和隨書源碼喲。
2.5 Binder連接池
- 當項目越來越龐大后,需要使用到的AIDL接口文件也越來越多,但我們不能有多少個AIDL就添加多少個Service,Service是四大組件之一,是一種系統資源,太多的Service會讓我們的App看起來很重量級;我們應該把所有AIDL放在一個Service中去管理。 這時候不同業務模塊之間是不能有耦合的,所有實現細節需要單獨來開,然后向服務端提供一個queryBInder接口,這個接口根據業務模塊的特征來返回Binder對象給它們,不同的業務模塊拿到所需的Binder對象給它們,不同的業務模塊拿到所需的Binder對象后就可以進行遠程方法調用了;由此可見Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service中去執行。
- 當新業務模塊加入新的AIDL,那么在它實現自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,給自己添加一個新的binderCode并返回相對應的Binder對象即可,不需要添加新的Service。作者建議在AIDL開發過程中引入BinderPool機制。 詳細源碼請參考原書和隨書源碼。
2.6 選用合適的IPC方式
IPC方式的優缺點和適用場景.PNG