概要
- 多進程概念及多進程常見注意事項
- IPC基礎:Android序列化和Binder
- 跨進程常見的幾種通信方式:Bundle通過Intent傳遞數據,文件共享,ContentProvider,基於Binder的AIDL和Messenger以及Socket。
- Binder連接池
- 各種進程間通信方式的優缺點及適用場景
IPC是 Inter-Process Communication的縮寫,意為進程間通信或跨進程通信,是指兩個進程之間進行數據交換的過程。
線程是CPU調度的最小單元,同時線程是一種有限的系統資源。進程一般指一個執行單元,在PC和移動設備上指一個程序或者一個應用。一個進程可以包含多個線程,因此進程和線程是包含與被包含的關系。最簡單的情況下,一個進程中只可以有一個線程,即主線程,在Android中也叫UI線程。
IPC不是Android中所獨有的,任何一個操作系統都需要相應的IPC機制,比如Windows上可以通過剪貼板等來進行進程間通信。Android是一種基於Linux內核的移動操作系統,它的進程間通信方式並不能完全繼承自Linux,它有自己的進程間通信方式。
1. Android中的多進程模式
通過給四大組件指定android:processs屬性可以開啟多進程模式,我們無法給一個線程或一個實體類指定其運行所在的進程。
<activity android:name=".SecondActivity" android:process=":remote"/> <activity android:name=".ThirdActivity" android:process="com.example.remote"/>
應用默認的進程是當前包名,以上分別給兩個Activity指定了進程,意味着當前應用又增加了兩個新進程。假設當前應用包名為com.ipc.example,那么SecondActivity所在的進程為com.ipc.example.remote,“:”的含義是要在當前進程名前面附上當前的包名,而且已“:”開頭的進程屬於當前應用的私有進程,其他應用的組件不可以和它跑在同一進程。不以“:”開頭的進程是全局進程,如指定了ThirdActivity所在的進程為全局進程。
2. 多進程模式的運行機制
新建UserManager類:
public class UserManager {
public static int sUserId = 1;
}
在MainActivity中將sUserId的值修改為2,由於MainActivity在應用默認進程,SecondActivity在指定進程,在SecondActivity中打印sUserId的值會發現sUserId值並沒有變,還是1。
出現這樣的原因是SecondActivity運行在單獨的進程,Android為每個應用分配了一個獨立的虛擬機或者說為每個進程都分配了一個獨立的虛擬機,不同的虛擬機在內存上都有不同的地址空間,這就導致在不同的虛擬機中訪問同一個對象會產生多分副本。上面示例中MainActivity修改了sUserId的值只會影響當前進程,對其他進程不會造成 任何影響。
所有運行在不同進程的四大組件,只要它們之間需要通過內存來共享數據都會失敗。一般來說多進程會造成以下幾個問題:
- 靜態成員和單例模式完全失效
- 線程同步機制完全失效(不在同一進程,,鎖的不是同一對象,所以不管鎖對象還是鎖全局類都無法保證線程同步)
- SharePreferences的可靠性下降
- Application會多次創建(在Application的onCreate()中打印當前進程名證實了同一應用不同進程下,Application會多次創建。這也說明了多進程模式下,不同進程的組件的確會擁有獨立的虛擬機、Application以及內存空間)
3. Serializable和Parcelable接口
Serializable和Parcelable可以完成對象的序列化過程,當我們需要通過Intent和Binder傳輸數據時就需要進行序列化,Serializable是Java提供的一個空接口,為對象提供標准的序列化和反序列化操作。使用簡單但是序列化和反序列都會做大量的I/O讀寫操作,內存開銷較大。
Parcelable是Android中的序列化方式,使用稍微復雜,但是效率高。
4. Android中的IPC方式:
4.1: 使用Bundle
四大組件中的其中三個(Activity、Service、Receiver)都是支持在Intent中傳遞Bundle數據,由於Bundle實現了Parcelable接口,所以它可以方便的在不同進程間傳輸。
4.2:使用文件共享
文件共享也是一種不錯的進程間通信方式,兩個進程通過讀寫同一個文件來交換數據,比如A進程中把數據寫入文件,B進程通過讀取這個文件來獲取數據。但是這種方式也是又一定局限性的,比如並發讀寫的問題會導致我們讀出的數據不是最新的,因此要避免並發讀寫的發生或者考慮使用線程同步來限制多個線程的讀寫操作。基於這樣的問題,文件共享方式適合對數據同步要求不高的進程之間進行通信。
4.3:使用Messenger
Messenger是一種輕量級的IPC方案,它的底層實現是AIDL,通過Messenger可以在不同進程中傳遞Message對象,在Message中放入我們需要傳遞的數據就可以輕松實現數據在進程間傳遞了。
Messenger的使用很簡單,由於它一次處理一個請求,因此在服務端不用考慮線程同步的問題。Messenger實現進程間通信大致可以分為以下幾步,分為服務端和客戶端。
- 服務端進程
首先需要在服務端新建一個Service來處理客戶端發起的請求,同時創建一個Handler並通過它來創建一個Messenger對象,然后在Service的onBind中返回這個Messenger對象底層的Binder即可。 - 客戶端進程
客戶端進程中首先要綁定服務端的Service,綁定成功后用服務端返回的IBinder對象創建一個Messenger,通過這個Messenger就可以向服務端發送類型為Message的消息了。
注冊Service,使其運行在單獨的進程,AndroidManifest.xml中配置:
Log日志:
在Messenger中進行數據傳遞必須將數據放入Message中,而Messenger和Message都實現了Parcelable接口,因此可以跨進程傳輸。上面的實例只是介紹了如何在服務端接受客戶端中發送的請求,但是有時候還需要回應客戶端。每當客戶端發來一條消息,服務端就自動回復一條“來自服務端的自動回復:消息已收到”。如果需要客服端能夠回應客戶端,那么和服務端一樣,在客戶端還需要創建一個新的Messenger,並把這個Messenger對象通過Message的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以回應客戶端。
修改后的服務端代碼:
修改后的客戶端代碼:
4.4: 使用AIDL
Messenger是以串行的方式處理客戶端的請求,如果有大量的請求同時發送到服務端,服務端仍然只能一個一個處理,此時Messenger就不大合適了,同時Messenger的作用是為了傳遞消息,且只能傳遞Bundle支持的數據類型,很多時候需要跨進程調用服務端的方法,這時候可以實用AIDL來實現跨進程的方法調用。AIDL也是Messenger的底層實現,因此Messenger本質上也是AIDL,只不過系統作了封裝。實用AIDL進行進程間通信的流程分為服務端和客戶端兩個方面,下面分別介紹:
- 服務端
服務端首先要創建一個Service用來監聽客戶端的連接請求,然后創建一個AIDL文件將暴露給客戶端的接口在這個AIDL文件中聲明,最后在Service中實現這個AIDL接口即可 - 客戶端
客戶端要做的事情稍微簡單些,首先綁定服務端的Service,綁定成功后將服務端返回的Binder對象轉成AIDL接口所屬的類型,接着就可以調用AIDL中的方法了 - AIDL接口的創建
詳情參考:https://www.zhihu.com/question/21581761需要注意的是,如果AIDL文件中用到了自定義的Parcelable對象,那么必須新建一個和它同名的AIDL文件,並在其中聲明為Parcelable類型。上面代碼中我們用到了BookEntity這個類,所以必須創建BookEntity.aidl,然后將BookEntity定義為Parcelable類型。
-
服務端的實現
運行在獨立進程:
<service android:name=".service.BookManagerService" android:process="com.ipc.example.remote"/>
- 客戶端實現
這是一次完整的使用AIDL實現進程間通信,但是遠遠還沒有完,AIDL的復雜性遠不止這些。下一篇着重總結AIDL的使用。