注:此篇筆記只記錄重難點(diǎn),對(duì)于基礎(chǔ)和詳細(xì)內(nèi)容請(qǐng)自行學(xué)習(xí)《Android開發(fā)藝術(shù)探索》
2.1 IPC簡(jiǎn)介
Inter-Process Communication的縮寫。含義為進(jìn)程間通信或跨進(jìn)程通信,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程。
進(jìn)程和線程的區(qū)別
- 按照操作系統(tǒng)的描述,線程是CPU調(diào)度的最小單元,同時(shí)線程是一種有限的系統(tǒng)資源。
- 進(jìn)程一般指一個(gè)執(zhí)行單元,在PC和移動(dòng)設(shè)備上指一個(gè)程序或者一個(gè)應(yīng)用。一個(gè)進(jìn)程可以包含多個(gè)線程,因此進(jìn)程和線程是包含與被包含的關(guān)系。
多進(jìn)程分為兩種
- 第一種情況是一個(gè)應(yīng)用因?yàn)槟承┰蜃陨硇枰捎枚嗑€程模式來實(shí)現(xiàn)。
- 另一種情況是當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù)
2.2 Android中的多進(jìn)程模式
2.2.1 開啟多進(jìn)程
通過給四大組件指定android:process屬性,我們可以開啟多線程模式
- 進(jìn)程名以":"開頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和它跑在同一進(jìn)程,而進(jìn)程名不以":"開頭的進(jìn)程屬于全局進(jìn)程,其他應(yīng)用通過ShareUID方式可以和它跑在同一個(gè)進(jìn)程中。
- Android系統(tǒng)會(huì)為每個(gè)應(yīng)用分配一個(gè)唯一的UID,具有相同UID的應(yīng)用才能共享數(shù)據(jù),兩個(gè)應(yīng)用通過ShareUID跑在同一個(gè)進(jìn)程中是有要求的,需要這兩個(gè)應(yīng)用有相同的ShareUID并且簽名相同才可以。在這種情況下,它們可以互相訪問對(duì)方的私有數(shù)據(jù),比如data目錄、組件信息等,不管它們是否跑在同一個(gè)進(jìn)程中。當(dāng)然如果它們跑在同一個(gè)進(jìn)程中,那么除了能共享data目錄、組件信息,還可以共享內(nèi)存數(shù)據(jù),或者說它們看起來就像是一個(gè)應(yīng)用的兩個(gè)部分。
2.2.2 多進(jìn)程模式的運(yùn)行機(jī)制
- Android為每一個(gè)應(yīng)用分配了一個(gè)獨(dú)立的虛擬機(jī),或者說為每個(gè)進(jìn)程都分配了一個(gè)獨(dú)立的虛擬機(jī),不同的虛擬機(jī)在不同的內(nèi)存分配上有不同的地址空間,這就導(dǎo)致在不同的虛擬機(jī)中訪問同一個(gè)類的對(duì)象會(huì)產(chǎn)生多份副本。
- 所有運(yùn)行在不同進(jìn)程中的四大組件,只要它們之間需要通過內(nèi)存來共享數(shù)據(jù),都會(huì)共享失敗。
一般來說,使用多進(jìn)程會(huì)造成如下幾個(gè)方面的問題:
- 靜態(tài)成員和單例模式完全失效
- 線程同步機(jī)制完全失效
不管是鎖對(duì)象還是鎖全局類都無法保證線程同步,因?yàn)椴煌M(jìn)程鎖的不是同一個(gè)對(duì)象
- SharedPreference的可靠性下降
SharedPreferences不支持兩個(gè)進(jìn)程同時(shí)去執(zhí)行寫操作,否則會(huì)導(dǎo)致一定幾率的數(shù)據(jù)丟失,這時(shí)因?yàn)镾haredPreferences底層是通過讀寫XML文件來實(shí)現(xiàn)的,并發(fā)寫顯然是可能出問題的,甚至并發(fā)讀寫都有可能發(fā)生問題
- Application會(huì)多次創(chuàng)建
運(yùn)行在同一個(gè)進(jìn)程中的組件是屬于同一個(gè)虛擬機(jī)和同一個(gè)Application的。同理,運(yùn)行在不同進(jìn)程中的組件是屬于兩個(gè)不同的虛擬機(jī)和Application的。
IPC基礎(chǔ)概念介紹
2.3.1 Serializable接口
是Java所提供的一個(gè)序列化接口,它是一個(gè)空接口,為對(duì)象提供標(biāo)準(zhǔn)的序列化和反序列化操作。使用Serializable來實(shí)現(xiàn)序列化相當(dāng)簡(jiǎn)單,只需要在類的聲明中指定一個(gè)類似下面的標(biāo)識(shí)即可自動(dòng)實(shí)現(xiàn)默認(rèn)的序列化過程。
private static final long serialVersionUID = 8711368828010083044L
通過Serializable方來實(shí)現(xiàn)對(duì)象的序列化,如下代碼:
//序列化過程
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();
原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當(dāng)前類的serialVersionUID相同時(shí)才能夠正常的被反序列化。
serialVersionUID的詳細(xì)工作機(jī)制是這樣的:序列化的時(shí)候系統(tǒng)會(huì)把當(dāng)前類的serialVersionUID寫入序列化的文件中(也可能是其他的中介),當(dāng)反序列化的時(shí)候系統(tǒng)會(huì)去檢測(cè)文件中的serialVersionUID,看它是否和當(dāng)前類的serialVersionUID一致,如果一致就說明序列化的類的版本和當(dāng)前類的版本是相同的,這個(gè)時(shí)候可以成功反序列化,否則就說明當(dāng)前類和序列化的類相比發(fā)生了某些變換。
給serialVersionUID制定為1L或者采用Eclipse根據(jù)當(dāng)前類結(jié)構(gòu)去生成的hash值,這兩者并沒有本質(zhì)區(qū)別。
- 靜態(tài)成員變量屬于類不屬于對(duì)象,所以不會(huì)參與序列化過程
- 其次用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過程
2.3.2 Parcelable接口
Parcelable也是一個(gè)接口,只要實(shí)現(xiàn)這個(gè)接口,一個(gè)類的對(duì)象就可以實(shí)現(xiàn)序列化并可以通過Intent的Binder傳遞
Parcelable的方法說明:
方法 | 功能 | 標(biāo)記位 |
---|---|---|
createFromParcel(Parcel in) | 從序列化的對(duì)象中創(chuàng)建原始對(duì)象 | |
newArray[int size] | 創(chuàng)建指定長(zhǎng)度的原始對(duì)象數(shù)組 | |
User(Parcel in) | 從序列化的對(duì)象中創(chuàng)建原始對(duì)象 | |
write ToParcel(Parcel out, int flags) | 將當(dāng)前對(duì)象寫入序列化結(jié)構(gòu)中,其中flags標(biāo)識(shí)有兩種值0或1(參見右側(cè)標(biāo)記位)。為1時(shí)標(biāo)識(shí)當(dāng)前對(duì)象需要作為返回值返回,不能立即釋放資源,幾乎所有情況都為0 | PARCELABLE_WRITE_RETURN_VALUE |
describeContents | 返回當(dāng)前對(duì)象的內(nèi)容描述。如果含有文件描述符,返回1(參見右側(cè)標(biāo)記位),否則返回0,幾乎所有的情況都返回0 | CONTENTS_FILE_DESCRIPTOR |
-
具體如下:
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); } }
系統(tǒng)已經(jīng)為我們提供了許多實(shí)現(xiàn)了Parcelable接口的類,它們都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同時(shí)List和Map也可以序列化,前提是它們里面的每個(gè)元素都是可序列化的。
如何選取
- Serializable是Java中的序列化接口,其使用起來簡(jiǎn)單但是開銷很大,序列化和反序列化需要大量I/O操作。而Parceleble是Android中的序列化方式,因此更適合在Android平臺(tái)上,缺點(diǎn)是麻煩,但是效率高,這是Android推薦的序列化方式,所以我們要首選Parcelable。
- Parcelable主要用在內(nèi)存序列化上,通過Parcelable將對(duì)象序列化到存儲(chǔ)設(shè)備中或者將對(duì)象序列化之后通過網(wǎng)絡(luò)傳輸,但是過程稍顯復(fù)雜,因此在這兩種情況下建議大家使用Serializable。
2.3.3 Binder
- 繼承了IBinder接口
- Binder是一種跨進(jìn)程通信方式
- 是ServiceManager連接各種Manager(ActivityManager,WindowManager等)和相應(yīng)ManagerService的橋梁
- 從Android應(yīng)用層來說,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介,當(dāng)bindService的時(shí)候,服務(wù)器會(huì)返回一個(gè)包含了服務(wù)器端業(yè)務(wù)調(diào)用的Binder對(duì)象,通過這個(gè)Binder對(duì)象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者是數(shù)據(jù),這里的服務(wù)包含了普通服務(wù)和基于AIDL的服務(wù)
aidl工具根據(jù)aidl文件自動(dòng)生成的java接口解析:
- 首先,它聲明了幾個(gè)接口方法,同時(shí)還聲明了幾個(gè)整型的id用于標(biāo)識(shí)這些方法,id用于標(biāo)識(shí)在transact過程中客戶端所請(qǐng)求的到底是哪個(gè)方法;
- 接著,它聲明了一個(gè)內(nèi)部類Stub,這個(gè)Stub就是一個(gè)Binder類,當(dāng)客戶端和服務(wù)端都位于同一個(gè)進(jìn)程時(shí),方法調(diào)用不會(huì)走跨進(jìn)程的transact過程,而當(dāng)兩者位于不同進(jìn)程時(shí),方法調(diào)用需要走transact過程,這個(gè)邏輯由Stub內(nèi)部的代理類Proxy來完成。
所以,這個(gè)接口的核心就是它的內(nèi)部類Stub和Stub內(nèi)部的代理類Proxy。 下面分析其中的方法:
- asInterface(android.os.IBinder obj):用于將服務(wù)器端的Binder對(duì)象轉(zhuǎn)化成客戶端所需的AIDL接口類型的對(duì)象,這種轉(zhuǎn)換過程是區(qū)分進(jìn)程的,如果客戶端和服務(wù)端是在同一進(jìn)程中,那么這個(gè)方法返回的是服務(wù)端的Stub對(duì)象本身,否則返回的是系統(tǒng)封裝的Stub.Proxy對(duì)象。
- asBinder:返回當(dāng)前Binder對(duì)象
- onTransact:這個(gè)方法運(yùn)行在服務(wù)端中的Binder線程池中,當(dāng)客戶端發(fā)起跨進(jìn)程請(qǐng)求時(shí),遠(yuǎn)程請(qǐng)求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理。
這個(gè)方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)
服務(wù)端通過code可以知道客戶端請(qǐng)求的目標(biāo)方法,接著從data中取出所需的參數(shù),然后執(zhí)行目標(biāo)方法,執(zhí)行完畢之后,將結(jié)果寫入到reply中。如果此方法返回false,說明客戶端的請(qǐng)求失敗,利用這個(gè)特性可以做權(quán)限驗(yàn)證(即驗(yàn)證是否有權(quán)限調(diào)用該服務(wù))。 - Proxy#[Method]:代理類中的接口方法,這些方法運(yùn)行在客戶端,當(dāng)客戶端遠(yuǎn)程調(diào)用此方法時(shí),它的內(nèi)部實(shí)現(xiàn)是:首先創(chuàng)建該方法所需要的參數(shù),然后把方法的參數(shù)信息寫入到_data中,接著調(diào)用transact方法來發(fā)起RPC請(qǐng)求,同時(shí)當(dāng)前線程掛起;然后服務(wù)端的onTransact方法會(huì)被調(diào)用,直到RPC過程返回后,當(dāng)前線程繼續(xù)執(zhí)行,并從_reply中取出RPC過程的返回結(jié)果,最后返回_reply中的數(shù)據(jù)。
首先,當(dāng)客戶端發(fā)起遠(yuǎn)程請(qǐng)求時(shí),由于當(dāng)前線程會(huì)被掛起直至服務(wù)端進(jìn)程返回?cái)?shù)據(jù),所以如果一個(gè)遠(yuǎn)程方法是很耗時(shí)的,那么不能在UI線程發(fā)起此遠(yuǎn)程請(qǐng)求;
其次,由于服務(wù)端的Binder方法運(yùn)行在Binder的線程池中,所以不管Binder是否耗時(shí)都應(yīng)該采用同步的方式去實(shí)現(xiàn),因?yàn)樗呀?jīng)運(yùn)行在一個(gè)線程中了。
Binder兩種重要的方法 linkToDeath 和 unlinkToDeath
Binder運(yùn)行在服務(wù)端,如果由于某種服務(wù)端異常終止了的話會(huì)導(dǎo)致客戶端的遠(yuǎn)程調(diào)用失敗、所以Binder提供了兩個(gè)配對(duì)的方法linkToDeath和unlinkToDeath,通過linkToDeath方法可以給Binder設(shè)置一個(gè)死亡代理,當(dāng)Binder死亡的時(shí)候客戶端就會(huì)收到通知,然后就可以重新發(fā)起連接從而恢復(fù)連接了。
如何給Binder設(shè)置死亡代理
1、聲明一個(gè)DeathRecipient對(duì)象、DeathRecipient是一個(gè)接口,其內(nèi)部只有一個(gè)方法bindDied,實(shí)現(xiàn)這個(gè)方法就可以在Binder死亡的時(shí)候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mRemoteBookManager == null) return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:這里重新綁定遠(yuǎn)程Service
}
};
2、在客戶端綁定遠(yuǎn)程服務(wù)成功之后,給binder設(shè)置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
2.4 Android的IPC方式
1、 使用Bundle
Bundle實(shí)現(xiàn)了Parcelable接口,Activity、Service和Receiver都支持在Intent中傳遞Bundle數(shù)據(jù)
2、 使用文件共享
這種方式簡(jiǎn)單,適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信,并且要妥善處理并發(fā)讀寫的問題,SharedPreferences是一個(gè)特例,雖然它也是文件的一種,但是由于系統(tǒng)對(duì)它的讀寫有一定的緩存策略,即在內(nèi)存中會(huì)有一份SharedPreferences文件的緩存,因此在多進(jìn)程模式下、系統(tǒng)對(duì)它的讀寫就變的不可靠,當(dāng)面對(duì)高并發(fā)讀寫訪問的時(shí)候,有很大幾率會(huì)丟失,因此,不建議在進(jìn)程間通信中使用SharedPreferences。
3、 使用Messenger
Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)就是AIDL。Messenger是以串行的方式處理請(qǐng)求的,即服務(wù)端只能一個(gè)個(gè)處理,不存在并發(fā)執(zhí)行的情形。
4、 使用AIDL
大致流程:首先建一個(gè)Service和一個(gè)AIDL接口,接著創(chuàng)建一個(gè)類繼承自AIDL接口中的Stub類中的抽象方法,在Service的onBind方法中返回這個(gè)類的對(duì)象,然后客戶端就可以綁定服務(wù)端Service,建立連接后就可以訪問遠(yuǎn)程服務(wù)端的方法了。
AIDL支持的數(shù)據(jù)類型:基本數(shù)據(jù)類型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL;
某些類即使和AIDL文件在同一個(gè)包中也要顯式import進(jìn)來;
AIDL中除了基本數(shù)據(jù)類,其他類型的參數(shù)都要標(biāo)上方向:in、out或者inout;
AIDL接口中支持方法,不支持聲明靜態(tài)變量;
為了方便AIDL的開發(fā),建議把所有和AIDL相關(guān)的類和文件全部放入同一個(gè)包中,這樣做的好處是,當(dāng)客戶端是另一個(gè)應(yīng)用的時(shí)候,可以直接把整個(gè)包復(fù)制到客戶端工程中。
-
RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進(jìn)程Listener的接口。RemoteCallbackList是一個(gè)泛型,支持管理任意的AIDL接口,因?yàn)樗械腁IDL接口都繼承自IInterface接口。
?
5、使用ContentProvider
ContentProvider主要以表格的形式來組織數(shù)據(jù),并且可以包含多個(gè)表;
ContentProvider還支持文件數(shù)據(jù),比如圖片、視頻等,系統(tǒng)提供的MediaStore就是文件類型的ContentProvider;
ContentProvider對(duì)底層的數(shù)據(jù)存儲(chǔ)方式?jīng)]有任何要求,可以是SQLite、文件,甚至是內(nèi)存中的一個(gè)對(duì)象都行;
-
要觀察ContentProvider中的數(shù)據(jù)變化情況,可以通過ContentResolver的registerContentObserver方法來注冊(cè)觀察者;
?
6、使用Socket
套接字,分為流式套接字和用戶數(shù)據(jù)報(bào)套接字兩種,分別對(duì)應(yīng)于網(wǎng)絡(luò)的傳輸控制層中TCP和UDP協(xié)議。
- TCP協(xié)議是面向連接的協(xié)議,提供穩(wěn)定的雙向通信功能,TCP連接的建立需要經(jīng)過"三次握手"才能完成,為了提供穩(wěn)定的數(shù)據(jù)傳輸功能,其本身提供了超時(shí)重傳功能,因此具有很高的穩(wěn)定性
- UDP是無連接的,提供不穩(wěn)定的單向通信功能,當(dāng)然UDP也可以實(shí)現(xiàn)雙向通信功能,在性能上,UDP具有更好的效率,其缺點(diǎn)是不保證數(shù)據(jù)能夠正確傳輸,尤其是在網(wǎng)絡(luò)擁塞的情況下。
Binder連接池
當(dāng)項(xiàng)目規(guī)模很大的時(shí)候,創(chuàng)建很多個(gè)Service是不對(duì)的做法,因?yàn)閟ervice是系統(tǒng)資源,太多的service會(huì)使得應(yīng)用看起來很重,所以最好是將所有的AIDL放在同一個(gè)Service中去管理。整個(gè)工作機(jī)制是:每個(gè)業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實(shí)現(xiàn)此接口,這個(gè)時(shí)候不同業(yè)務(wù)模塊之間是不能有耦合的,所有實(shí)現(xiàn)細(xì)節(jié)我們要單獨(dú)開來,然后向服務(wù)端提供自己的唯一標(biāo)識(shí)和其對(duì)應(yīng)的Binder對(duì)象;對(duì)于服務(wù)端來說,只需要一個(gè)Service,服務(wù)端提供一個(gè)queryBinder接口,這個(gè)接口能夠根據(jù)業(yè)務(wù)模塊的特征來返回相應(yīng)的Binder對(duì)象給它們,不同的業(yè)務(wù)模塊拿到所需的Binder對(duì)象后就可以進(jìn)行遠(yuǎn)程方法調(diào)用了。
Binder連接池的主要作用就是將每個(gè)業(yè)務(wù)模塊的Binder請(qǐng)求統(tǒng)一轉(zhuǎn)發(fā)到遠(yuǎn)程Service去執(zhí)行,從而避免了重復(fù)創(chuàng)建Service的過程。-
建議在AIDL開發(fā)工作中引入BinderPool機(jī)制。當(dāng)新業(yè)務(wù)模塊加入新的AIDL,那么在它實(shí)現(xiàn)自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,給自己添加一個(gè)新的binderCode并返回相對(duì)應(yīng)的Binder對(duì)象即可,不需要添加新的Service。
?