2.1 Android IPC簡(jiǎn)介
- Linux的IPC通信機(jī)制:
命名通道、共享內(nèi)存凛篙、信號(hào)量等來(lái)進(jìn)行IPC。 - Android系統(tǒng)使用Binder機(jī)制來(lái)實(shí)現(xiàn)IPC,也可以使用Socket實(shí)現(xiàn)任意兩個(gè)終端之間的通信润讥。
2.2 Android中的多進(jìn)程模式
-
通過(guò)給四大組件指定android:process屬性就可以開(kāi)啟多進(jìn)程模式。
- 默認(rèn)進(jìn)程的進(jìn)程名是包名packageName
- 進(jìn)程名以:開(kāi)頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程盘寡,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中楚殿,
- 進(jìn)程名不以:開(kāi)頭的進(jìn)程屬于全局進(jìn)程,其他應(yīng)用通過(guò)ShareUID方法可以和它跑在同一個(gè)進(jìn)程中竿痰。
- 兩個(gè)應(yīng)用必須有相同的ShareUID而且簽名相同脆粥,才可以泡在同一個(gè)進(jìn)程。
android系統(tǒng)會(huì)為每個(gè)進(jìn)程分配一個(gè)獨(dú)立的虛擬機(jī)影涉,不同的虛擬機(jī)在內(nèi)存分配上有不同的地址空間变隔,所以不同的虛擬機(jī)中訪問(wèn)同一個(gè)類(lèi)的對(duì)象會(huì)產(chǎn)生多個(gè)副本。
-
多進(jìn)程會(huì)造成幾個(gè)問(wèn)題:
- 靜態(tài)成員和單例模式失效
- 線程同步機(jī)制失效
- sharedPreferences失效(sharedPreferences不支持兩個(gè)進(jìn)程同時(shí)去執(zhí)行寫(xiě)操作)
- Application會(huì)多次創(chuàng)建蟹倾。(系統(tǒng)要在創(chuàng)建新的進(jìn)程的同時(shí)分配獨(dú)立的虛擬機(jī)匣缘,應(yīng)用會(huì)重新啟動(dòng)一次,也就會(huì)創(chuàng)建新的Application鲜棠。)
2.3 IPC基礎(chǔ)概念
2.3.1 Serializable接口
- serialVersionUId是一串long型數(shù)字肌厨,主要是用來(lái)輔助序列化和反序列化的,原則上序列化后的數(shù)據(jù)中的serialVersionUId只有和當(dāng)前類(lèi)的serialVersionUId相同才能夠正常地被反序列化豁陆。
- 靜態(tài)成員變量屬于類(lèi)不屬于對(duì)象柑爸,因此不會(huì)參與序列化過(guò)程
- transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過(guò)程
2.3.2 Parcelable接口
Parcelable接口內(nèi)部包裝了可序列化的數(shù)據(jù),可以在Binder中自由傳輸盒音,Parcelable主要用在內(nèi)存序列化上表鳍,可以直接序列化的有Intent馅而、Bundle、Bitmap以及List和Map等等
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;
}
//“內(nèi)容描述”进胯,如果含有文件描述符返回1用爪,否則返回0,幾乎所有情況下都是返回0
public int describeContents() {
return 0;
}
//實(shí)現(xiàn)序列化操作胁镐,flags標(biāo)識(shí)只有0和1偎血,1表示標(biāo)識(shí)當(dāng)前對(duì)象需要作為返回值返回,不能立即釋放資源盯漂,幾乎所有情況都為0
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
//實(shí)現(xiàn)反序列化操作
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
//從序列化后的對(duì)象中創(chuàng)建原始對(duì)象
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {//創(chuàng)建指定長(zhǎng)度的原始對(duì)象數(shù)組
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
}
注意:
如果有一個(gè)實(shí)現(xiàn)了Parcelable接口的User類(lèi)颇玷,Book對(duì)象是User對(duì)象的成員。那么在User的反序列化時(shí)就缆,需要傳遞當(dāng)前線程的上下類(lèi)加載器
private User(Parcel in){
book = in.readParcelable(Thread.currentThread().getContextClassloader());
}
2.3.3 Binder接口
- 概念:
- Binder是Android中的一個(gè)類(lèi)帖渠,它實(shí)現(xiàn)了IBinder接口。
- 從IPC角度看竭宰,Binder是Android中一種跨進(jìn)程通信的方式空郊;
- Binder還可以理解為虛擬的物理設(shè)備,它的設(shè)備驅(qū)動(dòng)是/dev/binder切揭;
- 從Framework層角度看狞甚,Binder是ServiceManager連接各種Manager和相應(yīng)的ManagerService的橋梁;
- 從Android應(yīng)用層來(lái)說(shuō)廓旬,Binder是客戶(hù)端和服務(wù)端進(jìn)行通信的媒介哼审,當(dāng)bindService的時(shí)候,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對(duì)象孕豹,通過(guò)這個(gè)Binder對(duì)象涩盾,客戶(hù)端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)。
- aidl生成的java接口解析
- 聲明了幾個(gè)接口方法
- 聲明一個(gè)內(nèi)部類(lèi)Stub励背。Stub是一個(gè)Binder類(lèi)春霍。當(dāng)客戶(hù)端和服務(wù)端在一個(gè)進(jìn)程內(nèi)時(shí),方法調(diào)用不會(huì)走跨進(jìn)程的transact叶眉;當(dāng)客戶(hù)端和服務(wù)端處于不同進(jìn)程终畅,方法調(diào)用要走跨進(jìn)程的transact,這個(gè)邏輯由Stub的內(nèi)部代理類(lèi)Proxy來(lái)完成竟闪。
- Stub類(lèi)的方法定義:
- asInterface(android.os.IBinder obj):將服務(wù)端的Binder對(duì)象轉(zhuǎn)換成客戶(hù)端所需的AIDL接口類(lèi)型的對(duì)象离福,這種轉(zhuǎn)換過(guò)程是區(qū)分進(jìn)程的,如果客戶(hù)端和服務(wù)端是在同一個(gè)進(jìn)程中炼蛤,那么這個(gè)方法返回的是服務(wù)端的Stub對(duì)象本身妖爷,否則返回的是系統(tǒng)封裝的Stub.Proxy對(duì)象。
- asBinder:返回當(dāng)前Binder對(duì)象。
- onTransact:這個(gè)方法運(yùn)行在服務(wù)端中的Binder線程池中絮识,當(dāng)客戶(hù)端發(fā)起跨進(jìn)程請(qǐng)求(mRemote.transact)時(shí)绿聘,遠(yuǎn)程請(qǐng)求會(huì)通過(guò)系統(tǒng)底層封裝后交由此方法來(lái)處理。
- 聲明了幾個(gè)標(biāo)識(shí)接口方法的整型id(static)次舌。這幾個(gè)id用于標(biāo)識(shí)在transact過(guò)程中客戶(hù)端所請(qǐng)求的到底是哪個(gè)方法熄攘。
- 聲明DESCRIPTIOR: Binder唯一標(biāo)識(shí),一般用類(lèi)名表示
- Proxy的方法定義:
- asBinder:返回服務(wù)端的Binder對(duì)象
- getInterfaceDescriptor: 返回DESCRIPTIOR
- Proxy#XXX接口方法:所有的接口方法都是調(diào)用mRemote.transact(id, data_parcel, reply_parcel, 0)彼念。流程為:客戶(hù)端發(fā)送RPC調(diào)用請(qǐng)求挪圾,同時(shí)客戶(hù)端當(dāng)前線程掛起;然后服務(wù)端onTransact方法調(diào)用逐沙,直到RPC過(guò)程返回后哲思,當(dāng)前客戶(hù)端線程繼續(xù)執(zhí)行,從reply_parcel中取出RPC過(guò)程中的返回結(jié)果吩案。
- linkToDeath/unlinkToDeath
Binder運(yùn)行在服務(wù)端棚赔,如果由于某種原因服務(wù)端異常終止了的話會(huì)導(dǎo)致客戶(hù)端的遠(yuǎn)程調(diào)用失敗,所以Binder提供了兩個(gè)配對(duì)的方法linkToDeath和unlinkToDeath徘郭。
如何給Binder設(shè)置死亡代理:
1. 聲明一個(gè)DeathRecipient對(duì)象靠益,DeathRecipient是一個(gè)接口,其內(nèi)部只有一個(gè)方法bindeDied残揉,實(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. 在客戶(hù)端綁定遠(yuǎn)程服務(wù)成功之后,給binder設(shè)置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
2.4 Android中的IPC方式
2.4.1. 使用Bundle:
Bundle實(shí)現(xiàn)了Parcelable接口冲甘,Activity、Service和Receiver都支持在Intent中傳遞Bundle數(shù)據(jù)途样。
2.4.2. 使用文件共享:
這種方式簡(jiǎn)單江醇,適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信,并且要妥善處理并發(fā)讀寫(xiě)的問(wèn)題何暇。 SharedPreferences是一個(gè)特例陶夜,雖然它也是文件的一種,但是由于系統(tǒng)對(duì)它的讀寫(xiě)有一定的緩存策略裆站,即在內(nèi)存中會(huì)有一份SharedPreferences文件的緩存条辟,因此在多進(jìn)程模式下,系統(tǒng)對(duì)它的讀寫(xiě)就變得不可靠宏胯,當(dāng)面對(duì)高并發(fā)讀寫(xiě)訪問(wèn)的時(shí)候羽嫡,有很大幾率會(huì)丟失數(shù)據(jù),因此肩袍,不建議在進(jìn)程間通信中使用SharedPreferences杭棵。
2.4.3. 使用Messenger:Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)就是AIDL氛赐。Messenger是以串行的方式處理請(qǐng)求的魂爪,即服務(wù)端只能一個(gè)個(gè)處理先舷,不存在并發(fā)執(zhí)行的情形。
Messenger中進(jìn)行數(shù)據(jù)傳輸必須將數(shù)據(jù)放入Message中滓侍,一般情況下
- 服務(wù)端會(huì)建一個(gè)Hander和Messenger對(duì)應(yīng)蒋川,并通過(guò)mMessenger.getBinder()暴露Binder接口給客戶(hù)端
- 客戶(hù)端通過(guò)new Messenger(binder)得到Messenger,然后通過(guò)mMessenger.send(mMessage)來(lái)實(shí)現(xiàn)數(shù)據(jù)傳輸
- 服務(wù)端得到數(shù)據(jù)后撩笆,Handler做處理
- 如客戶(hù)端需要反饋結(jié)果捺球,那需要客戶(hù)端也建立Messenger對(duì)應(yīng)的Hander。而服務(wù)端在Handler處理時(shí)浇衬,通過(guò)mMessage.replyTo得到客戶(hù)端的Messenger懒构,然后通過(guò)mMessenger.send(mReplyMessage)將result發(fā)給客戶(hù)端。最后客戶(hù)端在Handler中得到result耘擂。
2.4.4 使用AIDL
大致流程:首先建一個(gè)Service和一個(gè)AIDL接口胆剧,接著創(chuàng)建一個(gè)類(lèi)繼承自AIDL接口中的Stub類(lèi)并實(shí)現(xiàn)Stub類(lèi)中的抽象方法,在Service的onBind方法中返回這個(gè)類(lèi)的對(duì)象醉冤,然后客戶(hù)端就可以綁定服務(wù)端Service秩霍,建立連接后就可以訪問(wèn)遠(yuǎn)程服務(wù)端的方法了。
注意:
- AIDL的包結(jié)構(gòu)在服務(wù)端和客戶(hù)端要保持一致蚁阳,否則會(huì)運(yùn)行出錯(cuò)
- AIDL方法是在服務(wù)端的Binder線程池中執(zhí)行的铃绒,因此當(dāng)多個(gè)客戶(hù)端同時(shí)運(yùn)行時(shí),會(huì)出現(xiàn)多個(gè)線程同時(shí)訪問(wèn)的情形螺捐。因此AIDL方法要處理好線程同步颠悬。比如List,建議使用CopyOnWriteArrayList來(lái)進(jìn)行自動(dòng)的線程同步定血,類(lèi)似的還有ConcurrentHashMap赔癌。當(dāng)AIDL傳遞時(shí),Binder會(huì)按照List和Map的規(guī)范最形成ArrayList和Hashmap傳遞給客戶(hù)端澜沟。
- 觀察者模式:客戶(hù)端注冊(cè)一個(gè)listener到服務(wù)端灾票,用于觀察服務(wù)端的狀態(tài)。
- 需要加一個(gè)listener的aidl文件茫虽,并且要在原本的aidl文件中加registerlistener/unRegisterListener的接口
- 客戶(hù)端registerListener
- 服務(wù)端的listener保存要使用RemoteCallbackList
- 在需要的時(shí)機(jī)刊苍,服務(wù)端運(yùn)行
final int N = mRemoteCallbackList.beginBroadcast(); for (int i = 0; i < N; i++) { IxxxListener l = mRemoteCallbackList.getBroadcastItem(i); if (l != null) { try { l.onXxxed(); } catch (RemoteException e) { e.printStackTrace(); } } } mRemoteCallbackList.finishBroadcast();
RemoteCallbackList是系統(tǒng)專(zhuān)門(mén)提供的用于刪除跨進(jìn)程Listener的接口。RemoteCallbackList是一個(gè)泛型濒析,支持管理任意的AIDL接口正什,因?yàn)樗械腁IDL接口都繼承自IInterface接口。
- 遠(yuǎn)程調(diào)用的方法是運(yùn)行在Binder線程池号杏,同時(shí)客戶(hù)端線會(huì)掛起埠忘,如果遠(yuǎn)程調(diào)用耗時(shí),則會(huì)導(dǎo)致客戶(hù)端線程阻塞,此時(shí)要避免UI線程進(jìn)遠(yuǎn)程調(diào)用莹妒,尤其是onServiceConnected/onServiceDisConnected名船。
- 服務(wù)端方法是運(yùn)行在Binder線程池中的,所以原本就可以執(zhí)行大量耗時(shí)操作旨怠,因此不需要開(kāi)新線程進(jìn)行異步任務(wù)渠驼。
- 當(dāng)服務(wù)端調(diào)用客戶(hù)端listener中的方法時(shí),調(diào)用的方法是運(yùn)行在客戶(hù)端的Binder線程池鉴腻,因此如果此調(diào)用耗時(shí)迷扇,會(huì)導(dǎo)致服務(wù)端線程阻塞無(wú)法響應(yīng)。
- Binder是可能意外死亡的爽哎,往往是因?yàn)榉?wù)端進(jìn)程意外終止了蜓席。重練遠(yuǎn)程服務(wù)的方法:
- 死亡代理:DeathRecipient監(jiān)聽(tīng),在binderDied中重連
- onServiceDisconnected中重連课锌。
2.4.5 使用ContentProvider
- ContentProvider主要以表格的形式來(lái)組織數(shù)據(jù)厨内,并且可以包含多個(gè)表;
- ContentProvider還支持文件數(shù)據(jù)渺贤,比如圖片雏胃、視頻等,系統(tǒng)提供的MediaStore就是文件類(lèi)型的ContentProvider志鞍;
- ContentProvider對(duì)底層的數(shù)據(jù)存儲(chǔ)方式?jīng)]有任何要求瞭亮,可以是SQLite、文件固棚,甚至是內(nèi)存中的一個(gè)對(duì)象都行统翩;
- 要觀察ContentProvider中的數(shù)據(jù)變化情況,可以通過(guò)ContentResolver的registerContentObserver方法來(lái)注冊(cè)觀察者此洲;
- 跨進(jìn)程訪問(wèn)ContentProvider的數(shù)據(jù)厂汗,只需要通過(guò)ContentResolver的query、update黍翎、insert、delete方法艳丛。
- sqliteDatabase內(nèi)部對(duì)數(shù)據(jù)庫(kù)的操作是有同步處理的匣掸。
2.4.6使用Socket
2.5 Binder連接池
將所有的AIDL放在同一個(gè)Service中去管理是一種比較好的方式。
2.5.1 Binder連接池工作機(jī)制:
- 每個(gè)業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實(shí)現(xiàn)此接口氮双,這個(gè)時(shí)候不同業(yè)務(wù)模塊之間是不能有耦合的碰酝,所有實(shí)現(xiàn)細(xì)節(jié)我們要單獨(dú)開(kāi)來(lái),然后向服務(wù)端提供自己的唯一標(biāo)識(shí)和其對(duì)應(yīng)的Binder對(duì)象戴差;
- 對(duì)于服務(wù)端來(lái)說(shuō)送爸,只需要一個(gè)Service,服務(wù)端提供一個(gè)queryBinder接口,這個(gè)接口能夠根據(jù)業(yè)務(wù)模塊的特征來(lái)返回相應(yīng)的Binder對(duì)象給它們袭厂,不同的業(yè)務(wù)模塊拿到所需的Binder對(duì)象后就可以進(jìn)行遠(yuǎn)程方法調(diào)用了墨吓。
2.5.2 Binder連接池的主要作用
- 將每個(gè)業(yè)務(wù)模塊的Binder請(qǐng)求統(tǒng)一轉(zhuǎn)發(fā)到遠(yuǎn)程Service去執(zhí)行,從而避免了重復(fù)創(chuàng)建Service的過(guò)程纹磺。
- 建議在AIDL開(kāi)發(fā)工作中引入BinderPool機(jī)制
作者的BinderPool源碼