注:此篇筆記只記錄重難點(diǎn),對(duì)于基礎(chǔ)和詳細(xì)內(nèi)容請(qǐng)自行學(xué)習(xí)《Android開發(fā)藝術(shù)探索》
2.1 IPC簡介
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)簡單,只需要在類的聲明中指定一個(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ì)去檢測文件中的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)建指定長度的原始對(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中的序列化接口掸绞,其使用起來簡單但是開銷很大,序列化和反序列化需要大量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、 使用文件共享
這種方式簡單联予,適合在對(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方法來注冊觀察者;
?
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宫补。
?