第二章 IPC機制
學習清單:
Android中的多進程模式
-
IPC基礎概念
-
序列化
Serializable接口
Parcelable接口
Binder
-
-
Android中的IPC方式
Bundle
文件共享
Messenger
AIDL
ContentProvider
Socket
Binder連接池
如何選用合適的IPC方式
一.Android中的多進程模式
在談IPC之前, 我們首先需要理解Android中的多進程模式.
a. 什么是線程? 什么是進程?
線程是CPU調度的最小單元
一個進程可以有多個線程
一個進程至少要有一個線程, 即主線程, 在Android中也叫做UI線程
注意: 如果一個進程中需要執(zhí)行一個耗時的操作, 將其放在主線程執(zhí)行, 則會造成應用無響應, 也就是ANR(Application Not Responding), 如果要解決這個問題, 就需要用多線程, 將耗時操作交給別的線程處理
b. 多進程有什么用?
某些模塊因特殊原因要運行在單獨進程中
為加大一個應用可使用的內存代承,需通過多進程來獲取多份內存空間
1. 開啟多進程模式:
- 在Android中使用多進程只有一種方法, 那就是給四大組件(Activity | Service | Receiver | ContentProvider) 在AndroidMenifest中指定
android:process
屬性, 沒有指定process
屬性時, 默認進程的進程名是包名.
補充: 在為
android:process
屬性命名時, 有兩種方式:
android:process=":remote"
: 以 " : " 開頭是在當前進程名前附加上包名的簡寫方式, 以這種方式開頭的進程屬于私有進程, 其他應用不可以和它跑在同一個進程中
android:process="com.example.main.remote"
: 完整的命名方式, 該方式屬于全局進程, 其他應用通過ShareUID方式可以和它跑在同一個線程
2. 開啟多進程模式所帶來的問題:
① 靜態(tài)成員和單例模式完全失效
- Android系統(tǒng)為每一個進程都分配了一個獨立的虛擬機, 導致在不同的虛擬機中訪問同一個類的對象會產生多個副本
② 線程同步機制完全失效
- 原因同上
③ SharedPreferences的可靠性下降
- SharedPreferences不支持兩個進程同時進行讀寫操作奶段,即不支持并發(fā)讀寫暂雹,有一定幾率導致數(shù)據(jù)丟失
④ Application會多次創(chuàng)建
- Android系統(tǒng)會為新的進程分配獨立虛擬機凌停,相當于系統(tǒng)又把這個應用重新啟動了一次
二.IPC基礎概念
a. IPC是什么?
IPC(Inter-Process Communication驶忌,跨進程通信):指兩個進程之間進行數(shù)據(jù)交換的過程
任何一個操作系統(tǒng)都有對應的IPC機制
b. IPC的使用場景:
- 當某個進程需要向別的進程獲取數(shù)據(jù)時
c. Android的進程架構:
- 每一個Android進程都是獨立的蕾总,且都由兩部分組成,一部分是用戶空間粟焊,另一部分是內核空間冤狡,如下圖:
1. 序列化:
a. 序列化的介紹:
- 含義: 序列化表示將一個對象轉換成可存儲或可傳輸的狀態(tài)。序列化后的對象可以在網(wǎng)絡上進行傳輸项棠,也可以存儲到本地
- 場景: 需要通過Intent和Binder等傳輸類對象就必須完成對象的序列化過程
- 兩種方式:實現(xiàn)Serializable/Parcelable接口
b.Serializable接口:
實現(xiàn)方式:
實現(xiàn)Serializable接口
為該類指定SerialVersionUID (可選)
注意: 不指定UID可以實現(xiàn)序列化, 但是會影響反序列化. 所以我們應該手動去設定UID的值:
private static final long serialVersionUID = 1L;
c. Parcelable接口:
實現(xiàn)方式:
實現(xiàn)Parcelable接口
-
實現(xiàn)接口中的各種方法, 各方法功能如下:
b.Serializable接口和Parcelable接口的比較:
2. Binder:
a.概念:
從API的角度: 是一個類, 實現(xiàn)Binder接口
從IPC的角度: 是Android中的一種跨進程通信方式
從Framework角度: 是ServiceManager連結各種Manager和相應ManagerService的橋梁
從應用層的角度: 是客戶端和服務端進行通信的媒介, 客戶端通過連接他來獲取服務端提供的服務或者數(shù)據(jù)
b.Android是基于Linux內核基礎上設計的, 卻沒有把管道/消息隊列/共享內存/信號量/Socket等一些IPC通信手段作為Android的主要IPC方式, 而是采用了Binder機制, 其優(yōu)點有:
- 傳輸效率高悲雳、可操作性強:傳輸效率主要影響因素是內存拷貝的次數(shù),拷貝次數(shù)越少香追,傳輸速率越高合瓢。幾種數(shù)據(jù)傳輸方式比較:
方式 | 拷貝次數(shù) | 操作難度 |
---|---|---|
Binder | 1 | 簡易 |
消息隊列 | 2 | 簡易 |
Socket | 2 | 簡易 |
管道 | 2 | 簡易 |
共享內存 | 0 | 復雜 |
- 數(shù)據(jù)從發(fā)送方的緩存區(qū)拷貝到了內核和緩存區(qū), 而接收方的緩存區(qū)與內核的緩存區(qū)映射的是同一個物理地址, 節(jié)省了一次數(shù)據(jù)拷貝時間, 如圖:
實現(xiàn)C/S架構方便:Linux的眾IPC方式除了Socket以外都不是基于C/S架構,而Socket主要用于網(wǎng)絡間的通信且傳輸效率較低透典。Binder基于C/S 架構 晴楔,Server端與Client端相對獨立,穩(wěn)定性較好掷匠。
安全性高:傳統(tǒng)Linux IPC的接收方無法獲得對方進程可靠的UID/PID滥崩,從而無法鑒別對方身份岖圈;而Binder機制為每個進程分配了UID/PID且在Binder通信時會根據(jù)UID/PID進行有效性檢測讹语。
c.Binder框架定義了四個角色: Server, Client, ServiceManager和Binder驅動
其中Server、Client蜂科、ServiceManager運行于用戶空間顽决,Binder驅動運行于內核空間, 如圖:
下面簡單介紹這四個角色:
- ServiceManager: 服務的管理者, 將Binder名字轉換為Client中對改Binder的引用, 使得Client可以通過Binder名字獲得Service中Binder實體的引用, 如圖:
-
Binder驅動:
與硬件設備沒有直接關系, 其工作方式與設備驅動程序是一樣的, 工作于內核態(tài)
提供open(), mmap(), poll(), ioctl()等標準文件操作
以字符驅動設備中的misc設備注冊在設備目錄/dev下, 用戶通過/dev/binder訪問它
負責進程間binder通信的建立, 傳遞, 計數(shù)管理及數(shù)據(jù)的傳遞交互等底層支持
驅動和應用程序之間定義了一套接口協(xié)議, 主要功能有ioctl()接口實現(xiàn), 由于ioctl()靈活方便且能一次調用實現(xiàn)先寫后讀以滿足同步交互, 因此不必分別調用write()和read()接口
其代碼位于linux目錄的driver/misc/binder.c中
-
Service&Client:
服務器&客戶端. 在Binder驅動和Service Manager提供的基礎設施上, 進行Client-Server之間的通信
d.代理模式Proxy: 給某個對象一個代理對象, 并由代理對象控制對原對象的訪問, 如圖:
代理模式的組成:
Abstract Subject (抽象主題) : 聲明Real Subject和Proxy的共同接口,這樣在任何可以使用Real Subject的地方都可以使用Proxy
Real Subject (真實主題) : 定義了proxy所代表的Real subject
-
Proxy Subject (代理主題) :
內部含有一個Real Subject的引用, 可在任何時候操作目標對象
提供一個代替Real Subject相同的接口, 可在任何時候替代目標對象
e. Binder 工作模式:
服務器端:在服務端創(chuàng)建好了一個Binder對象后导匣,內部就會開啟一個線程用于接收Binder驅動發(fā)送的消息才菠,收到消息后會執(zhí)行onTransact(),并按照參數(shù)執(zhí)行不同的服務端代碼
Binder驅動:在服務端成功Binder對象后贡定,Binder驅動也會創(chuàng)建一個mRemote對象(也是Binder類)赋访,客戶端可借助它調用transact()即可向服務端發(fā)送消息
客戶端:客戶端要想訪問Binder的遠程服務,就必須獲取遠程服務的Binder對象在Binder驅動層對應的mRemote引用缓待。當獲取到mRemote對象的引用后蚓耽,就可以調用相應Binder對象的暴露給客戶端的方法
3. IPC方式
由上圖可以發(fā)現(xiàn), 這些IPC方式全都是通過Binder實現(xiàn)的, 只是封裝的方法不同, 接下來我們分別介紹這六種IPC方式
1.使用Bundle
a. Bundle: 支持在Activity, Service和Receiver之間通過Intent.putExtra()傳遞bundle數(shù)據(jù)
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("xxx","xxx");
intent.putExtra("data", bundle);
b. 原理: Bundle實現(xiàn)了Parcelable接口, 它可以方便的在不同的進程中傳輸
c. 注意: Bundle不支持的數(shù)據(jù)類型無法在進程間傳輸
2.使用文件共享
a. 文件共享: 兩個進程通過讀/寫同一個文件來交換數(shù)據(jù)。比如A進程把數(shù)據(jù)寫入文件旋炒,B進程通過讀取這個文件來獲取數(shù)據(jù)
b. 適用情況:對數(shù)據(jù)同步要求不高的進程之間進行通信步悠,并且要妥善處理并發(fā)讀/寫的問題
c. 雖然SharedPreferences也是文件存儲的一種,但不建議采用
- 原因: 系統(tǒng)對SharedPreferences的讀/寫有一定的緩存策略瘫镇,即在內存中有一份該文件的緩存鼎兽,因此在多進程模式下答姥,其讀/寫會變得不可靠,甚至丟失數(shù)據(jù)
3.使用Messenger
a. Messenger: 信使, 輕量級IPC方式, 通過它在不同進程間傳遞Message對象, 而在Message中放我們要傳遞的數(shù)據(jù), 從而實現(xiàn)IPC
相關記憶:
- Handler: 主要用于線程之間的數(shù)據(jù)通信
- Messenger: 進程間的數(shù)據(jù)通信
b. 特點:
底層實現(xiàn)的是AIDL, 即對AIDL進行了封裝, 更便于進行進程間通信
其服務端以串行的方式來處理客戶端的請求, 不存在并發(fā)執(zhí)行的情形, 故無需考慮線程同步的問題
可在不同進程中傳遞Message對象, Messenger可支持的數(shù)據(jù)類型即Message可支持的數(shù)據(jù)類型
- arg1, arg2, what字段: int
- obj字段: Object對象, 支持系統(tǒng)提供的Parcelable對象
- setData: Bundle對象
- 有兩個構造函數(shù), 分別接收Handler對象和Binder對象
c. 實現(xiàn)方法
-
服務端:
創(chuàng)建一個Service來處理客戶端連接請求
創(chuàng)建一個Handler來獲取Messenger對象
在Service的
onBind()
中返回這個Messenger對象底層的Binder
-
客戶端:
綁定服務端的Service
通過綁定成功后返回的IBinder對象創(chuàng)建一個Messenger對象
如果需要客戶端能夠回應服務端:
- 創(chuàng)建一個Handler和一個新的Messenger
- 將Messenger這個對象通過Message的replyTo參數(shù)傳遞給服務端
- 服務端通過replyTo參數(shù)就能夠回應客戶端
d. Messenger的缺點:
主要傳遞的是Message, 難以實現(xiàn)遠程方法調用
以串行的方式處理客戶端發(fā)來的消息, 不適合高并發(fā)場景
解決方法: 使用AIDL來實現(xiàn)IPC
4.使用AIDL
a.AIDL(Android Interface Definition Language谚咬,Android接口定義語言): 可以利用它定義客戶端與服務均認可的編程接口, 以便二者使用進程間通信 (IPC) 進行相互通信, AIDL會生成一個服務端的代理類, 通過它客戶端實現(xiàn)間接調用服務端的方法
b. 支持的數(shù)據(jù)類型:
基本數(shù)據(jù)類型(int, long, char, boolean, double等)
String和CharSequence
List: 只支持ArrayList, 里面每個元素都必須被AIDL支持
Map: 只支持HashMap, 里面每個元素都必須被AIDL支持, 包括key和value
Paecelable: 所有實現(xiàn)了Parcelable接口的對象
AIDl: 所有的AIDL接口本身也可以在AIDL文件中使用
注意: 除了基本數(shù)據(jù)類型, 其他類型的參數(shù)必須標上方向: in, out, inout, 用于表示數(shù)據(jù)的流向
- in
* 表示數(shù)據(jù)只能由客戶端流向服務端 * 服務端將會接收到這個對象的完整數(shù)據(jù), 但在服務端修改它不會對客戶端輸入的對象產生影響
- out
* 表示數(shù)據(jù)只能由服務端流向客戶端 * 服務端將會接收到這個對象的的空對象, 但在服務端對接收到的空對象有任何修改之后客戶端將會同步變動
- inout
* 表示數(shù)據(jù)可在服務端與客戶端之間雙向流通 * 服務端將會接收到客戶端傳來對象的完整信息, 且客戶端將會同步服務端對該對象的任何變動
c. 兩種AIDL文件
用于定義parcelable對象, 以供其他AIDL文件使用AIDL中非默認支持的數(shù)據(jù)類型的
用于定義方法接口, 以供系統(tǒng)使用來完成跨進程通信的
注意:
- 自定義的Parcelable對象必須把java文件和自定義的AIDL文件顯式的import進來, 無論是否在同一包內
- AIDL文件用到自定義Parcelable的對象, 必須新建一個和它同名的AIDL文件, 并在其中聲明它為Parcelable類型
d. AIDL本質上是系統(tǒng)提供了一套可快速實現(xiàn)Binder的工具. 關鍵類和方法:
AIDL接口: 繼承IInterface
Stub類: Binder的實體類, 服務端通過這個類來提供服務
Proxy類: 服務器的本地代理, 客戶端通過這個類來獲取服務
-
asInterface(): 客戶端調用, 將服務端返回的Binder對象, 轉換成客戶端所需的AIDL接口類型對象. 返回對象:
若客戶端和服務端位于同一進程, 則直接返回Stub對象本身
否則, 返回系統(tǒng)封裝后的Stub.proxy對象
asBinder(): 根據(jù)當前調用情況返回代理Proxy的Binder對象
onTransact(): 運行服務端的Binder線程池中, 當客戶端發(fā)起跨進程請求時, 遠程請求會通過系統(tǒng)底層封裝后交由此方法處理
transact(): 運行在客戶端, 當客戶端發(fā)送遠程請求的同時將當前線程掛起. 之后調用服務端的onTransact()直到遠程請求返回, 當前線程才繼續(xù)執(zhí)行
e. 實現(xiàn)方法
-
服務端:
創(chuàng)建一個AIDL文件
創(chuàng)建一個Service, 實現(xiàn)AIDL的接口函數(shù)并暴露AIDL接口
-
客戶端:
通過bindService綁定服務端的Service
綁定成功后, 將服務端返回的Binder對象轉化為AIDL接口所屬的類型, 進而調用AIDL中提供的方法
總結: 服務端里的某個Service為和它綁定的特定客戶端提供Binder對象, 客戶端通過AIDL的靜態(tài)接口asInterface()將Binder對象轉化為AIDL接口的代理對象, 通過這個代理我們就可以發(fā)送遠程調用請求
f. 可能產生ANR的情形
-
對于客戶端
調用服務端的方法是運行在服務端的Binder線程池中, 若所調用的方法里執(zhí)行了較耗時的任務, 同時會導致客戶端線程長時間阻塞, 易導致客戶端ANR
在onServiceConnected()和onServiceDisconnected()里直接調用服務端的耗時方法, 易導致客戶端ANR
-
對于服務端
服務端的方法本身就運行在服務端的Binder線程中, 可在其中執(zhí)行耗時操作, 而無需再開啟子線程
回調客戶端Listener的方法是運行在客戶端的Binder線程中, 若所調用的方法里執(zhí)行了較耗時的任務, 易導致服務端ANR
提示: 解決客戶端頻繁調用服務器方法導致性能極大損耗的辦法 : 實現(xiàn)觀察者模式. 即當客戶端關注的數(shù)據(jù)發(fā)生變化時, 再讓服務端通知客戶端去做相應的業(yè)務處理
g. AIDL解注冊失敗
原因:Binder進行對象傳輸實際是通過序列化和反序列化進行鹦付,即Binder會把客戶端傳遞過來的對象重新轉化并生成一個新的對象,雖然在注冊和解注冊的過程中使用的是同一個客戶端對象择卦,但經過Binder傳到服務端后會生成兩個不同的對象睁壁。另外,多次跨進程傳輸?shù)耐粋€客戶端對象會在服務端生成不同的對象互捌,但它們在底層的Binder對象是相同的
解決辦法:當客戶端解注冊的時候潘明,遍歷服務端所有的Listener,找到和解注冊Listener具有相同的Binder對象的服務端Listener秕噪,刪掉即可
需要用到RemoteCallBackList:Android系統(tǒng)專門提供的用于刪除跨進程listener的接口钳降。其內部自動實現(xiàn)了線程同步的功能
5.使用ContentProvider
a. ContentProvider: Android中提供的專門用于不同應用間進行數(shù)據(jù)共享的方式,底層實現(xiàn)使用Binder
b. 特點:
相比于AIDL腌巾,使用更加簡單遂填,無需關心底層細節(jié)
系統(tǒng)預置了許多ContentProvider如:通訊錄信息、日程表信息等
ContentProvider主要以表格的形式來組織數(shù)據(jù)澈蝙,使用方法與數(shù)據(jù)庫很類似
ContentProvider還支持文件數(shù)據(jù)吓坚,如圖片、視頻等
c. 使用方法:
-
服務端:
-
新建一個類繼承自ContentProvider, 并實現(xiàn)六個抽象方法:
onCreate(): ContentProvider的創(chuàng)建, 一般用于一些初始化動作
query(): 對數(shù)據(jù)表進行查詢操作
insert(): 對數(shù)據(jù)表進行插入操作
delete(): 對數(shù)據(jù)表進行刪除操作
update(): 對數(shù)據(jù)表進行更新操作
getType(): 返回一個Uri請求所對應的MIME(媒體)類型, 如圖片灯荧、視頻
注意:
- update, insert和delete均運行在Binder線程池中, 而onCreate運行在主線程(UI線程), 即不能在onCreate中執(zhí)行耗時操作
- 一個SQLiteDatabase內部對數(shù)據(jù)庫的操作有同步處理, 但多個SQLiteDatabase之間無法同步
注冊ContentProvider
-
<provider
android:name=".provider.BookProvider"
android:authorities="com.ljw.charpter_2.book.provider"/>
- 客戶端: 通過getContentResolver()獲取到ContentResolver對象, 并通過此對象執(zhí)行相應的操作
6.使用Socket
a. Socket: 套接字, 是網(wǎng)絡通信中的概念, 分為: 流式套接字和用戶數(shù)據(jù)報套接字, 分別對應TCP和UDP
網(wǎng)絡傳輸協(xié)議:
TCP: 面向連接的協(xié)議, 提供穩(wěn)定的雙向通信功能, 具有很高的穩(wěn)定性
UDP: 無連接, 提供不穩(wěn)定的單向通道, 也可實現(xiàn)雙向功能, 效率更高, 但不能保證數(shù)據(jù)安全送達
b. 注意:
不能在主線程(UI線程)中訪問網(wǎng)絡
使用Socket進行通信, 需要聲明權限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
c. 使用方法:
-
服務端:
創(chuàng)建一個Service, 在線程中建立端口, 等待客戶端連接請求
在與客戶端連接后, 會生成一個新的Socket, 通過它可以和客戶端進行數(shù)據(jù)傳輸
在于客戶端斷開連接后, 關閉相應的Socket并結束線程
-
客戶端:
開啟一個線程, 通過Socket發(fā)出連接請求
連接成功后, 通過這個Socket讀取服務端消息
斷開連接, 關閉Socket
以上六種IPC方式的優(yōu)缺點及使用場景如下圖:
4.Binder連接池
背景: 當多個業(yè)務模塊都需要使用到AIDL來進行IPC時, 則需要為每一個模塊創(chuàng)建對應的aidl文件, 與之對應的則要創(chuàng)建許多的Service服務. 這樣會極大的占用我們有限的系統(tǒng)資源
-
功能: 將每一個模塊的Binder請求都統(tǒng)一轉發(fā)到一個遠程Service中去執(zhí)行, 避免重復創(chuàng)建新的Service
- 原理: 每個業(yè)務模塊有著自己的AIDL接口并實現(xiàn), 然后向服務端提供自己的唯一標識和Binder對象. 服務端只需要一個Service并提供一個queryBinder接口, 它將根據(jù)業(yè)務模塊的標識來返回相應的Binder對象, 不同的業(yè)務模塊拿到自己的所需的Binder對象就可以進行對應的遠程操作
-
實現(xiàn):
-
AIDL接口
實現(xiàn)相關模塊的AIDL接口
-
創(chuàng)建一個IBinderPool.aidl文件, 獲取相關模塊所需的AIDL接口
interface IBinderPool { IBinder queryBinder(int binderCode); }
創(chuàng)建一個BinderPool文件, 實現(xiàn)IBinderPool接口
public static class BinderPoolImpl extends IBinderPool.Stub { public BinderPoolImpl() { super(); } ? @Override public IBinder queryBinder(int binderCode) throws RemoteException { IBinder binder = null; switch (binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputerImpl(); break; } default: break; } return binder; } }
- Binder連接池的具體實現(xiàn), 來綁定遠程服務
服務端: 遠程服務BinderPoolService實現(xiàn), 在onBind()處返回實例化的IBinderPool實體類對象
-
客戶端:
通過BinderPool類中的getInstance(Context)獲取BinderPoll類實例
通過BinderPool類實例里實現(xiàn)的queryBinder(int)方法獲取所需要的Binder對象
-
總結
IPC是指兩個進程之間進行數(shù)據(jù)交換的過程
Android中的IPC方式底層都是由Binder實現(xiàn)的, 由此可見Binder在Android中的重要性
要根據(jù)不同的需求, 使用不同的IPC方式, 因地制宜
要多用, 不然會忘