本文已經收錄到個人Github我的博客,歡迎大佬們光臨寒舍:html
個人GIthub博客java
IPC
?IPC
是Inter-Process Communication
的縮寫,含義是進程間通訊,是指兩個進程之間進行數據交換的過程。linux
有些讀者可能疑惑: "那什麼是進程呢?什麼是線程呢?多進程和多線程有什麼區別呢?"android
二者關係:一個進程可包含多個線程,即一個應用程序上能夠同時執行多個任務。git
- 主線程(UI線程):UI操做
- 有限個子線程:耗時操做
注意:不可在主線程作大量耗時操做,會致使ANR(應用無響應)。解決辦法:將耗時任務放在線程中。github
IPC
不是Android所特有的,Android中最有特點的IPC
方式是Binder
。而平常開發中涉及到的知識:AIDL,插件化,組件化等等,都離不開Binder。因而可知,IPC
是挺重要的。數據庫
Android
中的多進程模式Q1:開啓多線程的方式:緩存
AndroidMenifest
中給四大組件指定屬性android:process
precess的命名規則:安全
- 默認進程:沒有指定該屬性則運行在默認進程,其進程名就是包名。
- 以「:」爲命名開頭的進程:「:」的含義是在進程名前面加上包名,屬於當前應用私有進程
- 完整命名的進程:屬於全局進程,其餘應用能夠經過ShareUID方式和他跑在用一個進程中(須要ShareUID和簽名相同)。
Q2:多進程模式的運行機制:服務器
Andoird爲每一個進程分配了一個獨立的虛擬機,不一樣虛擬機在內存分配上有不一樣的地址空間,這也致使了不一樣虛擬機中訪問同一個對象會產生多份副本。
帶來四個方面的問題:
- 靜態變量和單例模式失效-->緣由:不一樣虛擬機中訪問同一個對象會產生多份副本。
- 線程同步機制失效-->緣由:內存不一樣,線程沒法同步。
- SharedPreference的可靠性降低-->緣由:底層是經過讀寫XML文件實現的,發生併發問題。
- Application屢次建立-->緣由:Android系統會爲新的進程分配獨立虛擬機,至關於應用從新啓動了一次。
IPC
基礎概念這裏主要介紹三方面內容:
- Serializable
- Parcelable
- Binder
只有熟悉這三方面的內容,才能更好理解
IPC
的各類方式
Java提供的序列化接口,使用方式比較簡單:
- 實體類實現Serializable
- 手動設置/系統自動生成serialVersionUID
//Serializable Demo
public class Person implements Serializable{
private static final long serialVersionUID = 7382351359868556980L;
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
複製代碼
這裏特別注意一下serialVersionUID:
- 含義:是Serializable接口中用來輔助序列化和反序列化過程。
- 注意:原則上序列化後的數據中的serialVersionUID要和當前類的serialVersionUID 相同才能正常的序列化。當類發生很是規性變化(修改了類名/修改了成員變量的類型)的時候,序列化失敗。
是Android中的序列化接口,使用的時候,類中須要實現下面幾點:
- 實現Parcelable接口
- 內容描述
- 序列化方法
- 反序列化方法
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
//返回內容描述
public int describeContents() {
return 0;
}
//序列化
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
//反序列化
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
//從序列化的對象中建立原始對象
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
//從序列化的對象中建立原始對象
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
@Override
public String toString() {
return String.format("User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
}
}
複製代碼
Serializable接口 | Parcelable接口 | |
---|---|---|
平臺 | Java | Andorid |
序列化原理 | 將一個對象轉換成可存儲或者可傳輸的狀態 | 將對象進行分解,且分解後的每一部分都是傳遞可支持的數據類型 |
優缺點 | 優勢:使用簡單 缺點:開銷大(由於須要進行大量的IO操做) | 優勢:高效 缺點:使用麻煩 |
使用場景 | 將對象序列化到存儲設備或者經過網絡傳輸 | 主要用在內存序列化上 |
Q1:Binder是什麼?
Q2:Android是基於Linux內核基礎上設計的,卻沒有把管道/消息隊列/共享內存/信號量/Socket等一些IPC通訊手段做爲Android的主要IPC方式,而是新增了Binder機制,其優勢有:
A1:傳輸效率高、可操做性強
傳輸效率主要影響因素是內存拷貝的次數,拷貝次數越少,傳輸速率越高。幾種數據傳輸方式比較
方式 | 拷貝次數 | 操做難度 |
---|---|---|
Binder | 1 | 簡易 |
消息隊列 | 2 | 簡易 |
Socket | 2 | 簡易 |
管道 | 2 | 簡易 |
共享內存 | 0 | 複雜 |
從Android進程架構角度分析:對於消息隊列、Socket和管道來講,數據先從發送方的緩存區拷貝到內核開闢的緩存區中,再從內核緩存區拷貝到接收方的緩存區,一共兩次拷貝,如圖:
對Binder來講:數據從發送方的緩存區拷貝到內核的緩存區,而接收方的緩存區與內核的緩存區是映射到同一塊物理地址的,節省了一次數據拷貝的過程
A2:實現C/S架構方便
Linux的衆IPC方式除了Socket之外都不是基於C/S架構,而Socket主要用於網絡間的通訊且傳輸效率較低。Binder基於C/S 架構 ,Server端與Client端相對獨立,穩定性較好。
A3:安全性高
傳統Linux IPC的接收方沒法得到對方進程可靠的UID/PID,從而沒法鑑別對方身份;而Binder機制爲每一個進程分配了UID/PID且在Binder通訊時會根據UID/PID進行有效性檢測。
Q3:Binder框架定義了四個角色呢?
A1:Server&Client
服務器&客戶端。在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通訊。
A2:ServiceManager:
服務的管理者,將Binder名字轉換爲Client中對該Binder的引用,使得Client能夠經過Binder名字得到Server中Binder實體的引用。
A3:Binder驅動
ioctl(input/output control)是一個專用於設備輸入輸出操做的系統調用,該調用傳入一個跟設備有關的請求碼,系統調用的功能徹底取決於請求碼
Q4:Binder 工做原理是什麼
當發出遠程請求後客戶端會掛起,直到返回數據纔會喚醒Client
Q5:當服務端進程異常終止的話,形成Binder死亡的話,怎麼辦?
在客戶端綁定遠程服務成功後,給Binder設置死亡代理,當Binder死亡的時候,咱們會收到通知,從而從新發起鏈接請求。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:這裏從新綁定遠程Service
}
}
複製代碼
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
複製代碼
IPC
方式Android中的
IPC
方式有不少種,可是都與Binder
有或多或少的關係
小課堂測試:在A進程進行計算後的結果不是Bundle所支持的數據類型,該如何傳給B進程?
Answer: 將在A進程進行的計算過程轉移到B進程中的一個Service裏去作,這樣可成功避免進程間的通訊問題。
AIDL(Android Interface Definition Language,Android接口定義語言):若是在一個進程中要調用另外一個進程中對象的方法,可以使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,經過它客戶端實現間接調用服務端對象的方法。
想了解String和CharSequence區別的讀者,能夠看下這篇文章:String和CharSequence的區別
注意:除了基本數據類型,其它類型的參數必須標上方向:in、out或inout,用於表示在跨進程通訊中數據的流向。
注意:
- 自定義的Parcelable對象必須把java文件和自定義的AIDL文件顯式的import進來,不管是否在同一包內。
- AIDL文件用到自定義Parcelable的對象,必須新建一個和它同名的AIDL文件,並在其中聲明它爲Parcelable類型。
a:本質是系統提供了一套可快速實現Binder的工具。
b:關鍵類和方法是什麼?
Binder
的實現類,服務端經過這個類來提供服務。asInterface()
:客戶端調用,將服務端的返回的Binder對象,轉換成客戶端所須要的AIDL接口類型對象。返回對象:
- 若客戶端和服務端位於同一進程,則直接返回Stub對象自己;
- 不然,返回的是系統封裝後的Stub.proxy對象。
asBinder()
:返回代理Proxy的Binder對象。onTransact()
:運行服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會經過系統底層封裝後交由此方法來處理。transact()
:運行在客戶端,當客戶端發起遠程請求的同時將當前線程掛起。以後調用服務端的onTransact()直到遠程請求返回,當前線程才繼續執行。若是感興趣的讀者想要了解具體的AIDL實現IPC的流程,筆者分享一篇文章:android跨進程通訊(IPC):使用AIDL
A.服務端:
B.客戶端:
總結:服務端裏的某個Service給和它綁定的特定客戶端進程提供Binder對象,客戶端經過AIDL接口的靜態方法asInterface() 將Binder對象轉化成AIDL接口的代理對象,經過這個代理對象就能夠發起遠程調用請求。
A.客戶端:
B.服務端:
解決客戶端頻繁調用服務器方法致使性能極大損耗的辦法:實現觀察者模式。
即當客戶端關注的數據發生變化時,再讓服務端通知客戶端去作相應的業務處理。
須要用到RemoteCallBackList:Android系統專門提供的用於刪除跨進程listener的接口。其內部自動實現了線程同步的功能。
Q1.什麼是Messager?
A1:Messager是輕量級的IPC方案,經過它可在不一樣進程中傳遞Message對象。
Messenger.send(Message);
複製代碼
Q2:特色是什麼?
Messenge可支持的數據類型:
- arg一、arg二、what字段:int型數據
- obj字段:Object對象,支持系統提供的Parcelable對象
- setData:Bundle對象
Q3:實現的方法:
讀者若是對Messenger的具體使用感興趣的話,能夠看下這篇文章:IPC-Messenger使用實例
A1:服務端:
onBind()
中返回Messenger對應的Binder對象。A2:客戶端:
經過bindService綁定服務端的Service;
經過綁定後返回的IBinder對象建立一個Messenger,進而可向服務器端進程發送Message數據。(至此只完成單向通訊)
在客戶端建立一個Handler並由此建立一個Messenger,並經過Message的replyTo字段傳遞給服務器端進程。服務端經過讀取Message獲得Messenger對象,進而向客戶端進程傳遞數據。(完成雙向通訊)
Q4:缺點:
解決方式:使用AIDL的方式處理IPC以應對高併發的場景
ContentProvider是Android提供的專門用來進行不一樣應用間數據共享的方式,底層一樣是經過Binder實現的。
Socket不只能夠跨進程,還能夠跨設備通訊
Q1:使用類型是什麼?
Q2:實現方法是什麼?
A1:服務端:
A2:客戶端:
名稱 | 優勢 | 缺點 | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸Bundle支持的數據類型 | 四大組件間的進程間通訊 |
文件共享 | 簡單易用 | 不適合高併發場景,沒法作到進程間的即時通訊 | 無併發訪問,交換簡單數據且實時性不高 |
AIDL | 支持一對多併發和實時通訊 | 使用稍複雜,須要處理線程同步 | 一對多且有RPC需求 |
Messenger | 支持一對多串行通訊 | 不能很好處理高併發,不支持RPC,只能傳輸Bundle支持的數據類型 | 低併發的一對多 |
ContentProvider | 支持一對多併發數據共享 | 可理解爲受約束的AIDL | 一對多進程間數據共享 |
Socket | 支持一對多併發數據共享 | 實現細節繁瑣 | 網絡數據交換 |
有多個業務模塊都須要AIDL來進行IPC,此時須要爲每一個模塊建立特定的aidl文件,那麼相應的Service就會不少。必然會出現系統資源耗費嚴重、應用過分重量級的問題。所以須要Binder鏈接池,經過將每一個業務模塊的Binder請求統一轉發到一個遠程Service中去執行的方式,從而避免重複建立Service。
Q1:工做原理是什麼?
每一個業務模塊建立本身的AIDL接口並實現此接口,而後向服務端提供本身的惟一標識和其對應的Binder對象。服務端只須要一個Service,服務器提供一個queryBinder接口,它會根據業務模塊的特徵來返回相應的Binder對像,不一樣的業務模塊拿到所需的Binder對象後就可進行遠程方法的調用了。
Q2:實現方式是什麼?
讀者若是對具體的實現方式感興趣的話,能夠看一下這篇文章:Android IPC機制(四):細說Binder鏈接池
恭喜你,已經完成了此次奇妙的
IPC
之旅了,若是你感到對概念仍是有點模糊不清的話,不要緊,很正常,不用太糾結於細節,你能夠繼續進行下面的旅程了,將來的你,再看這篇文章,也許會有更深的體會,到時候就會有茅舍頓開的感受了。將來的你,必定會更優秀!!!路漫漫其修遠兮,吾將上下而求索。《離騷》--屈原
若是文章對您有一點幫助的話,但願您能點一下贊,您的點贊,是我前進的動力
本文參考連接: