要點提煉|開發藝術之IPC

在上一篇Window裏說起過IPC,本篇將詳細總結IPC,知識點以下:

  • IPC基礎及概念
    • 多進程模式
    • 序列化
      • Serializable接口
      • Parcelable接口
    • Binder機制
  • IPC方式
    • Bundle
    • 文件共享
    • AIDL
    • Messager
    • ContentProvider
    • Socket
  • Binder鏈接池

1、IPC基礎及概念html

1.多進程模式java

a.進程&線程linux

  • 進程:通常指一個執行單元,在PC和移動設備上指一個程序應用
  • 線程:CPU調度的最小單元。線程是一種有限的系統資源。

二者關係: 一個進程可包含多個線程,即一個應用程序上能夠同時執行多個任務。android

  • 主線程(UI線程):UI操做
  • 有限個子線程:耗時操做

注意:不可在主線程作大量耗時操做,會致使ANR(應用無響應)。git

b.開啓多進程模式的方式shell

  • (不經常使用)經過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爲每一個應用分配了獨立的虛擬機,不一樣的虛擬機在內存分配上有不一樣的地址空間,這會致使在不一樣的虛擬機中訪問同一個類的對象會產生多份副本。bash

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系列


3、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的流程:

推薦閱讀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可支持的數據類型。
    • arg一、arg二、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對象,進而向客戶端進程傳遞數據。(完成雙向通訊)

實例使用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(套接字):不只可跨進程,還能夠跨設備通訊。

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


但願這篇文章對你有幫助~

相關文章
相關標籤/搜索