一篇文章學會使用 Android IPC 多進程
概述
IPC 即 Inter-Process Communication,含義為進程間通信或者跨進程通信,是指兩個進程之間進行數據交換的過程。
線程是 CPU 調度的最小單元,是一種有限的系統資源。進程一般指一個執行單元,在PC和移動設備上是指一個程序或者應用。進程與線程是包含與被包含的關係。一個進程可以包含多個線程。最簡單的情況下一個進程只有一個線程,即主線程(例如 Android 的 UI 線程)。
任何操作系統都需要有相應的 IPC 機制。在 Android 中,IPC 的使用場景大概有以下:
有些模塊由於特殊原因需要運行在單獨的進程中。
通過多進程來獲取多份內存空間。
當前應用需要向其他應用獲取數據。
1. 開啟多進程模式
給四大組件在Manifest中指定 android:process 屬性。這個屬性的值就是進程名。
tips:使用 adb shell ps 或 adb shell ps|grep 包名 查看當前所存在的進程信息。
2. 多線程模式的運行機制
Android 為每個進程都分配了一個獨立的虛擬機,不同虛擬機在內存分配上有不同的地址空間,導致不同的虛擬機訪問同一個類的對象會產生多份副本。例如不同進程的 Activity 對靜態變數的修改,對其他進程不會造成任何影響。所有運行在不同進程的四大組件,只要它們之間需要通過內存在共享數據,都會共享失敗。四大組件之間不可能不通過中間層來共享數據。
多進程會帶來以下問題:
靜態成員和單例模式完全失效。
線程同步鎖機制完全失效。這兩點都是因為不同進程不在同一個內存空間下,鎖的對象也不是同一個對象。
SharedPreferences 的可靠性下降。SharedPreferences 底層是 通過讀/寫 XML 文件實現的,並發讀/寫會導致一定幾率的數據丟失。
Application 會多次創建。
由於系統創建新的進程的同時分配獨立虛擬機,其實這就是啟動一個應用的過程。在多進程模式中,不同進程的組件擁有獨立的虛擬機、Application以及內存空間。實現跨進程的方式有很多:
Intent傳遞數據。
共享文件和SharedPreferences。
基於Binder的Messenger和AIDL。
Socket。
3. Binder
Android 中進程間通訊的核心就是 Binder 機制,強烈建議了解一下 Binder 機制。
Android Binder 進程間通訊
4. Android 中的 IPC 方式
主要有以下方式:
Intent 中附加 extras 來傳遞消息
共享文件
Binder 方式
四大組件之一的 ContentProvider
Socket
1. 使用Bundle
四大組件中的三大組件(Activity、Service、Receiver)都支持在 Intent 中傳遞 Bundle 數據。Bundle 實現了 Parcelable 介面,當我們在一個進程中啟動了另一個進程的 Activity、Service、Receiver,可以再 Bundle 中附加我們需要傳輸給遠程進程的消息並通過 Intent 發送出去。被傳輸的數據必須能夠被序列化。
2. 使用文件共享
一些概念:
兩個進程通過讀寫同一個文件來交換數據。還可以通過 ObjectOutputStream / ObjectInputStream 序列化一個對象到文件中,或者在另一個進程從文件中反序列這個對象。
注意:反序列化得到的對象只是內容上和序列化之前的對象一樣,本質是兩個對象。
文件並發讀寫會導致讀出的對象可能不是最新的,並發寫的話那就更嚴重了。所以文件共享方式適合對數據同步要求不高的進程之間進行通信,並且要妥善處理並發讀寫問題。
SharedPreferences 底層實現採用XML文件來存儲鍵值對。系統對它的讀/寫有一定的緩存策略,即在內存中會有一份 SharedPreferences 文件的緩存,因此在多進程模式下,系統對它的讀/寫變得不可靠,面對高並發讀/寫時 SharedPreferences 有很大幾率丟失數據,因此不建議在IPC中使用 SharedPreferences 。
3. 使用Messenger
Messenger 可以在不同進程間傳遞 Message 對象。是一種輕量級的 IPC 方案,底層實現是 AIDL。
具體使用時,分為服務端和客戶端:
服務端:創建一個 Service 來處理客戶端請求,同時創建一個 Handler 並通過它來創建一個Messenger,然後再 Service 的 onBind 中返回 Messenger 對象底層的 Binder 即可。
privatefinalMessengermMessenger=newMessenger(newxxxHandler());
客戶端:綁定服務端的 Sevice,利用服務端返回的 IBinder 對象來創建一個 Messenger,通過這個 Messenger 就可以向服務端發送消息了,消息類型是 Message 。如果需要服務端響應,則需要創建一個Handler並通過它來創建一個 Messenger(和服務端一樣),並通過 Message 的 replyTo 參數傳遞給服務端。服務端通過 Message 的 replyTo 參數就可以回應客戶端了。
總而言之,就是客戶端和服務端 拿到對方的 Messenger 來發送 Message 。只不過客戶端通過 bindService 而服務端通過 message.replyTo 來獲得對方的Messenger。
Messenger中有一個 Hanlder 以串列的方式處理隊列中的消息。不存在並發執行,因此我們不用考慮線程同步的問題。
4. 使用AIDL
如果有大量的並發請求,使用 Messenger 就不太適合,同時如果需要跨進程調用服務端的方法,Messenger 就無法做到了。這時我們可以使用AIDL。
流程如下:
服務端需要創建 Service來監聽客戶端請求,然後創建一個 AIDL 文件,將暴露給客戶端的介面在AIDL文件中聲明,最後在Service中實現這個AIDL介面即可。
客戶端首先綁定服務端的 Service,綁定成功後,將服務端返回的 Binder 對象轉成 AIDL 介面所屬的類型,接著就可以調用 AIDL 中的方法了。
注意事項:
AIDL 支持的數據類型:
基本數據類型、String、CharSequence
List:只支持 ArrayList,裡面的每個元素必須被AIDL支持
Map:只支持 HashMap,裡面的每個元素必須被AIDL支持
Parcelable
所有的AIDL介面本身也可以在AIDL文件中使用
自定義的 Parcelable 對象和 AIDL 對象,不管它們與當前的 AIDL 文件是否位於同一個包,都必須顯式 import 進來。
如果 AIDL 文件中使用了自定義的 Parcelable 對象,就必須新建一個和它同名的 AIDL 文件,並在其中聲明它為 Parcelable 類型。
packagecom.ryg.chapter_2.aidl;parcelableBook;
AIDL介面中的參數除了基本類型以外都必須表明方向in/out。AIDL介面文件中只支持方法,不支持聲明靜態常量。建議把所有和AIDL相關的類和文件放在同一個包中,方便管理。
voidaddBook(inBookbook);
AIDL方法是在服務端的Binder線程池中執行的,因此當多個客戶端同時連接時,管理數據的集合直接採用 CopyOnWriteArrayList 來進行自動線程同步。類似的還有 ConcurrentHashMap 。
因為客戶端的 listener 和服務端的 listener 不是同一個對象,所以 RecmoteCallbackList 是系統專門提供用於刪除跨進程 listener 的介面,支持管理任意的 AIDL 介面,因為所有 AIDL 介面都繼承自 IInterface 介面。
publicclassRemoteCallbackList
它內部通過一個Map介面來保存所有的 AIDL 回調,這個Map的key是 IBinder 類型,value是 Callback 類型。當客戶端解除註冊時,遍歷服務端所有listener,找到和客戶端 listener 具有相同 Binder 對象的服務端 listenr 並把它刪掉。 7. 客戶端 RPC 的時候線程會被掛起,由於被調用的方法運行在服務端的 Binder 線程池中,可能很耗時,不能在主線程中去調用服務端的方法。
5. 使用ContentProvider
ContentProvider 是四大組件之一,其底層實現和 Messenger 一樣是 Binder。ContentProvider 天生就是用來進程間通信,只需要實現一個自定義或者系統預設置的 ContentProvider,通過 ContentResolver 的 query、update、insert 和 delete 方法即可。
創建 ContentProvider,只需繼承 ContentProvider 實現 onCreate 、 query 、 update 、 insert 、 getType 六個抽象方法即可。除了 onCreate 由系統回調並運行在主線程,其他五個方法都由外界調用並運行在Binder線程池中。
6. 使用Socket
Socket 可以實現計算機網路中的兩個進程間的通信,當然也可以在本地實現進程間的通信。服務端 Service 監聽本地埠,客戶端連接指定的埠,建立連接成功後,拿到 Socket 對象就可以向服務端發送消息或者接受服務端發送的消息。
5. 具體實現
參考代碼:
https://github.com/jeanboydev/Android-AIDLTest
TAG:Jeanboy |