IPC是Inter-Proess Communication的縮寫瑟慈,意思是跨進(jìn)程通信,即兩個進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程。今天我們就來聊聊Android中的IPC機(jī)制下翎。
IPC基礎(chǔ)
多進(jìn)程
進(jìn)程和線程
線程是CPU調(diào)度的最小單元混稽,是一種有限的系統(tǒng)資源采驻。而進(jìn)程則一般指一個執(zhí)行單元,在PC和移動設(shè)備上指一個程序或應(yīng)用匈勋。
一個進(jìn)程可以包含多個線程礼旅,在Android中,主線程為UI線程洽洁,只有在UI線程中才能操作界面元素痘系。
進(jìn)程和線程之間的關(guān)系其實就相當(dāng)于公司和部門之間的關(guān)系一樣。一個進(jìn)程就相當(dāng)于一個公司饿自,線程就相當(dāng)于公司里的部門汰翠,各司其職,可以并行工作昭雌。最小的公司可能只有一個部門复唤,即最基礎(chǔ)的進(jìn)程只有一個線程。至于多進(jìn)程也很好理解烛卧,有些公司很龐大佛纫,下屬又有好幾個小公司,就像一個龐大的應(yīng)用可能會有多個進(jìn)程一樣总放。
開啟多進(jìn)程模式的方式
常用方法為在AndroidMenifest中給四大組件指定android:process
屬性呈宇。(用JNI在native層fork一個新的進(jìn)程也可以實現(xiàn),但不屬于常規(guī)方法)
有兩種效果不同的命名方法:
- 省略包名局雄,如
android:process=":remote"
甥啄,表示進(jìn)程名為com.example.myapplication:remote。屬于當(dāng)前應(yīng)用的私有進(jìn)程炬搭,其他進(jìn)程的組件不能和他跑在同一進(jìn)程中蜈漓。 - 完整命名的進(jìn)程,如
android:process="com.example.myapplication.remote"
尚蝌。屬于全局進(jìn)程迎变,其他應(yīng)用可以通過ShareUID方式和他跑在用一個進(jìn)程中。
關(guān)于UID
Android系統(tǒng)為每個應(yīng)用分配一個唯一的UID飘言,具有相同UID的應(yīng)用才能共享數(shù)據(jù)衣形。
兩個應(yīng)用通過ShareUID跑在同一進(jìn)程的條件:ShareUID相同且簽名也相同。
滿足上述條件的兩個應(yīng)用,無論是否跑在同一進(jìn)程谆吴,它們可共享data目錄倒源,組件信息。
若跑在同一進(jìn)程句狼,它們除了可共享data目錄笋熬、組件信息,還可共享內(nèi)存數(shù)據(jù)腻菇。它們就像是一個應(yīng)用的兩個部分胳螟。
多進(jìn)程產(chǎn)生的問題
- 靜態(tài)成員和單例模式失效;
- 線程同步機(jī)制失效筹吐;
- SharePreferences的可靠性下降糖耸;
- Application會重復(fù)創(chuàng)建。
不同的進(jìn)程就是不同的JVM虛擬機(jī)丘薛,那么就會產(chǎn)生問題1和問題2嘉竟。由于SharePreferences底層是基于XML文件的讀寫實現(xiàn)的,那么并發(fā)讀寫肯定很容易出問題,即問題3洋侨。Android系統(tǒng)會為新的進(jìn)程分配獨立虛擬機(jī)舍扰,相當(dāng)于系統(tǒng)又把這個應(yīng)用重新啟動了一次,于是就出現(xiàn)了問題4希坚。
序列化
進(jìn)程中通信會涉及到序列化的相關(guān)內(nèi)容边苹,序列化表示將一個對象轉(zhuǎn)換成可存儲或可傳輸?shù)臓顟B(tài)。序列化后的對象可以在網(wǎng)絡(luò)上進(jìn)行傳輸裁僧,也可以存儲到本地勾给。
兩種類型的變量不會參與序列化:
- 靜態(tài)成員變量屬于類,不屬于對象锅知。
- 用transient關(guān)鍵字標(biāo)記的成員變量。
Java原生的序列化接口為 Serializable 脓钾,Android獨有的序列化接口為 Parcelable售睹。它們之間的區(qū)別是什么呢?
Serializable的作用是為了保存對象的屬性到本地文件可训、數(shù)據(jù)庫昌妹、網(wǎng)絡(luò)流以方便數(shù)據(jù)傳輸,當(dāng)然這種傳輸可以是程序內(nèi)的也可以是兩個程序間的握截。而Android的Parcelable的設(shè)計初衷是因為Serializable效率過慢飞崖,為了在程序內(nèi)不同組件間以及不同Android程序間(AIDL)高效的傳輸數(shù)據(jù)而設(shè)計,這些數(shù)據(jù)僅在內(nèi)存中存在谨胞,Parcelable是通過IBinder通信的消息的載體固歪。
它們的優(yōu)劣如何?
Parcelable的性能比Serializable好,在內(nèi)存開銷方面較小牢裳,所以在內(nèi)存間數(shù)據(jù)傳輸時推薦使用Parcelable逢防,如activity間傳輸數(shù)據(jù),而Serializable可將數(shù)據(jù)持久化方便保存蒲讯,所以在需要保存或網(wǎng)絡(luò)傳輸數(shù)據(jù)時選擇Serializable忘朝,因為android不同版本Parcelable可能不同,所以不推薦使用Parcelable進(jìn)行數(shù)據(jù)持久化判帮。
Serializable
Serializable是Java提供的序列化接口局嘁,使用起來很簡單,只需要在類的聲明中指定一個serialVersionUID即可(不指定也行),代碼如下:
public class Person implements Serializable {
private static final long serialVersionUID = 44260788630571004L;
public int id;
public String name;
...
}
serialVersionUID是用于在反序列化時進(jìn)行校驗晦墙,相當(dāng)于是一個版號的作用悦昵,如果serialVersionUID不同,說明類可能被改動過了偎痛,沒法反序列化旱捧。
序列化的過程都由系統(tǒng)自動完成了。我們只需使用ObjectOutputStream和ObjectInputStream即可完成序列化和反序列化的操作踩麦。代碼如下:
public static void main(String[] args) {
Person person = new Person(1,"Jerry");
try {
//序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
outputStream.writeObject(person);
outputStream.close();
//反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
Person myPerson = (Person) inputStream.readObject();
inputStream.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Parcelable
Parcelable接口是Android根據(jù)應(yīng)用場景(多進(jìn)程通信)提供的一種序列化方式枚赡。只要實現(xiàn)這個接口,一個類的對象就可以實現(xiàn)序列化并通過Intent和Binder傳遞谓谦。
相對Serializable贫橙,Parcelable就要復(fù)雜一些,需要實現(xiàn)writeToParcel反粥、describeContents函數(shù)以及靜態(tài)的CREATOR變量卢肃,實際上就是將如何打包和解包的工作自己來定義,而序列化的這些操作完全由底層實現(xiàn)才顿。
public class MyParcelable implements Parcelable {
private int mData;
private String mStr;
public int describeContents() {
return 0;
}
// 寫數(shù)據(jù)進(jìn)行保存
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
out.writeString(mStr);
}
// 用來創(chuàng)建自定義的Parcelable的對象
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
// 讀數(shù)據(jù)進(jìn)行恢復(fù)
private MyParcelable(Parcel in) {
mData = in.readInt();
mStr = in.readString();
}
}
由于Parcelable針對android平臺進(jìn)行過優(yōu)化莫湘,效率要高于Serializable,缺點就是使用起來麻煩一些郑气。
Binder
概念
- 從API角度:是一個類幅垮,實現(xiàn)IBinder接口。
- 從IPC角度:是Android中的一種跨進(jìn)程通信方式尾组。
- 從Framework角度:是ServiceManager連接各種Manager和相應(yīng)ManagerService的橋梁忙芒。
- 從應(yīng)用層:是客戶端和服務(wù)端進(jìn)行通信的媒介』淝龋客戶端通過它可獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)呵萨。
Android是基于Linux內(nèi)核基礎(chǔ)上設(shè)計的,大家都知道跨跨,Linux內(nèi)核有多種進(jìn)程間通信的方式:管道/消息隊列/共享內(nèi)存/Socket等潮峦,為啥還要搞個Binder 出來呢?
性能方面:
Binder相對于傳統(tǒng)的Socket方式,更加高效跑杭。Binder數(shù)據(jù)拷貝只需要一次铆帽,而管道、消息隊列德谅、Socket都需要2次爹橱,共享內(nèi)存方式一次內(nèi)存拷貝都不需要,但實現(xiàn)方式又比較復(fù)雜窄做。
安全方面:
Binder機(jī)制從協(xié)議本身就支持對通信雙方做身份校檢愧驱,從而大大提升了安全性。
一些理解
感覺網(wǎng)上好多大佬都講過Binder機(jī)制椭盏,長篇大論组砚,內(nèi)容不可謂不細(xì)致,不過對于新手而言有點難以理解掏颊,我一開始看的時候就感覺云里霧里的糟红。由于細(xì)致的講解網(wǎng)上到處都是,我這種菜鳥也不可能講的更好乌叶,所以下面我還是來談?wù)勎掖譁\的認(rèn)識吧盆偿。
首先是Binder機(jī)制的四個組件
Client、Server准浴、ServiceManager以及Binder驅(qū)動事扭。
Binder通信采用C/S架構(gòu),那么一開始的問題就是哪個是客戶端哪個是服務(wù)端呢乐横?
答案是不一定求橄。乍一看,Client葡公、Server一個客戶端一個服務(wù)端罐农,似乎挺合理的,但其實在Binder機(jī)制中Server有可能也是客戶端催什,而服務(wù)端是ServiceManager啃匿。
至于Binder驅(qū)動,則可以看成是一個連接客戶端和服務(wù)端的橋梁蛆楞,相當(dāng)于一個虛擬的硬件設(shè)備。
進(jìn)程間通信其實就是交互數(shù)據(jù)對吧夹厌,那么Client就是要獲取數(shù)據(jù)的進(jìn)程豹爹,Server就是提供數(shù)據(jù)的進(jìn)程。
我的理解是:Binder 機(jī)制就是 Client 和 Server 在 ServiceManager 進(jìn)程的管理下矛纹,通過Binder驅(qū)動進(jìn)行通信臂聋。
先從ServiceManager說起
ServiceManager 是系統(tǒng)級的服務(wù),單獨運行在一個進(jìn)程中,從字面意思來看孩等,它是用來管理 Service 的艾君。Service 就是 Server 提供的服務(wù)。那么它是怎么管理的呢肄方?
首先冰垄,假設(shè)如果讓我們自己管理一堆服務(wù)我們會怎么辦呢?
直接用服務(wù)的名字么权她?大部分人應(yīng)該會選擇給每個服務(wù)分配一個獨一無二的ID吧虹茶,其實 ServiceManager 也是這么做的。
只不過ServiceManager用的是句柄隅要。句柄是啥蝴罪?英文Handle,簡單點說就是一個整數(shù)值步清,可以看作是 service 的ID要门。
ServiceManager錄了所有系統(tǒng)service所對應(yīng)的Binder句柄,它的核心功能就是維護(hù)好這些句柄值廓啊。因此對于Server來說欢搜,在ServiceManager中注冊就是創(chuàng)建自己的句柄值,而Client查詢也是查詢目標(biāo)服務(wù)的句柄值崖瞭。通過句柄值狂巢,Client 就可以獲取對應(yīng)的 Service 的Binder 代理,從而完成通信书聚。
那么唧领,之前提到 ServiceManager 也是一個單獨的進(jìn)程,那么其他進(jìn)程是如何獲取它的句柄值的呢雌续?
ServiceManager 本身的句柄值固定為0斩个,即handle=0。因此驯杜,無論是Client受啥,還是 Server 都可以通過這個固定值直接獲得 ServiceManager 的Binder代理,從而和ServiceManager進(jìn)行通信鸽心。
另外滚局,句柄值都是ServiceManager 維護(hù)的,不同進(jìn)程中指代相同Binder實體的句柄值可能是不同的顽频,即A進(jìn)程拿到的句柄值和B進(jìn)程拿到的句柄值可能不同藤肢,但指代的是同一個 Serivce。
Binder 機(jī)制的流程
- Android系統(tǒng)啟動時會啟動糯景,ServiceManager進(jìn)程嘁圈。ServiceManager 注冊自己的handle值為0省骂。
- ServiceManager在死循環(huán)中,不停地去讀內(nèi)核中binder driver最住,看是否有新的請求(注冊Service或查詢Service)
- Server 創(chuàng)建一個handle為0的代理binder钞澳,向ServiceManager注冊自己。
- Server 進(jìn)入無限循環(huán)涨缚,不停地去讀內(nèi)核中binder driver的請求數(shù)據(jù)轧粟,如果是發(fā)送給自己的,解包Parcel對象仗岖,處理并將結(jié)果返回逃延。
- Client 創(chuàng)建一個handle為0的代理binder,向ServiceManager 查詢Service 的handle值轧拄。
- Client 通過請求到的 handle值揽祥,獲得該service 的代理binder ,通過代理binder調(diào)用service的方法檩电。