導(dǎo)語
對(duì)Android中的IPC機(jī)制和多進(jìn)程開發(fā)模式有深入的理解。
主要內(nèi)容
- Android IPC 簡(jiǎn)介
- Android中的多進(jìn)程模式
- IPC基礎(chǔ)概念介紹
- Android中的IPC方式
- Binder連接池
- 選用合適的IPC方式
具體內(nèi)容
Android IPC 簡(jiǎn)介
IPC即Inter-Process Communication狈定,含義為進(jìn)程間通信或者跨進(jìn)程通信宫静,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程读串。
線程是CPU調(diào)度的最小單元横辆,是一種有限的系統(tǒng)資源。進(jìn)程一般指一個(gè)執(zhí)行單元,在PC和移動(dòng)設(shè)備上是指一個(gè)程序或者應(yīng)用性锭。進(jìn)程與線程是包含與被包含的關(guān)系。一個(gè)進(jìn)程可以包含多個(gè)線程叫胖。最簡(jiǎn)單的情況下一個(gè)進(jìn)程只有一個(gè)線程草冈,即主線程(例如Android的UI線程)。
任何操作系統(tǒng)都需要有相應(yīng)的IPC機(jī)制瓮增。如Windows上的剪貼板怎棱、管道和郵槽;Linux上命名管道绷跑、共享內(nèi)容拳恋、信號(hào)量等。Android中最有特色的進(jìn)程間通信方式就是binder砸捏,另外還支持socket谬运。contentProvider是Android底層實(shí)現(xiàn)的進(jìn)程間通信。
在Android中垦藏,IPC的使用場(chǎng)景大概有以下:
- 有些模塊由于特殊原因需要運(yùn)行在單獨(dú)的進(jìn)程中梆暖。
- 通過多進(jìn)程來獲取多份內(nèi)存空間。
- 當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù)掂骏。
Android中的多進(jìn)程模式
開啟多進(jìn)程模式
在Android中使用多線程只有一種方法:給四大組件在Manifest中指定 android:process 屬性轰驳。這個(gè)屬性的值就是進(jìn)程名。這意味著不能在運(yùn)行時(shí)指定一個(gè)線程所在的進(jìn)程弟灼。
<activity android:name=".MainActivity"
android:process=":remote"/>
<activity android:name=".MainActivity"
android:process="com.example.c2.remote"/>
兩種進(jìn)程命名方式的區(qū)別:
- “:remote”
“:”的含義是指在當(dāng)前的進(jìn)程名前面附加上當(dāng)前的包名级解,完整的進(jìn)程名為“com.example.c2.remote”。這種進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程田绑,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中勤哗。 - “com.example.c2.remote”
這是一種完整的命名方式。這種進(jìn)程屬于全局進(jìn)程辛馆,其他應(yīng)用可以通過ShareUID方式和它跑在同一個(gè)進(jìn)程中俺陋。
tips:使用 adb shell ps 或 adb shell ps|grep 包名 查看當(dāng)前所存在的進(jìn)程信息。
多線程模式的運(yùn)行機(jī)制
Android為每個(gè)進(jìn)程都分配了一個(gè)獨(dú)立的虛擬機(jī)昙篙,不同虛擬機(jī)在內(nèi)存分配上有不同的地址空間,導(dǎo)致不同的虛擬機(jī)訪問同一個(gè)類的對(duì)象會(huì)產(chǎn)生多份副本诱咏。例如不同進(jìn)程的Activity對(duì)靜態(tài)變量的修改苔可,對(duì)其他進(jìn)程不會(huì)造成任何影響。所有運(yùn)行在不同進(jìn)程的四大組件袋狞,只要它們之間需要通過內(nèi)存在共享數(shù)據(jù)焚辅,都會(huì)共享失敗映屋。四大組件之間不可能不通過中間層來共享數(shù)據(jù)。
多進(jìn)程會(huì)帶來以下問題:
- 靜態(tài)成員和單例模式完全失效同蜻。
- 線程同步鎖機(jī)制完全失效棚点。
這兩點(diǎn)都是因?yàn)椴煌M(jìn)程不在同一個(gè)內(nèi)存空間下,鎖的對(duì)象也不是同一個(gè)對(duì)象湾蔓。 - SharedPreferences的可靠性下降瘫析。
SharedPreferences底層是 通過讀/寫XML文件實(shí)現(xiàn)的,并發(fā)讀/寫會(huì)導(dǎo)致一定幾率的數(shù)據(jù)丟失默责。 - Application會(huì)多次創(chuàng)建贬循。
由于系統(tǒng)創(chuàng)建新的進(jìn)程的同時(shí)分配獨(dú)立虛擬機(jī),其實(shí)這就是啟動(dòng)一個(gè)應(yīng)用的過程桃序。在多進(jìn)程模式中杖虾,不同進(jìn)程的組件擁有獨(dú)立的虛擬機(jī)、Application以及內(nèi)存空間媒熊。
多進(jìn)程相當(dāng)于兩個(gè)不同的應(yīng)用采用了SharedUID的模式奇适。
實(shí)現(xiàn)跨進(jìn)程的方式有很多:
- Intent傳遞數(shù)據(jù)。
- 共享文件和SharedPreferences芦鳍。
- 基于Binder的Messenger和AIDL嚷往。
- Socket等。
IPC基礎(chǔ)概念介紹
主要介紹 Serializable 怜校、 Parcelable 间影、 Binder 。Serializable和Parcelable接口可以完成對(duì)象的序列化過程茄茁,我們通過Intent和Binder傳輸數(shù)據(jù)時(shí)就需要Parcelable和Serializable魂贬。還有的時(shí)候我們需要對(duì)象持久化到存儲(chǔ)設(shè)備上或者通過網(wǎng)絡(luò)傳輸?shù)狡渌蛻舳耍残枰猄erializable完成對(duì)象持久化裙顽。
Serializable接口
Serializable 是Java提供的一個(gè)序列化接口( 空接口)付燥,為對(duì)象提供標(biāo)準(zhǔn)的序列化和反序列化操作。只需要一個(gè)類去實(shí)現(xiàn) Serializable 接口并聲明一個(gè) serialVersionUID 即可實(shí)現(xiàn)序列化愈犹。
private static final long serialVersionUID = 8711368828010083044L
serialVersionUID也可以不聲明键科。如果不手動(dòng)指定 serialVersionUID 的值,反序列化時(shí)如果當(dāng)前類有所改變( 比如增刪了某些成員變量) 漩怎,那么系統(tǒng)就會(huì)重新計(jì)算當(dāng)前類的hash值并更新 serialVersionUID 勋颖。這個(gè)時(shí)候當(dāng)前類的 serialVersionUID 就和序列化數(shù)據(jù)中的serialVersionUID 不一致,導(dǎo)致反序列化失敗勋锤,程序就出現(xiàn)crash饭玲。
靜態(tài)成員變量屬于類不屬于對(duì)象,不參與序列化過程叁执,其次 transient 關(guān)鍵字標(biāo)記的成員變量也不參與序列化過程茄厘。
通過重寫writeObject和readObject方法可以改變系統(tǒng)默認(rèn)的序列化過程矮冬。
Parcelable接口
Parcel內(nèi)部包裝了可序列化的數(shù)據(jù),可以在Binder中自由傳輸次哈。序列化過程中需要實(shí)現(xiàn)的功能有序列化胎署、反序列化和內(nèi)容描述。
序列化功能由 writeToParcel 方法完成,最終是通過 Parcel 的一系列writer方法來完成窑滞。
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(code);
out.writeString(name);
}
反序列化功能由 CREATOR 來完成琼牧,其內(nèi)部表明了如何創(chuàng)建序列化對(duì)象和數(shù)組,通過 Parcel 的一系列read方法來完成葛假。
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];
}
};
protected Book(Parcel in) {
code = in.readInt();
name = in.readString();
}
在Book(Parcel in)方法中障陶,如果有一個(gè)成員變量是另一個(gè)可序列化對(duì)象,在反序列化過程中需要傳遞當(dāng)前線程的上下文類加載器聊训,否則會(huì)報(bào)無法找到類的錯(cuò)誤抱究。
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
內(nèi)容描述功能由 describeContents 方法完成,幾乎所有情況下都應(yīng)該返回0带斑,僅當(dāng)當(dāng)前對(duì)象中存在文件描述符時(shí)返回1鼓寺。
public int describeContents() {
return 0;
}
Serializable 是Java的序列化接口,使用簡(jiǎn)單但開銷大勋磕,序列化和反序列化過程需要大量I/O操作妈候。而 Parcelable 是Android中的序列化方式,適合在Android平臺(tái)使用挂滓,效率高但是使用麻煩苦银。 Parcelable 主要在內(nèi)存序列化上,Parcelable 也可以將對(duì)象序列化到存儲(chǔ)設(shè)備中或者將對(duì)象序列化后通過網(wǎng)絡(luò)傳輸赶站,但是稍顯復(fù)雜幔虏,推薦使用 Serializable 。
Binder
Binder是Android中的一個(gè)類贝椿,實(shí)現(xiàn)了 IBinder 接口想括。從IPC角度說,Binder是Android的一種跨進(jìn)程通訊方式烙博,Binder還可以理解為一種虛擬物理設(shè)備瑟蜈,它的設(shè)備驅(qū)動(dòng)是/dev/binder。從Android Framework角度來說渣窜,Binder是 ServiceManager 連接各種Manager(ActivityManager铺根、WindowManager)和相應(yīng) ManagerService 的橋梁。從Android應(yīng)用層來說乔宿,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介夷都,當(dāng)bindService時(shí),服務(wù)端返回一個(gè)包含服務(wù)端業(yè)務(wù)調(diào)用的Binder對(duì)象予颤,通過這個(gè)Binder對(duì)象囤官,客戶端就可以獲取服務(wù)器端提供的服務(wù)或者數(shù)據(jù)( 包括普通服務(wù)和基于AIDL的服務(wù))。
Binder通信采用C/S架構(gòu)蛤虐,從組件視角來說党饮,包含Client、Server驳庭、ServiceManager以及binder驅(qū)動(dòng)刑顺,其中ServiceManager用于管理系統(tǒng)中的各種服務(wù)。
圖中的Client,Server,Service Manager之間交互都是虛線表示饲常,是由于它們彼此之間不是直接交互的蹲堂,而是都通過與Binder驅(qū)動(dòng)進(jìn)行交互的,從而實(shí)現(xiàn)IPC通信方式贝淤。其中Binder驅(qū)動(dòng)位于內(nèi)核空間柒竞,Client,Server,Service Manager位于用戶空間。Binder驅(qū)動(dòng)和Service Manager可以看做是Android平臺(tái)的基礎(chǔ)架構(gòu)播聪,而Client和Server是Android的應(yīng)用層朽基,開發(fā)人員只需自定義實(shí)現(xiàn)client、Server端离陶,借助Android的基本平臺(tái)架構(gòu)便可以直接進(jìn)行IPC通信稼虎。
Android中Binder主要用于 Service ,包括AIDL和Messenger招刨。普通Service的Binder不涉及進(jìn)程間通信霎俩,Messenger的底層其實(shí)是AIDL,所以下面通過AIDL分析Binder的工作機(jī)制沉眶。
由系統(tǒng)根據(jù)AIDL文件自動(dòng)生成.java文件
- Book.java
表示圖書信息的實(shí)體類打却,實(shí)現(xiàn)了Parcelable接口。 - Book.aidl
Book類在AIDL中的聲明沦寂。 - IBookManager.aidl
定義的管理Book實(shí)體的一個(gè)接口学密,包含 getBookList 和 addBook 兩個(gè)方法。盡管Book類和IBookManager位于相同的包中传藏,但是在IBookManager仍然要導(dǎo)入Book類腻暮。 - IBookManager.java
系統(tǒng)為IBookManager.aidl生產(chǎn)的Binder類,在 gen 目錄下毯侦。
IBookManager繼承了 IInterface 接口哭靖,所有在Binder中傳輸?shù)慕涌诙夹枰^IInterface接口。結(jié)構(gòu)如下:
- 聲明了 getBookList 和 addBook 方法侈离,還聲明了兩個(gè)整型id分別標(biāo)識(shí)這兩個(gè)方法试幽,用于標(biāo)識(shí)在 transact 過程中客戶端請(qǐng)求的到底是哪個(gè)方法。
- 聲明了一個(gè)內(nèi)部類 Stub 卦碾,這個(gè) Stub 就是一個(gè)Binder類铺坞,當(dāng)客戶端和服務(wù)端位于同一進(jìn)程時(shí)起宽,方法調(diào)用不會(huì)走跨進(jìn)程的 transact 。當(dāng)二者位于不同進(jìn)程時(shí)济榨,方法調(diào)用需要走 transact 過程坯沪,這個(gè)邏輯有 Stub 的內(nèi)部代理類 Proxy 來完成。
- 這個(gè)接口的核心實(shí)現(xiàn)就是它的內(nèi)部類 Stub 和 Stub 的內(nèi)部代理類 Proxy 擒滑。
Stub和Proxy類的內(nèi)部方法和定義
- DESCRIPTOR
Binder的唯一標(biāo)識(shí)腐晾,一般用Binder的類名表示。 - asInterface(android.os.IBinder obj)
將服務(wù)端的Binder對(duì)象轉(zhuǎn)換為客戶端所需的AIDL接口類型的對(duì)象丐一,如果C/S位于同一進(jìn)程藻糖,此方法返回就是服務(wù)端的Stub對(duì)象本身,否則返回的就是系統(tǒng)封裝后的Stub.proxy對(duì)象库车。 - asBinder
返回當(dāng)前Binder對(duì)象巨柒。 - onTransact
這個(gè)方法運(yùn)行在服務(wù)端的Binder線程池中,由客戶端發(fā)起跨進(jìn)程請(qǐng)求時(shí)凝颇,遠(yuǎn)程請(qǐng)求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理潘拱。該方法的原型是:
java public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
- 服務(wù)端通過code確定客戶端請(qǐng)求的目標(biāo)方法是什么,
接著從data取出目標(biāo)方法所需的參數(shù)拧略,然后執(zhí)行目標(biāo)方法芦岂。 - 執(zhí)行完畢后向reply寫入返回值( 如果有返回值)。
- 如果這個(gè)方法返回值為false垫蛆,那么服務(wù)端的請(qǐng)求會(huì)失敗禽最,利用這個(gè)特性我們可以來做權(quán)限驗(yàn)證。
- Proxy#getBookList 和Proxy#addBook
這兩個(gè)方法運(yùn)行在客戶端袱饭,內(nèi)部實(shí)現(xiàn)過程如下:
- 首先創(chuàng)建該方法所需要的輸入型對(duì)象Parcel對(duì)象_data川无,輸出型Parcel對(duì)象_reply和返回值對(duì)象List。
- 然后把該方法的參數(shù)信息寫入_data(如果有參數(shù))虑乖。
- 接著調(diào)用transact方法發(fā)起RPC(遠(yuǎn)程過程調(diào)用)懦趋,同時(shí)當(dāng)前線程掛起。
- 然后服務(wù)端的onTransact方法會(huì)被調(diào)用知道RPC過程返回后疹味,當(dāng)前線程繼續(xù)執(zhí)行仅叫,并從_reply中取出RPC過程的返回結(jié)果,最后返回_reply中的數(shù)據(jù)糙捺。
AIDL文件不是必須的诫咱,之所以提供AIDL文件,是為了方便系統(tǒng)為我們生成IBookManager.java洪灯,但我們完全可以自己寫一個(gè)坎缭。
linkToDeath和unlinkToDeath
如果服務(wù)端進(jìn)程異常終止,我們到服務(wù)端的Binder連接斷裂。但是掏呼,如果我們不知道Binder連接已經(jīng)斷裂坏快,那么客戶端功能會(huì)受影響。通過linkTODeath我們可以給Binder設(shè)置一個(gè)死亡代理哄尔,當(dāng)Binder死亡時(shí)假消,我們就會(huì)收到通知。
- 聲明一個(gè) DeathRecipient 對(duì)象。 DeathRecipient 是一個(gè)接口鸣戴,只有一個(gè)方法 binderDied 缰雇,當(dāng)Binder死亡的時(shí)候疏之,系統(tǒng)就會(huì)回調(diào) binderDied 方法爸业,然后我們就可以重新綁定遠(yuǎn)程服務(wù)扯旷。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:這里重新綁定遠(yuǎn)程Service
}
};
- 在客戶端綁定遠(yuǎn)程服務(wù)成功后惰瓜,給binder設(shè)置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
- 另外,可以通過Binder的 isBinderAlive 判斷Binder是否死亡赋续。
Android中的IPC方式
主要有以下方式:
- Intent中附加extras
- 共享文件
- Binder
- ContentProvider
- Socket
使用Bundle
四大組件中的三大組件( Activity昆箕、Service、Receiver) 都支持在Intent中傳遞 Bundle 數(shù)據(jù)。
Bundle實(shí)現(xiàn)了Parcelable接口,因此可以方便的在不同進(jìn)程間傳輸带射。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)了另一個(gè)進(jìn)程的Activity晤揣、Service钠四、Receiver,可以再Bundle中附加我們需要傳輸給遠(yuǎn)程進(jìn)程的消息并通過Intent發(fā)送出去。被傳輸?shù)臄?shù)據(jù)必須能夠被序列化凡怎。
使用文件共享
我們可以序列化一個(gè)對(duì)象到文件系統(tǒng)中的同時(shí)從另一個(gè)進(jìn)程中恢復(fù)這個(gè)對(duì)象氛雪。
- 通過 ObjectOutputStream / ObjectInputStream 序列化一個(gè)對(duì)象到文件中,或者在另一個(gè)進(jìn)程從文件中反序列這個(gè)對(duì)象。注意:反序列化得到的對(duì)象只是內(nèi)容上和序列化之前的對(duì)象一樣,本質(zhì)是兩個(gè)對(duì)象。
- 文件并發(fā)讀寫會(huì)導(dǎo)致讀出的對(duì)象可能不是最新的服猪,并發(fā)寫的話那就更嚴(yán)重了 。所以文件共享方式適合對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信拐云,并且要妥善處理并發(fā)讀寫問題罢猪。
- SharedPreferences 底層實(shí)現(xiàn)采用XML文件來存儲(chǔ)鍵值對(duì)。系統(tǒng)對(duì)它的讀/寫有一定的緩存策略叉瘩,即在內(nèi)存中會(huì)有一份 SharedPreferences 文件的緩存膳帕,因此在多進(jìn)程模式下,系統(tǒng)對(duì)它的讀/寫變得不可靠薇缅,面對(duì)高并發(fā)讀/寫時(shí) SharedPreferences 有很大幾率丟失數(shù)據(jù)危彩,因此不建議在IPC中使用 SharedPreferences 。
使用Messenger
Messenger可以在不同進(jìn)程間傳遞Message對(duì)象泳桦。是一種輕量級(jí)的IPC方案汤徽,底層實(shí)現(xiàn)是AIDL。它對(duì)AIDL進(jìn)行了封裝灸撰,使得我們可以更簡(jiǎn)便的進(jìn)行IPC谒府。
具體使用時(shí)拼坎,分為服務(wù)端和客戶端:
- 服務(wù)端:創(chuàng)建一個(gè)Service來處理客戶端請(qǐng)求,同時(shí)創(chuàng)建一個(gè)Handler并通過它來創(chuàng)建一個(gè)
Messenger狱掂,然后再Service的onBind中返回Messenger對(duì)象底層的Binder即可演痒。
private final Messenger mMessenger = new Messenger (new xxxHandler());
- 客戶端:綁定服務(wù)端的Sevice,利用服務(wù)端返回的IBinder對(duì)象來創(chuàng)建一個(gè)Messenger趋惨,通過這個(gè)Messenger就可以向服務(wù)端發(fā)送消息了鸟顺,消息類型是 Message 。如果需要服務(wù)端響應(yīng)器虾,則需要?jiǎng)?chuàng)建一個(gè)Handler并通過它來創(chuàng)建一個(gè)Messenger( 和服務(wù)端一樣) 讯嫂,并通過 Message 的 replyTo 參數(shù)傳遞給服務(wù)端。服務(wù)端通過Message的 replyTo 參數(shù)就可以回應(yīng)客戶端了兆沙。
總而言之欧芽,就是客戶端和服務(wù)端 拿到對(duì)方的Messenger來發(fā)送 Message 。只不過客戶端通過bindService 而服務(wù)端通過 message.replyTo 來獲得對(duì)方的Messenger葛圃。
Messenger中有一個(gè) Hanlder 以串行的方式處理隊(duì)列中的消息千扔。不存在并發(fā)執(zhí)行,因此我們不用考慮線程同步的問題库正。
使用AIDL
如果有大量的并發(fā)請(qǐng)求曲楚,使用Messenger就不太適合,同時(shí)如果需要跨進(jìn)程調(diào)用服務(wù)端的方法褥符,Messenger就無法做到了龙誊。這時(shí)我們可以使用AIDL。
流程如下:
- 服務(wù)端需要?jiǎng)?chuàng)建Service來監(jiān)聽客戶端請(qǐng)求喷楣,然后創(chuàng)建一個(gè)AIDL文件趟大,將暴露給客戶端的接口在AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可铣焊。
- 客戶端首先綁定服務(wù)端的Service逊朽,綁定成功后,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬的類型粗截,接著就可以調(diào)用AIDL中的方法了惋耙。
AIDL支持的數(shù)據(jù)類型:
- 基本數(shù)據(jù)類型、String熊昌、CharSequence
- List:只支持ArrayList绽榛,里面的每個(gè)元素必須被AIDL支持
- Map:只支持HashMap,里面的每個(gè)元素必須被AIDL支持
- Parcelable
- 所有的AIDL接口本身也可以在AIDL文件中使用
自定義的Parcelable對(duì)象和AIDL對(duì)象婿屹,不管它們與當(dāng)前的AIDL文件是否位于同一個(gè)包灭美,都必須顯式import進(jìn)來。
如果AIDL文件中使用了自定義的Parcelable對(duì)象昂利,就必須新建一個(gè)和它同名的AIDL文件届腐,并在其中聲明它為Parcelable類型铁坎。
package com.ryg.chapter_2.aidl;
parcelable Book;
AIDL接口中的參數(shù)除了基本類型以外都必須表明方向in/out。AIDL接口文件中只支持方法犁苏,不支持聲明靜態(tài)常量硬萍。建議把所有和AIDL相關(guān)的類和文件放在同一個(gè)包中,方便管理围详。
void addBook(in Book book);
AIDL方法是在服務(wù)端的Binder線程池中執(zhí)行的朴乖,因此當(dāng)多個(gè)客戶端同時(shí)連接時(shí),管理數(shù)據(jù)的集合直接采用 CopyOnWriteArrayList 來進(jìn)行自動(dòng)線程同步助赞。類似的還有 ConcurrentHashMap 买羞。
因?yàn)榭蛻舳说膌istener和服務(wù)端的listener不是同一個(gè)對(duì)象,所以 RemoteCallbackList 是系統(tǒng)專門提供用于刪除跨進(jìn)程listener的接口雹食,支持管理任意的AIDL接口畜普,因?yàn)樗蠥IDL接口都繼承自 I 接口。
public class RemoteCallbackList<E extends IInterface>
它內(nèi)部通過一個(gè)Map接口來保存所有的AIDL回調(diào)群叶,這個(gè)Map的key是 IBinder 類型吃挑,value是 Callback 類型。當(dāng)客戶端解除注冊(cè)時(shí)街立,遍歷服務(wù)端所有l(wèi)istener儒鹿,找到和客戶端listener具有相同Binder對(duì)象的服務(wù)端listenr并把它刪掉。
客戶端RPC的時(shí)候線程會(huì)被掛起几晤,由于被調(diào)用的方法運(yùn)行在服務(wù)端的Binder線程池中,可能很耗時(shí)植阴,不能在主線程中去調(diào)用服務(wù)端的方法蟹瘾。
權(quán)限驗(yàn)證
默認(rèn)情況下,我們的遠(yuǎn)程服務(wù)任何人都可以連接掠手,我們必須加入權(quán)限驗(yàn)證功能憾朴,權(quán)限驗(yàn)證失敗則無法調(diào)用服務(wù)中的方法。通常有兩種驗(yàn)證方法:
- 在onBind中驗(yàn)證喷鸽,驗(yàn)證不通過返回null众雷。
驗(yàn)證方式比如permission驗(yàn)證,在AndroidManifest聲明:
<permission
android:name="com.rgy.chapter_2.permisson.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
Android自定義權(quán)限和使用權(quán)限:
public IBinder onBind(Intent intent){
int check = checkCallingOrSelefPermission("com.ryq.chapter_2.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
這種方法也適用于Messager做祝。
- 在onTransact中驗(yàn)證砾省,驗(yàn)證不通過返回false。
可以permission驗(yàn)證混槐,還可以采用Uid和Pid驗(yàn)證编兄。
使用ContentProvider
ContentProvider是四大組件之一,天生就是用來進(jìn)程間通信声登。和Messenger一樣狠鸳,其底層實(shí)現(xiàn)是用Binder揣苏。
系統(tǒng)預(yù)置了許多ContentProvider,比如通訊錄件舵、日程表等卸察。要RPC訪問這些信息,只需要通過ContentResolver的query、update定躏、insert和delete方法即可愚隧。
創(chuàng)建自定義的ContentProvider,只需繼承ContentProvider類并實(shí)現(xiàn) onCreate 洪乍、 query 、 update 夜焦、 insert 壳澳、 getType 六個(gè)抽象方法即可。getType用來返回一個(gè)Uri請(qǐng)求所對(duì)應(yīng)的MIME類型茫经,剩下四個(gè)方法對(duì)應(yīng)于CRUD操作巷波。這六個(gè)方法都運(yùn)行在ContentProvider進(jìn)程中,除了 onCreate 由系統(tǒng)回調(diào)并運(yùn)行在主線程里卸伞,其他五個(gè)方法都由外界調(diào)用并運(yùn)行在Binder線程池中抹镊。
ContentProvider是通過Uri來區(qū)分外界要訪問的數(shù)據(jù)集合,例如外界訪問ContentProvider中的表荤傲,我們需要為它們定義單獨(dú)的Uri和Uri_Code垮耳。根據(jù)Uri_Code,我們就知道要訪問哪個(gè)表了遂黍。
query终佛、update、insert雾家、delete四大方法存在多線程并發(fā)訪問铃彰,因此方法內(nèi)部要做好線程同步。若采用SQLite并且只有一個(gè)SQLiteDatabase芯咧,SQLiteDatabase內(nèi)部已經(jīng)做了同步處理牙捉。若是多個(gè)SQLiteDatabase或是采用List作為底層數(shù)據(jù)集,就必須做線程同步敬飒。
使用Socket
Socket也稱為“套接字”邪铲,分為流式套接字和用戶數(shù)據(jù)報(bào)套接字兩種,分別對(duì)應(yīng)于TCP和UDP協(xié)議驶拱。Socket可以實(shí)現(xiàn)計(jì)算機(jī)網(wǎng)絡(luò)中的兩個(gè)進(jìn)程間的通信霜浴,當(dāng)然也可以在本地實(shí)現(xiàn)進(jìn)程間的通信。我們以一個(gè)跨進(jìn)程的聊天程序來演示蓝纲。
在遠(yuǎn)程Service建立一個(gè)TCP服務(wù)阴孟,然后在主界面中連接TCP服務(wù)晌纫。服務(wù)端Service監(jiān)聽本地端口,客戶端連接指定的端口永丝,建立連接成功后锹漱,拿到 Socket 對(duì)象就可以向服務(wù)端發(fā)送消息或者接受服務(wù)端發(fā)送的消息。
除了采用TCP套接字慕嚷,也可以用UDP套接字哥牍。實(shí)際上socket不僅能實(shí)現(xiàn)進(jìn)程間的通信,還可以實(shí)現(xiàn)設(shè)備間的通信(只要設(shè)備之間的IP地址互相可見)喝检。
Binder連接池
前面提到AIDL的流程是:首先創(chuàng)建一個(gè)service和AIDL接口嗅辣,接著創(chuàng)建一個(gè)類繼承自AIDL接口中的Stub類并實(shí)現(xiàn)Stub中的抽象方法,客戶端在Service的onBind方法中拿到這個(gè)類的對(duì)象挠说,然后綁定這個(gè)service澡谭,建立連接后就可以通過這個(gè)Stub對(duì)象進(jìn)行RPC。
那么如果項(xiàng)目龐大损俭,有多個(gè)業(yè)務(wù)模塊都需要使用AIDL進(jìn)行IPC蛙奖,隨著AIDL數(shù)量的增加,我們不能無限制地增加Service杆兵,我們需要把所有AIDL放在同一個(gè)Service中去管理雁仲。
- 服務(wù)端只有一個(gè)Service,把所有AIDL放在一個(gè)Service中琐脏,不同業(yè)務(wù)模塊之間不能有耦合攒砖。
- 服務(wù)端提供一個(gè) queryBinder 接口,這個(gè)接口能夠根據(jù)業(yè)務(wù)模塊的特征來返回響應(yīng)的Binder對(duì)象給客戶端日裙。
- 不同的業(yè)務(wù)模塊拿到所需的Binder對(duì)象就可以進(jìn)行RPC了祭衩。