一、IPC的說明
IPC是Inter-Process Communication的縮寫,含義為進程間通信或跨進程通信,是指兩個進程之間進行數據交換的過程。
IPC不是Android獨有的,任何一個操作系統都需要有相應的IPC機制,比如Windows上可以通過剪貼板,管道和郵槽來進行進程間通信;Linux上可以通過命名管道、共享內容、信號量等進行進程間通信。
對於Android來說,它是一種基於Linux內核的移動操作系統,但它的進程間通信方式並不能完全繼承自Linux;相反,它有自己的進程間通信方式。
在Android中可以通過Binder輕松的實現進程間通信。Android還支持Socket,通過Socket可以實現任意兩個終端之間的通信,同一設備的兩個進程通過Socket通信自然也是可以的。
二、Android中IPC的使用
首先,只有面對多進程的情況才需要考慮進程間通信。多進程的情況分為兩種:
第一種是一個應用因為某些原因,自身需要采用多進程模式來實現,比如有些模塊需要運行在單獨的進程中、或者為了加大一個應用的可使用內存,需要通過多進程來獲取多分內存空間。
另一種是當前應用需要向其他的應用獲取數據,所以必須采用跨進程的方式來獲取數據,比如使用系統的ContentProvider去查詢數據。
通過給四大組件在AndroidMenifest中指定android:process屬性,可以輕易地開啟多進程模式。
三、Android中IPC帶來的問題
兩個應用共享數據:Android系統會為每個應用分配一個唯一的UID,具有相同UID的應用才能共享數據。兩個應用通過ShareUID跑在同一個進程是有要求的,需要這兩個應用有相同的ShareUID並且簽名相同才可以。在這種情況下,他們可以相互訪問對方的私有數據,比如data目錄,組件信息等,不管他們是否跑在同一個進程。
Android系統為每個應用分配了一個獨立的虛擬機,或者說為每一個進程都分配一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致在不同的虛擬機中訪問同一個對象會產生多分副本。所有運行在不同進程中的四大組件,只要它們之間需要通過內存來共享數據,都會共享失敗,這也是多進程帶來的主要影響。
一般來說,使用多進程會造成如下的問題:
(1)靜態成員和單例模式完全失效(不同的虛擬機中訪問同一個對象會產生多分副本)
(2)線程同步機制完全失效(不在同一塊內存,不管是所對象還是鎖全局類都無法保證線程同步)
(3)SharePreferences的可靠性下降(不支持兩個進程同時寫操作)
(4)Application會多次創建(因為創建新進程會分配獨立虛擬機,相當於啟動一個新的應用)
雖說不能直接的共享內存,但是通過跨進程通信還是可以實現數據交互。
四、Android中各種IPC方式
1、使用Bundle
四大組件中三大組件Activity、Service、Receiver都支持在Intent中傳遞Bundle數據。
由於Bundle實現了Parcelable接口,所以它可以很方便的在不同的進程間傳輸數據。當然我們傳輸的數據必須能夠被序列化,比如基本類型、實現了Parcelable接口的對象、實現了Serializable接口的對象以及一些Android支持的特殊對象。
2、使用文件共享
兩個進程通過讀寫同一個文件來交換數據,比如A進程把數據寫入文件,B進程通過讀取這個文件來獲取數據。
Android系統基於Linux,使得並發讀寫文件可以沒有限制的進行,甚至兩個線程同時對文件讀寫操作都是允許的,盡管可能出問題,因此文件共享方式適合在對數據同步要求不高的進程間進行通信。
SharedPreferences也屬於文件的一種,但是由於系統對它的讀寫有一定的緩存策略,即在內存中會有一份SharedPreferences文件的緩存;因此在多進程模式下,系統對它的讀寫就變得不可靠,會有很大幾率丟失數據,不建議在進程間通信中使用SharedPreferences。
3、使用Messenger
Messenger可以理解為信使,通過它可以再不同進程中傳遞Message對象,在Message中放入我們需要傳遞的數據,就可以實現數據的進程間傳遞了。
Messenger是一種輕量級的IPC方案,它的底層實現是AIDL。由於它一次處理一個請求,因此在服務端不需要考慮線程同步的問題,因為服務端不存在並發執行的情形。
4、使用AIDL
AIDL是 Android Interface definition language的縮寫,它是一種android內部進程通信接口的描述語言。AIDL可以處理發送到服務器端大量的並發請求(不同與Messenger的串行處理方式),也可以實現跨進程的方法調用。
在Android中使用方法:創建一個Service和一個AIDL接口,接着創建一個類繼承自AIDL接口中的Stub類並實現Stub中的抽象方法,在Service的onBind方法中返回這個類的對象,然后客戶端綁定服務端Service,建立連接后就可以訪問遠程服務器了。
5、使用ContentProvider
ContentProvider是Android中提供的專門用於不同應用間進行數據共享的方式,天生適合進程間通信。
ContentProvider的底層實現也是Binder,但是它的使用過程比AIDL簡單的多,因為系統做了封裝,使得無需關心細節即可輕松實現IPC。ContentProvider主要以表格的形式組織數據,和數據庫很類似,但ContentProvider對底層的數據存儲方式沒有任何要求,既可以使用Sqlite數據庫,也可以使用文件方式,甚至可以使用內存中的一個對象來存儲。
6、使用Socket
Socket套接字,是網絡通信中的概念,分為流式套接字和用戶數據奧套接字兩種,對應於網絡的傳輸控制層中的TCP和UDP協議。
兩個進程可以通過Socket來實現信息的傳輸,Socket本身可以支持傳輸任意字節流。
===選擇合適的IPC方式===,如下表
名稱 | 優點 | 缺點 | 適用場景 |
Bundle | 簡單易用 | 只能傳輸Bundle支持的數據類型 | 四大組件的進程間通信 |
文件共享 | 簡單易用 | 不適合高並發場景,並且無法做到進程間 即時通信 |
無並發訪問清醒,交換簡單的數據,實時性不搞的場景 |
AIDL | 功能強大,支持一對多並發通信,支持實時通信 | 使用稍復雜,需要處理好線程同步 | 一對多通信且有RPC需求 |
Messenger | 功能一般,支持一對多串行通信,支持實時通信 | 不能很好的處理高並發情形,不支持RPC,數據通過Message進行傳輸,因此只能傳輸Bundle支持的數據類型 | 低並發的一對多即時通信,無RPC需求 |
ContentProvider | 在數據源訪問方面功能強大,支持一對多並發數據共享, 可通過Call方法擴展其他操作 |
可以理解為受約束的AICL,主要提供數據的CRUD數據 | 一對多的進程間數據共享 |
Socket | 功能強大,可以通過網絡傳輸字節流,支持一對多並發實時通信 | 實現細節稍微繁瑣,不支持直接的RPC | 網絡數據交換 |
RPC(Remote Procedure Call,遠程過程調用)是種C/S的編程模式,出於一種類比的願望,在一台機器上運行的主程序,可以調用另一台機器上准備好的子程序。
通過RPC可以充分利用非共享內存的多處理器環境,可以簡便地將你的應用分布在多台工作站上,應用程序就像運行在一個多處理器的計算機上一樣。
五、Binder介紹
Binder是Android系統進程間通信方式之一。Linux已經擁有的進程間通信IPC手段包括: 管道(Pipe)、信號(Signal)、跟蹤(Trace)、插口(Socket)、報文隊列(Message)、共享內存(Share Memory)和信號量(Semaphore)。
Binder框架定義了四個角色:Server,Client,ServiceManager以及Binder驅動。
其中Server,Client,ServiceManager運行於用戶空間,驅動運行於內核空間。Binder就是一種把這四個組件粘合在一起的粘結劑了,其中,核心組件便是Binder驅動程序了,Service Manager提供了輔助管理的功能,Client和Server正是在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。這四個角色的關系和互聯網類似:Server是服務器,Client是客戶終端,ServiceManager是域名服務器(DNS),驅動是路由器。
Binder的理解:
1、從IPC角度來說,Binder是Android中的一種跨進程通信方式,該方式在Linux中沒有;
2、從Android Framework角度來說,Binder是ServiceManager連接各種Manager和相應ManagerService的橋梁;
3、從Android應用層來說,Binder是客戶端和服務端進行通許in的媒介,當BindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Bind而對象,客戶端就可以獲取服務端提供的服務或者數據,這里的服務包括普通服務和基於AIDL的服務。
在Android開發中,Binder主要用在Service中,包括AIDL和Messenger,普通服務中的Binder不涉及進程間通信。
直觀來講,Binder是Android中的一個類,實現了IBinder接口。
Binder的工作機制:
當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以一個遠程方法是很耗時的,那么不能在UI線程中發起此遠程請求;
由於服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該采用同步的方式去實現,因為它已經運行在一個線程中了。
六、具體實例