1.Android IPC簡(jiǎn)介
- IPC為進(jìn)程間通訊,兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過(guò)程。
- IPC不是Android所獨(dú)有的,任何一個(gè)操作系統(tǒng)都有對(duì)應(yīng)的IPC機(jī)制鲁僚。Windows上通過(guò)剪切板、管道裁厅、油槽等進(jìn)行進(jìn)程間通訊冰沙。Linux上通過(guò)命名空間、共享內(nèi)容执虹、信號(hào)量等進(jìn)行進(jìn)程間通訊拓挥。Android中沒(méi)有完全繼承于Linux,有特色的進(jìn)程間通訊方式是Binder(基于BInder的Messenger和AIDL)袋励,還支持Socket侥啤。
- 使用場(chǎng)景:由于某些原因應(yīng)用自身需要采用多進(jìn)程模式來(lái)實(shí)現(xiàn)当叭,或者為了加大一個(gè)應(yīng)用可使用的內(nèi)存,因?yàn)锳ndroid對(duì)當(dāng)個(gè)應(yīng)用可使用的最大內(nèi)存做了限制盖灸。
2.Android中的多進(jìn)程模式
- 同一個(gè)應(yīng)用蚁鳖,通過(guò)給四大組件指定android:process屬性,就可以開(kāi)啟多進(jìn)程模式赁炎。
- 進(jìn)程名以":"開(kāi)頭的屬于當(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)用可以通過(guò)ShareUID跑在同一個(gè)進(jìn)程并且簽名相同,他們可以共享data目錄姻报、組件信息己英、共享內(nèi)存數(shù)據(jù)。
- 多進(jìn)程通訊的問(wèn)題:
1.靜態(tài)成員和單例模式完全失效吴旋。
2.線程同步機(jī)制完全失效损肛。
3.SharedPreferences的可靠性下降
4.Application會(huì)多次創(chuàng)建
問(wèn)題1、2原因是因?yàn)檫M(jìn)程不同邮府,已經(jīng)不是同一塊內(nèi)存了;
問(wèn)題3 是因?yàn)镾haredPreferences不支持兩個(gè)進(jìn)程同時(shí)進(jìn)行讀寫(xiě)操作溉奕,有一定幾率導(dǎo)致數(shù)據(jù)丟失褂傀;
問(wèn)題4 是當(dāng)一個(gè)組件跑在一個(gè)新的進(jìn)程中,系統(tǒng)會(huì)為他創(chuàng)建新的進(jìn)程同時(shí)分配獨(dú)立的虛擬機(jī)加勤,所有這個(gè)過(guò)程其實(shí)就是啟動(dòng)一個(gè)應(yīng)用的過(guò)程仙辟,,因此相當(dāng)于系統(tǒng)又把這個(gè)應(yīng)用重新啟動(dòng)了一遍鳄梅,Application也是新建了叠国。
實(shí)現(xiàn)跨進(jìn)程通訊有很多方式共享文件、SharedPreferences戴尸、基于Binder的Messenger和AIDL粟焊、Socket等。
3. IPC基礎(chǔ)概念介紹
1.Serializable
- serialVersionUID是用來(lái)輔助序列化和反序列化過(guò)程的孙蒙,原則上序列化后的數(shù)據(jù)中的serialVersionUID要和當(dāng)前類的serialVersionUID相同才能正常的序列化,(當(dāng)類結(jié)構(gòu)改變不能反序列化)项棠。
- 靜態(tài)成員變量屬于類不屬于對(duì)象,所以不會(huì)參加序列化過(guò)程挎峦;其次用transient關(guān)鍵字標(biāo)明的成員變量也不參加序列化過(guò)程香追。
- 重寫(xiě)如下兩個(gè)方法可以重寫(xiě)系統(tǒng)默認(rèn)的序列化和反序列化過(guò)程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
2.Parcable
Serializable是Java中的序列化接口,需要大量的I/O操作坦胶,開(kāi)銷大透典,Parcable是Android的序列化方式晴楔,更適合Android,效率高峭咒,(如果涉及到持久化保存税弃,建議使用Serializable,Parcable版本不同讹语,可能會(huì)發(fā)生變化)钙皮。
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;
}
private 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方法來(lái)完成,最終通過(guò)Parcel中的一系列write方法完成的顽决。
反序列化功能由CREATEOR來(lái)完成短条,其內(nèi)部標(biāo)明了如何創(chuàng)建序列號(hào)對(duì)象和訴諸,
并通過(guò)Parcel的一系列read方法來(lái)完成反序列化過(guò)程才菠。內(nèi)容描述功能由describeContents方法來(lái)完成
茸时,幾乎所有情況都返回0,只有當(dāng)前對(duì)象存在文件描述符時(shí)赋访,才返回1可都。
3.Binder(例子)
AIDL的本質(zhì)是系統(tǒng)為我們提供了一個(gè)實(shí)現(xiàn)Binder的工具,僅此而已蚓耽。
源碼分析見(jiàn)書(shū)
1.從IPC的角度來(lái)說(shuō)渠牲,Binder是Android的一種跨進(jìn)程的通訊方式;Binder也可以理解為是一種虛擬額物理設(shè)備步悠,他的設(shè)備驅(qū)動(dòng)是/dev/binder签杈;
2.從Android Framework角度來(lái)說(shuō),Binder是ServiceManager連接各種Manager(ActivityManager鼎兽、WindowManager答姥、等等)和ManagerService的橋梁;
3.從Android應(yīng)用層來(lái)說(shuō)谚咬,Binder是客戶端與服務(wù)端通訊的媒介鹦付。在Android開(kāi)發(fā)中,Binder主要用于Service中择卦,包括AIDL和Messenger敲长,其中普通的Service的Binder不涉及進(jìn)程間通訊;而Messenger的底層其實(shí)就是AIDL秉继。
Binder死亡時(shí)重連服務(wù)
1.設(shè)置死亡代理判斷Binder是否死亡
Binder的兩個(gè)重要方法linkToDeath和unlinkToDeath潘明。通過(guò)linkToDeath可以給Binder設(shè)置一個(gè)死亡代理,當(dāng)Binder死亡時(shí)秕噪,我們就會(huì)收到通知钳降,然后就可以重新發(fā)起連接請(qǐng)求。聲明一個(gè)DeathRecipient對(duì)象腌巾,DeathRecipient是一個(gè)接口遂填,其內(nèi)部只有一個(gè)方法binderDied铲觉,實(shí)現(xiàn)這個(gè)方法后就可以在Binder死亡的時(shí)候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:接下來(lái)重新綁定遠(yuǎn)程Service
}
}
在客戶端綁定遠(yuǎn)程服務(wù)成功后吓坚,給Binder設(shè)置死亡代理
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
2.在onServiceDisconnected重連遠(yuǎn)程服務(wù)
區(qū)別:binderDied是在客戶端的Binder線程中回調(diào)撵幽,不能訪問(wèn)UI,而onServiceDisconnected可以訪問(wèn)UI礁击。
源碼示例
方法介紹:
1.DESCRIPTOR: Binder的唯一標(biāo)識(shí)盐杂,一般用當(dāng)前Binder的類名表示。
2.asInterface(android.os.IBinder obj): 將服務(wù)端的Binder對(duì)象轉(zhuǎn)換成客戶端所需要的AIDL接口類型的對(duì)象哆窿;
如果客戶端和服務(wù)端位于相同進(jìn)程链烈,那么此方法返回的就是服務(wù)端Stub對(duì)象本身,否則返回系統(tǒng)封裝后的Stub.proxy對(duì)象挚躯。
3.asBinder :用于返回當(dāng)前的Binder對(duì)象
4.onTransact 運(yùn)行在服務(wù)端的Binder線程池中强衡,當(dāng)客戶端發(fā)起跨進(jìn)程通訊時(shí),遠(yuǎn)程請(qǐng)求會(huì)通過(guò)系統(tǒng)底層封裝交由此方法處理码荔。
public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
服務(wù)端通過(guò)code確認(rèn)客戶端請(qǐng)求的目標(biāo)方法是什么漩勤,接著從data中取出目標(biāo)方法所需的參數(shù)(如果有),然后執(zhí)行目標(biāo)方法缩搅。
當(dāng)目標(biāo)方法執(zhí)行完后越败,向reply中寫(xiě)入返回值(如果有)。如果方法返回值為false硼瓣,那么服務(wù)端的請(qǐng)求會(huì)失敗究飞,利用這個(gè)特性我們可以來(lái)做權(quán)限驗(yàn)證。
4.Android中的IPC方式
1.使用Bundle:
由于Bundle實(shí)現(xiàn)了Parcelable接口巨双,所以可以方便的在不同進(jìn)程中傳輸噪猾;Activity霉祸、Service和Receiver都支持在Intent中傳遞Bundle數(shù)據(jù)筑累。
2.使用文件共享:
兩個(gè)進(jìn)程通過(guò)讀/寫(xiě)一個(gè)文件來(lái)交換數(shù)據(jù);適合對(duì)數(shù)據(jù)同步要求性不高的場(chǎng)景丝蹭;并要避免并發(fā)寫(xiě)這種場(chǎng)景或者處理好線程同步問(wèn)題。SharedPreferences是個(gè)特例奔穿,雖然也是文件的一種,但系統(tǒng)在內(nèi)存中有一份SharedPreferences文件的緩存贱田,因此在多線程模式下,系統(tǒng)對(duì)他的讀/寫(xiě)就變得不可靠男摧,高并發(fā)讀寫(xiě)SharedPreferences有一定幾率會(huì)丟失數(shù)據(jù)蔬墩,因此不建議在多進(jìn)程通訊時(shí)采用SharedPreferences。
3.使用Messenger代碼示例
Messenger是輕量級(jí)的IPC方案拇颅,底層實(shí)現(xiàn)是AIDL,他對(duì)AIDL進(jìn)行了封裝樟插,Messenger 服務(wù)端是以串行的方式來(lái)處理客戶端的請(qǐng)求的韵洋,不存在并發(fā)執(zhí)行的情形。
缺憾:無(wú)法并發(fā)黄锤,只適合傳遞消息,無(wú)法跨進(jìn)程調(diào)用方法
4.使用AIDL[代碼示例]
1.AIDL注意
Messenger也是AIDL勉吻,系統(tǒng)做了封裝旅赢。
服務(wù)端首先創(chuàng)建一個(gè)Service用來(lái)監(jiān)聽(tīng)客戶端的連接請(qǐng)求,然后創(chuàng)建一個(gè)AIDL文件短纵,將暴露給客戶端的接口在AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可香到”ㄆ疲客戶端首先綁定服務(wù)端的Service,綁定成功后充易,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)化成AIDL接口所屬的類型,調(diào)用相對(duì)應(yīng)的AIDL中的方法盹靴。
注意:
1.AIDL支持的數(shù)據(jù)類型:
- 基本數(shù)據(jù)類型;
- String梭冠、CharSequence改备;
- List 只支持ArrayList,里面的元素必須都能被AIDL所支持;
- Map 只支持HashMap盐捷,里面的元素(key和value)必須都能被AIDL所支持;
- Parcelable 所有實(shí)現(xiàn)了Parcelable接口的對(duì)象毙驯;
- AIDL 所有AIDL接口本身也可以在AIDL文件中使用。
2.自定義的Parcelable對(duì)象和AIDL對(duì)象必須顯示的import進(jìn)來(lái)(即使在同一個(gè)包)垦巴。
3.除了基本數(shù)據(jù)類型铭段,需要用inout表示輸入輸出型參數(shù)。
4.為了方便AIDL開(kāi)發(fā)憔披,建議把所有和AIDL相關(guān)的類和文件都放在同一個(gè)包中爸吮,好處在于,當(dāng)客戶端是另一個(gè)應(yīng)用的時(shí)候锰霜,我們可以直接把整個(gè)包復(fù)制到客戶端工程中去桐早。
5.RemoteCallbackList是系統(tǒng)專門(mén)提供用于刪除跨進(jìn)程listener的接口,RemoteCallbackList是泛型友存,支持管理任意的AIDL接口陶衅,因?yàn)樗蠥IDL接口都繼承自android.os.IInterface接口。
6.需注意AIDL客戶端發(fā)起RPC過(guò)程的時(shí)候侠驯,客戶端的線程會(huì)掛起奕巍,如果是UI線程發(fā)起的RPC過(guò)程儒士,如果服務(wù)端處理事件過(guò)長(zhǎng),就會(huì)導(dǎo)致ANR诅福。
2.設(shè)置遠(yuǎn)程服務(wù)權(quán)限
方法1:在onBind中驗(yàn)證
①.在Manifest聲明權(quán)限
<permission
android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
②.在Service中的onBind中驗(yàn)證
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
Log.d(TAG, "onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
③.在Manifest注冊(cè)權(quán)限
<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />
方法2:在服務(wù)端的onTransact方法中進(jìn)行權(quán)限驗(yàn)證
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
Log.d(TAG, "check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
Log.d(TAG, "onTransact: " + packageName);
if (!packageName.startsWith("com.ryg")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
3.AIDl接口實(shí)現(xiàn)觀察者模式
BookManagerService
BookManagerActivity
4.RemoteCallbackList刪除跨進(jìn)程listener
①.使用RemoteCallbackList代替CopyOnWriteArrayList
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =
new RemoteCallbackList<IOnNewBookArrivedListener>();
②.獲取監(jiān)聽(tīng)器對(duì)象
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();//需要配套使用氓润,哪怕是只獲取一個(gè)
5.注意:
服務(wù)端本身可以做異步操作,客戶端當(dāng)訪問(wèn)耗時(shí)操作時(shí)挨措,需要開(kāi)啟子線程
同理崩溪,當(dāng)服務(wù)端調(diào)用客戶端的listener中的方法時(shí),被調(diào)用的方法也運(yùn)行在Binder線程池中觉既,同樣乳幸,需開(kāi)啟子線程
5.使用ContentProvider
ContentProvider底層實(shí)現(xiàn)也是Binder,專門(mén)用于不同應(yīng)用間的進(jìn)行數(shù)據(jù)共享的方式尝艘,ContentProvider主要是表格的形式來(lái)組織數(shù)據(jù),并且可以包含多個(gè)表眯漩,同時(shí)還支持文件數(shù)據(jù),圖片,視頻等闽颇。
6.使用Socket
網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換兵多,這個(gè)連接的一端稱為一個(gè)socket,
7.使用Binder連接池
當(dāng)我們使用AIDL的使用,有很多業(yè)務(wù)模塊需要用到AIDL剩膘,會(huì)創(chuàng)建很多服務(wù)怠褐,這個(gè)時(shí)候就需要用到BInder連接池,來(lái)管理這些AIDL,這個(gè)時(shí)候就只需要使用一個(gè)Service宪巨,根據(jù)不同的AIDL的code溜畅,去創(chuàng)建不同的AIDL即可。