在上一篇Window裡提及過IPC,本篇將詳細總結IPC,知識點如下:
- IPC基礎及概念
- 多程式模式
- 序列化
- Serializable介面
- Parcelable介面
- Binder機制
- IPC方式
- Bundle
- 檔案共享
- AIDL
- Messager
- ContentProvider
- Socket
- Binder連線池
一、IPC基礎及概念
1.多程式模式
a.程式&執行緒
- 程式:一般指一個執行單元,在PC和移動裝置上指一個程式或應用。
- 執行緒:CPU排程的最小單元。執行緒是一種有限的系統資源。
兩者關係: 一個程式可包含多個執行緒,即一個應用程式上可以同時執行多個任務。
- 主執行緒(UI執行緒):UI操作
- 有限個子執行緒:耗時操作
注意:不可在主執行緒做大量耗時操作,會導致ANR(應用無響應)。
b.開啟多程式模式的方式:
- (不常用)通過JNI在native層fork一個新的程式。
- (常用)在AndroidMenifest中給四大元件指定屬性
android:process
,程式名的命名規則:- 預設程式:沒有指定該屬性則執行在預設程式,其程式名就是包名。
- 以“:”開頭的程式:
- 省略包名,如
android:process=":remote"
,表示程式名為com.example.myapplication:remote
。 - 屬於當前應用的私有程式,其他程式的元件不能和他跑在同一程式中。
- 省略包名,如
- 完整命名的程式:
- 如
android:process="com.example.myapplication.remote"
。 - 屬於全域性程式,其他應用可以通過ShareUID方式和他跑在用一個程式中。
- 如
UID&ShareUID:
- Android系統為每個應用分配一個唯一的UID,具有相同UID的應用才能共享資料。
- 兩個應用通過ShareUID跑在同一程式的條件:ShareUID相同且簽名也相同。
- 滿足上述條件的兩個應用,無論是否跑在同一程式,它們可共享data目錄,元件資訊。
- 若跑在同一程式,它們除了可共享data目錄、元件資訊,還可共享記憶體資料。它們就像是一個應用的兩個部分。
c.檢視程式資訊的方法:
- 通過DDMS檢視檢視程式資訊。
- 通過shell檢視,命令為:
adb shell ps|grep 包名
。
d.需要程式間通訊的必要性:所有執行在不同程式的四大元件,只要它們之間需要通過記憶體在共享資料,都會共享失敗。
原因:由於Android為每個應用分配了獨立的虛擬機器,不同的虛擬機器在記憶體分配上有不同的地址空間,這會導致在不同的虛擬機器中訪問同一個類的物件會產生多份副本。
e.多程式造成的影響,總結為以下四方面:
①靜態變數和單例模式失效。
- 由獨立的虛擬機器造成。
②執行緒同步機制失效。
- 原因同上。
③SharedPreference的不可靠下降。
- SharedPreferences不支援兩個程式同時進行讀寫操作,即不支援併發讀寫,有一定機率導致資料丟失。
④Application多次建立。
- Android系統會為新的程式分配獨立虛擬機器,相當於系統又把這個應用重新啟動了一次。
推薦閱讀:關於Android多程式
2.序列化
a.序列化的介紹
- 含義:序列化表示將一個物件轉換成可儲存或可傳輸的狀態。序列化後的物件可以在網路上進行傳輸,也可以儲存到本地。
- 場景:需要通過Intent和Binder等傳輸類物件就必須完成物件的序列化過程。
- 兩種方式:實現Serializable/Parcelable介面。
b.Serializable介面和Parcelable介面的比較:
c.serialVersionUID
- 含義:是Serializable介面中用來輔助序列化和反序列化過程。
- 注意:原則上序列化後的資料中的serialVersionUID要和當前類的serialVersionUID 相同才能正常的序列化。
注意:兩種變數不會參與序列化過程:
- 靜態成員變數屬於類,不屬於物件。
- 用transient關鍵字標記的成員變數。
推薦閱讀:序列化Serializable和Parcelable的理解和區別
3.IPC簡介
a.IPC(Inter-Process Communication,跨程式通訊):指兩個程式之間進行資料交換的過程。
b.任何一個作業系統都有對應的IPC機制。
- Windows:通過剪下板、管道、油槽等進行程式間通訊。
- Linux:通過名稱空間、共享內容、訊號量等進行程式間通訊。
- Android:沒有完全繼承Linux,比如,其獨具特色的通訊方式有Binder、Socket等等。
c.IPC的使用場景:
- 由於某些原因,應用自身需要採用多程式模式來實現。可能原因有:
- 某些模組因特殊原因要執行在單獨程式中;
- 為加大一個應用可使用的記憶體,需通過多程式來獲取多份記憶體空間。
- 當前應用需要向其它應用獲取資料。
d.Android的程式架構:每一個Android程式都是獨立的,且都由兩部分組成,一部分是使用者空間,另一部分是核心空間,如下圖:
如此設計的優點:
- 穩定性、安全性高:每一個Android程式都擁有自己獨立的虛擬地址空間,一方面可以限制其他程式訪問自己的虛擬地址空間;另一方面,當一個程式崩潰時不至於“火燒連營”。
- 便於複用與管理:核心共享有助於系統維護和併發操作、節省空間。
4.Binder機制
a.概念:
- 從API角度:是一個類,實現IBinder介面。
- 從IPC角度:是Android中的一種跨程式通訊方式。
- 從Framework角度:是ServiceManager連線各種Manager和相應ManagerService的橋樑。
- 從應用層:是客戶端和服務端進行通訊的媒介。客戶端通過它可獲取服務端提供的服務或者資料。
b.Android是基於Linux核心基礎上設計的,卻沒有把管道/訊息佇列/共享記憶體/訊號量/Socket等一些IPC通訊手段作為Android的主要IPC方式,而是新增了Binder機制,其優點有:
- 傳輸效率高、可操作性強:傳輸效率主要影響因素是記憶體拷貝的次數,拷貝次數越少,傳輸速率越高。幾種資料傳輸方式比較:
方式 | 拷貝次數 | 操作難度 |
---|---|---|
Binder | 1 | 簡易 |
訊息佇列 | 2 | 簡易 |
Socket | 2 | 簡易 |
管道 | 2 | 簡易 |
共享記憶體 | 0 | 複雜 |
從Android程式架構角度分析:對於訊息佇列、Socket和管道來說,資料先從傳送方的快取區拷貝到核心開闢的快取區中,再從核心快取區拷貝到接收方的快取區,一共兩次拷貝,如圖:
而對於Binder來說,資料從傳送方的快取區拷貝到核心的快取區,而接收方的快取區與核心的快取區是對映到同一塊實體地址的,節省了一次資料拷貝的過程,如圖:
由於共享記憶體操作複雜,綜合來看,Binder的傳輸效率是最好的。
-
實現C/S架構方便:Linux的眾IPC方式除了Socket以外都不是基於C/S架構,而Socket主要用於網路間的通訊且傳輸效率較低。Binder基於C/S 架構 ,Server端與Client端相對獨立,穩定性較好。
-
安全性高:傳統Linux IPC的接收方無法獲得對方程式可靠的UID/PID,從而無法鑑別對方身份;而Binder機制為每個程式分配了UID/PID且在Binder通訊時會根據UID/PID進行有效性檢測。
c.Binder框架定義了四個角色:Server,Client,ServiceManager和Binder驅動。
其中Server、Client、ServiceManager執行於使用者空間,Binder驅動執行於核心空間。關係如圖:
下面簡單介紹這四個角色:
- ServiceManager:服務的管理者,將Binder名字轉換為Client中對該Binder的引用,使得Client可以通過Binder名字獲得Server中Binder實體的引用。流程如圖:
- Binder驅動:
- 與硬體裝置沒有關係,其工作方式與裝置驅動程式是一樣的,工作於核心態。
- 提供open()、mmap()、poll()、**ioctl()**等標準檔案操作。
- 以字元驅動裝置中的misc裝置註冊在裝置目錄/dev下,使用者通過/dev/binder訪問該它。
- 負責程式之間binder通訊的建立,傳遞,計數管理以及資料的傳遞互動等底層支援。
- 驅動和應用程式之間定義了一套介面協議,主要功能由**ioctl()**介面實現,由於ioctl()靈活、方便且能夠一次呼叫實現先寫後讀以滿足同步互動,因此不必分別呼叫write()和read()介面。
- 其程式碼位於linux目錄的drivers/misc/binder.c中。
- Server&Client:伺服器&客戶端。在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通訊。
d.代理模式Proxy:給某個物件提供一個代理物件,並由代理物件控制對原物件的訪問。如圖:
代理模式的組成:
- Abstarct Subject(抽象主題):宣告Real Subject和Proxy的共同介面,這樣在任何可以使用Real Subject的地方都可以使用Proxy。
- Real Subject(真實主題):定義了Proxy所代表的Real Subject。
- Proxy Subject(代理主題):
- 內部含有Real Subject的引用,可在任何時候操作目標物件;
- 提供一個與Real Subject相同的介面,可在任何時候替代目標物件。
推薦閱讀:代理模式
e.Binder 工作原理:
- 伺服器端:在服務端建立好了一個Binder物件後,內部就會開啟一個執行緒用於接收Binder驅動傳送的訊息,收到訊息後會執行onTranscat(),並按照引數執行不同的服務端程式碼。
- Binder驅動:在服務端成功Binder物件後,Binder驅動也會建立一個mRemote物件(也是Binder類),客戶端可藉助它呼叫transcat()即可向服務端傳送訊息。
- 客戶端:客戶端要想訪問Binder的遠端服務,就必須獲取遠端服務的Binder物件在Binder驅動層對應的mRemote引用。當獲取到mRemote物件的引用後,就可以呼叫相應Binder物件的暴露給客戶端的方法。
後面會通過AIDL和Messager更深刻地體會這一工作原理。
推薦閱讀:Android - Binder驅動、Binder設計與實現、Binder系列、
三、IPC方式
由上圖可以看到,其他一些IPC方式實際都是通過Binder來實現,只不過封裝方式不同。接下來分別總結其他六種IPC方式:
1.使用Bundle
a.Bundle:支援在Activity、Service和Receiver之間通過**Intent.putExtra()**傳遞Bundle資料。
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("xxx","xxx");
intent.putExtra("data", bundle);
複製程式碼
b.原理:Bundle實現Parcelable介面,它可方便的在不同的程式中傳輸。
c.注意:Bundle不支援的資料型別無法在程式中被傳遞。
思考下面這種情況: Q:在A程式進行計算後的結果不是Bundle所支援的資料型別,該如何傳給B程式? A:將在A程式進行的計算過程轉移到B程式中的一個Service裡去做,這樣可成功避免程式間的通訊問題。
推薦閱讀:通過Bundle在Android Activity間傳遞資料
2.使用檔案共享
a.檔案共享:兩個程式通過讀/寫同一個檔案來交換資料。比如A程式把資料寫入檔案,B程式通過讀取這個檔案來獲取資料。
b.適用情況:對資料同步要求不高的程式之間進行通訊,並且要妥善處理併發讀/寫的問題。
c.雖然SharedPreferences也是檔案儲存的一種,但不建議採用。
- 原因:系統對SharedPreferences的讀/寫有一定的快取策略,即在記憶體中有一份該檔案的快取,因此在多程式模式下,其讀/寫會變得不可靠,甚至丟失資料。
3.使用AIDL
a.AIDL(Android Interface Definition Language,Android介面定義語言):如果在一個程式中要呼叫另一個程式中物件的方法,可使用AIDL生成可序列化的引數,AIDL會生成一個服務端物件的代理類,通過它客戶端實現間接呼叫服務端物件的方法。
b.可支援的資料型別:
- 基本資料型別:byte,int,long,float,double,boolean,char
- String型別
- CharSequence型別
- ArrayList、HashMap且裡面的每個元素都能被AIDL支援
- 實現Parcelable介面的物件
- 所有AIDL介面本身
注意:除了基本資料型別,其它型別的引數必須標上方向:in、out或inout,用於表示在跨程式通訊中資料的流向。
- in
- 表示資料只能由客戶端流向服務端。
- 服務端將會接收到這個物件的完整資料,但在服務端修改它不會對客戶端輸入的物件產生影響。
- out
- 表示資料只能由服務端流向客戶端。
- 服務端將會接收到這個物件的的空物件,但在服務端對接收到的空物件有任何修改之後客戶端將會同步變動。
- inout
- 表示資料可在服務端與客戶端之間雙向流通。
- 服務端將會接收到客戶端傳來物件的完整資訊,且客戶端將會同步服務端對該物件的任何變動。
c.兩種AIDL檔案:
- 用於定義parcelable物件,以供其他AIDL檔案使用AIDL中非預設支援的資料型別的。
- 用於定義方法介面,以供系統使用來完成跨程式通訊的。
注意:
- 自定義的Parcelable物件必須把java檔案和自定義的AIDL檔案顯式的import進來,無論是否在同一包內。
- AIDL檔案用到自定義Parcelable的物件,必須新建一個和它同名的AIDL檔案,並在其中宣告它為Parcelable型別。
d.AIDL的本質是系統提供了一套可快速實現Binder的工具。關鍵類和方法:
- AIDL介面:繼承IInterface。
- Stub類:Binder的實現類,服務端通過這個類來提供服務。
- Proxy類:伺服器的本地代理,客戶端通過這個類呼叫伺服器的方法。
- asInterface():客戶端呼叫,將服務端的返回的Binder物件,轉換成客戶端所需要的AIDL介面型別物件。返回物件:
- 若客戶端和服務端位於同一程式,則直接返回Stub物件本身;
- 否則,返回的是系統封裝後的Stub.proxy物件。
- asBinder():根據當前呼叫情況返回代理Proxy的Binder物件。
- onTransact():執行服務端的Binder執行緒池中,當客戶端發起跨程式請求時,遠端請求會通過系統底層封裝後交由此方法來處理。
- transact():執行在客戶端,當客戶端發起遠端請求的同時將當前執行緒掛起。之後呼叫服務端的onTransact()直到遠端請求返回,當前執行緒才繼續執行。
通過此處例項具體瞭解AIDL實現IPC的流程:
推薦閱讀:Android中AIDL的工作原理
e.實現方法:
- 服務端:
- 建立一個aidl檔案;
- 建立一個Service,實現AIDL的介面函式並暴露AIDL介面。
- 客戶端:
- 通過bindService繫結服務端的Service;
- 繫結成功後,將服務端返回的Binder物件轉化成AIDL介面所屬的型別,進而呼叫相應的AIDL中的方法。
總結:服務端裡的某個Service給和它繫結的特定客戶端程式提供Binder物件,客戶端通過AIDL介面的靜態方法asInterface() 將Binder物件轉化成AIDL介面的代理物件,通過這個代理物件就可以發起遠端呼叫請求。
f.可能產生ANR的情形:
- 對於客戶端,且假設在主執行緒呼叫方法:
- 呼叫服務端的方法是執行在服務端的Binder執行緒池中,若所呼叫的方法裡執行了較耗時的任務,同時會導致客戶端執行緒長時間阻塞,易導致客戶端ANR。
- 在**onServiceConnected()和onServiceDisconnected()**裡直接呼叫服務端的耗時方法,易導致客戶端ANR。
- 對於服務端:
- 服務端的方法本身就執行在服務端的Binder執行緒中,可在其中執行耗時操作,而無需再開啟子執行緒。
- 回撥客戶端Listener的方法是執行在客戶端的Binder執行緒中,若所呼叫的方法裡執行了較耗時的任務,易導致服務端ANR。
g.解決客戶端頻繁呼叫伺服器方法導致效能極大損耗的辦法:實現觀察者模式。即當客戶端關注的資料發生變化時,再讓服務端通知客戶端去做相應的業務處理。
比如:每個客戶端的請求Listener傳遞給服務端,服務端用一個list儲存,當資料變化時伺服器再依次通知,此時客戶端就用Listener進行回撥處理。注意要用Handler切換到主執行緒。
h.AIDL 解註冊失敗
- 原因:Binder進行物件傳輸實際是通過序列化和反序列化進行,即Binder會把客戶端傳遞過來的物件重新轉化並生成一個新的物件,雖然在註冊和解註冊的過程中使用的是同一個客戶端物件,但經過Binder傳到服務端後會生成兩個不同的物件。另外,多次跨程式傳輸的同一個客戶端物件會在服務端生成不同的物件,但它們在底層的Binder物件是相同的。
- 解決辦法:當客戶端解註冊的時候,遍歷服務端所有的Listener,找到和解註冊Listener具有相同的Binder物件的服務端Listener,刪掉即可。
需要用到RemoteCallBackList:Android系統專門提供的用於刪除跨程式listener的介面。其內部自動實現了執行緒同步的功能。
4.使用Messager
a.Messenger:輕量級的IPC方案,通過它可在不同程式中傳遞Message物件。
Messenger.send(Message);
複製程式碼
相關記憶:
- Handler:主要進行執行緒間的資料通訊。
- Messenger:程式間的資料通訊。
b.特點:
- 底層實現是AIDL,即對AIDL進行了封裝,更便於進行程式間通訊。
- 其服務端以序列的方式來處理客戶端的請求,不存在併發執行的情形,故無需考慮執行緒同步的問題。
- 可在不同程式中傳遞Message物件,Messager可支援的資料型別即Messenge可支援的資料型別。
- arg1、arg2、what欄位:int型資料
- obj欄位:Object物件,支援系統提供的Parcelable物件
- setData:Bundle物件
- 有兩個建構函式,分別接收Handler物件和Binder物件。
c.實現方法:
- 服務端:
- 建立一個Service用於提供服務;
- 其中建立一個Handler用於接收客戶端程式發來的資料;
- 利用Handler建立一個Messenger物件;
- 在Service的**onBind()**中返回Messenger對應的Binder物件。
- 客戶端:
- 通過bindService繫結服務端的Service;
- 通過繫結後返回的IBinder物件建立一個Messenger,進而可向伺服器端程式傳送Message資料。(至此只完成單向通訊)
- 在客戶端建立一個Handler並由此建立一個Messenger,並通過Message的replyTo欄位傳遞給伺服器端程式。服務端通過讀取Message得到Messenger物件,進而向客戶端程式傳遞資料。(完成雙向通訊)
d.Message的缺點:
- 主要作用是傳遞 Message,難以實現遠端方法呼叫。
- 以序列的方式處理客戶端發來的訊息的,不適合高併發的場景。
解決辦法:考慮使用AIDL實現IPC。
推薦閱讀:超簡單的Binder,AIDL和Messenger的原理及使用流程
5.使用ContentProvider
a.ContentProvider:是Android提供的專門用來進行不同應用間資料共享的方式。
底層同樣是通過Binder實現的。
b.注意:
- 除了**onCreat()**執行在UI執行緒中,其他的query()、update()、insert()、delete()和getType()都執行在Binder執行緒池中。
- CRUD四大操作存在多執行緒併發訪問,要注意在方法內部要做好執行緒同步。
- 一個SQLiteDatabase內部對資料庫的操作有同步處理,但多個SQLiteDatabase之間無法同步。
基礎篇: 元件篇之ContentProvider
6.使用Socket
a.Socket(套接字):不僅可跨程式,還可以跨裝置通訊。
b.使用型別
- 流套接字:基於TCP協議,採用流的方式提供可靠的位元組流服務。
- 資料包套接字:基於UDP協議,採用資料包文提供資料打包傳送的服務。
c.實現方法:TCP/UDP
- 服務端:
- 建立一個Service,線上程中建立TCP服務、監聽相應的埠等待客戶端連線請求;
- 與客戶端連線時,會生成新的Socket物件,利用它可與客戶端進行資料傳輸;
- 與客戶端斷開連線時,關閉相應的Socket並結束執行緒。
- 客戶端:
- 開啟一個執行緒、通過Socket發出連線請求;
- 連線成功後,讀取服務端訊息;
- 斷開連線,關閉Socket。
d.注意:使用Socket進行通訊
- 需要宣告許可權:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
複製程式碼
- 不能在主執行緒中訪問網路
推薦閱讀:這是一份很詳細的Socket使用攻略
綜上,以上六種IPC方式的優缺點和使用場景見下圖:
四.Binder連線池
a.背景:有多個業務模組都需要AIDL來進行IPC,此時需要為每個模組建立特定的aidl檔案,那麼相應的Service就會很多。必然會出現系統資源耗費嚴重、應用過度重量級的問題。
b.作用:將每個業務模組的Binder請求統一轉發到一個遠端Service中去執行,從而避免重複建立Service。
c.工作原理:每個業務模組建立自己的AIDL介面並實現此介面,然後向服務端提供自己的唯一標識和其對應的Binder物件。服務端只需要一個Service,伺服器提供一個queryBinder介面,它會根據業務模組的特徵來返回相應的Binder對像,不同的業務模組拿到所需的Binder物件後就可進行遠端方法的呼叫了。流程如圖:
d.實現方式:
- 為每個業務模組建立AIDL介面並具體實現;
- 為Binder連線池建立AIDL介面IBinderPool.aidl並具體實現;
- 遠端服務BinderPoolService的實現,在onBind()返回例項化的IBinderPool實現類物件;
- Binder連線池的具體實現,來繫結遠端服務。
- 客戶端的呼叫。
例項:細說Binder連線池
現在可以回答以下問題:
Q:在Android開發中提高開發效率的方法?
A:使用Binder連線池,避免反覆建立Service,統一管理和維護AIDL。
推薦閱讀:Android的IPC機制、Android跨程式通訊IPC
希望這篇文章對你有幫助~