要點提煉|開發藝術之IPC

釐米姑娘發表於2019-03-02

在上一篇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介面的比較:

要點提煉|開發藝術之IPC

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程式都是獨立的,且都由兩部分組成,一部分是使用者空間,另一部分是核心空間,如下圖:

要點提煉|開發藝術之IPC

如此設計的優點:

  • 穩定性、安全性高:每一個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和管道來說,資料先從傳送方的快取區拷貝到核心開闢的快取區中,再從核心快取區拷貝到接收方的快取區,一共兩次拷貝,如圖:

要點提煉|開發藝術之IPC

而對於Binder來說,資料從傳送方的快取區拷貝到核心的快取區,而接收方的快取區與核心的快取區是對映到同一塊實體地址的,節省了一次資料拷貝的過程,如圖:

要點提煉|開發藝術之IPC

由於共享記憶體操作複雜,綜合來看,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驅動執行於核心空間。關係如圖:

要點提煉|開發藝術之IPC

下面簡單介紹這四個角色:

  • ServiceManager:服務的管理者,將Binder名字轉換為Client中對該Binder的引用,使得Client可以通過Binder名字獲得Server中Binder實體的引用。流程如圖:

要點提煉|開發藝術之IPC

  • 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:給某個物件提供一個代理物件,並由代理物件控制對原物件的訪問。如圖:

要點提煉|開發藝術之IPC

代理模式的組成:

  • 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方式

image

由上圖可以看到,其他一些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的流程:

要點提煉|開發藝術之IPC

推薦閱讀Android中AIDL的工作原理

e.實現方法

  • 服務端:
    • 建立一個aidl檔案
    • 建立一個Service,實現AIDL的介面函式並暴露AIDL介面。
  • 客戶端:
    • 通過bindService繫結服務端的Service;
    • 繫結成功後,將服務端返回的Binder物件轉化成AIDL介面所屬的型別,進而呼叫相應的AIDL中的方法。

總結:服務端裡的某個Service給和它繫結的特定客戶端程式提供Binder物件,客戶端通過AIDL介面的靜態方法asInterface() 將Binder物件轉化成AIDL介面的代理物件,通過這個代理物件就可以發起遠端呼叫請求。

例項使用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的介面。其內部自動實現了執行緒同步的功能。

推薦文章Android:學習AIDL,這一篇文章就夠了


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物件,進而向客戶端程式傳遞資料。(完成雙向通訊)

要點提煉|開發藝術之IPC

例項使用Messenger實現IPC

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(套接字):不僅可跨程式,還可以跨裝置通訊。

要點提煉|開發藝術之IPC

b.使用型別

  • 流套接字:基於TCP協議,採用流的方式提供可靠的位元組流服務。
  • 資料包套接字:基於UDP協議,採用資料包文提供資料打包傳送的服務。

c.實現方法:TCP/UDP

  • 服務端:
    • 建立一個Service,線上程中建立TCP服務、監聽相應的埠等待客戶端連線請求;
    • 與客戶端連線時,會生成新的Socket物件,利用它可與客戶端進行資料傳輸;
    • 與客戶端斷開連線時,關閉相應的Socket並結束執行緒。
  • 客戶端:
    • 開啟一個執行緒、通過Socket發出連線請求;
    • 連線成功後,讀取服務端訊息;
    • 斷開連線,關閉Socket。

要點提煉|開發藝術之IPC

d.注意:使用Socket進行通訊

  • 需要宣告許可權
<uses-permission android:name="android.permission.INTERNET" />  
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
複製程式碼
  • 能在主執行緒中訪問網路

推薦閱讀:這是一份很詳細的Socket使用攻略

綜上,以上六種IPC方式的優缺點和使用場景見下圖:

要點提煉|開發藝術之IPC


四.Binder連線池

a.背景:有多個業務模組都需要AIDL來進行IPC,此時需要為每個模組建立特定的aidl檔案,那麼相應的Service就會很多。必然會出現系統資源耗費嚴重、應用過度重量級的問題。

b.作用:將每個業務模組的Binder請求統一轉發到一個遠端Service中去執行,從而避免重複建立Service。

c.工作原理:每個業務模組建立自己的AIDL介面並實現此介面,然後向服務端提供自己的唯一標識和其對應的Binder物件。服務端只需要一個Service,伺服器提供一個queryBinder介面,它會根據業務模組的特徵來返回相應的Binder對像,不同的業務模組拿到所需的Binder物件後就可進行遠端方法的呼叫了。流程如圖:

要點提煉|開發藝術之IPC

d.實現方式:

  • 為每個業務模組建立AIDL介面並具體實現;
  • 為Binder連線池建立AIDL介面IBinderPool.aidl並具體實現;
  • 遠端服務BinderPoolService的實現,在onBind()返回例項化的IBinderPool實現類物件;
  • Binder連線池的具體實現,來繫結遠端服務。
  • 客戶端的呼叫。

例項細說Binder連線池

現在可以回答以下問題:

Q:在Android開發中提高開發效率的方法?

A:使用Binder連線池,避免反覆建立Service,統一管理和維護AIDL。

推薦閱讀Android的IPC機制Android跨程式通訊IPC


希望這篇文章對你有幫助~

相關文章