前言
IPC是什么?- 為什么要學
IPC?- 怎么進行
IPC?
下面是我這篇博文的學習腦圖,方便讀者更快的找到自己想要了解的知識點,

一、IPC簡介
1.1什么是IPC
IPC是Inter-Process Communication的縮寫,含義為行程間通信或者跨行程通信,是指兩個行程之間進行資料交換的程序,
看到這里,需要先了解一下行程、執行緒以及它們的關系,
行程:一般指一個執行單元,在PC和移動設備上指一個程式或應用,
執行緒:CPU調度的最小單元,執行緒是一種有限的系統資源,
二者之間的關系:
一個行程可包含多個執行緒,即一個應用程式上可以同時執行多個任務,
注意:不可在主執行緒做大量耗時操作,會導致ANR(應用無回應),

IPC不是Android中所獨有的,任何一個作業系統都需要有相應的IPC機制,Binder是Android中最有特色的行程間通信方式,
1.2為什么要進行IPC
行程間通信的必要性
? 所有運行在不同行程的四大組件,只要它們之間需要通過記憶體在共享資料,都會共享失敗,這是由于Android為每個應用分配了獨立的虛擬機,不同的虛擬機在記憶體分配上有不同的地址空間,這會導致在不同的虛擬機中訪問同一個類的物件會產生多份副本,
1.3怎么進行IPC
使用Bundle、檔案共享、
Messenger、AIDL、ContentProvider、Socket,
二、Android 中的多行程模式
2.1開啟行程
- (常規)在
AndroidMenifest中給四大組件指定屬性android:process,- (不常規)通過
JNI在native層fork一個新的行程,
行程名的默認規則:
默認行程:
- 沒有指定該屬性則運行在默認行程,其行程名就是包名,
以“:”開頭的行程:
- 省略包名,如
android:process=":remote",表示行程名為com.example.myapplication:remote, - 屬于當前應用的私有行程,其他行程的組件不能和他跑在同一行程中,
完整命名的行程:
- 如
android:process="com.example.myapplication.remote", - 屬于全域行程,其他應用可以通過
**ShareUID**方式和他跑在用一個行程中,
UID: Android系統會為每個應用分配一個唯一的UID,具有相同的UID才能共享資料,兩個應用通過
ShareUID跑在同一個行程中的條件:具有相同的ShareUID和簽名,
- 滿足上述條件的兩個應用,無論是否跑在同一行程,它們可共享data目錄,組件資訊,
- 若跑在同一行程,它們除了可共享data目錄、組件資訊,還可共享記憶體資料,它們就像是一個應用的兩個部分,
2.2多行程的運行機制
Android為每一個行程分配了一個獨立的虛擬機,不同虛擬機在記憶體分配上有不同的地址空間,這也導致了不同虛擬機中訪問同一個物件會產生多份副本,
一般來說使用多行程會帶來以下四個方面的問題:
靜態變數和單例模式失效 原因:不同虛擬機中訪問同一個物件會產生多份副本,
執行緒同步機制失效 原因:記憶體不同,執行緒無法同步,
SharedPreference的可靠性下降 原因:底層是通過讀寫XML檔案實作的,發生并發問題,Application多次創建- 原因:Android系統會為新的行程分配獨立虛擬機,相當于應用重新啟動了一次,
為了解決這些問題,可以采用跨行程通信方法,通過Intent,共享檔案和SharedPreferences,Messenger、AIDL和Socket等,
三、IPC基礎概念
在了解以下三種介面使用前,需要先了解一下什么是序列化和反序列化,
1.什么是序列化?
含義:序列化表示將一個物件轉換成可存盤或可傳輸的狀態,序列化后的物件可以在網路上進行傳輸,也可以存盤到本地,
場景:需要通過
Intent和Binder等傳輸類物件就必須完成物件的序列化程序,兩種方式:實作
Serializable/Parcelable介面,
2.什么是反序列化?
把位元組序列恢復為物件的程序稱為物件的反序列化,與序列化相反,
3.1 Serializable介面
Serializable是Java所提供的一個序列化介面,是一個空介面,為物件提供標準的序列化和反序列化操作,
用法:物體類實作
Serializable介面,宣告serialVesionUID,注意:
serialVesionUID非必需,但是不宣告會對反序列化有影響,?
serialVesionUID與當前類的serialVesionUID相同才能被正常反序列化,?
serialVesionUID可以系統配置/手動修改,
代碼示例:
//User物體類實作Serializable介面;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
public int UserId;
public String userName;
public boolean isMale;
public User(int userId, String userName, boolean isMale) {
UserId = userId;
this.userName = userName;
this.isMale = isMale;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public int getUserId() {
return UserId;
}
public void setUserId(int userId) {
UserId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean isMale() {
return isMale;
}
public void setMale(boolean male) {
isMale = male;
}
}
//序列化程序
User user = new User(1,"Yuki",true);
ObjectOutputStream outputStream;
{
try {
outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
//反序列化程序
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();
} catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
3.2 Parcelable介面
用法:
物體類實作
Parcelable介面內容描述
序列化
反序列化
代碼示例:
//User物體類實作Parcelable;
public class User implements Parcelable {
public int UserId;
public String userName;
public boolean isMale;
// public Book book;
public User(int userId, String userName, boolean isMale) {
UserId = userId;
this.userName = userName;
this.isMale = isMale;
}
protected User(Parcel in) {
UserId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
// book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
//反序列化
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public int getUserId() {
return UserId;
}
public void setUserId(int userId) {
UserId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean isMale() {
return isMale;
}
public void setMale(boolean male) {
isMale = male;
}
//內容描述
@Override
public int describeContents() {
return 0;
}
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(UserId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
// dest.writeParcelable(book,0);
}
}
Parcelable方法說明
| 方法 | 功能 | 標記位 |
|---|---|---|
createFromParcel(Parcel in) | 從序列化后的物件中創建原始物件 | |
newArray(int size) | 創建指定長度的原始物件陣列 | |
writeToParcel(Parcel dest, int flags) | 將當前物件寫入序列化結構中,其中flags標識有兩種值:0或者1,為1時標識當前物件需要作為回傳值回傳,不能立即釋放資源,幾乎所有的情況都為0 | PARCELABLE_WRITE_RETURN_VALUE |
| User(Parcel in) | 從序列化后的物件中創建原始物件 | |
describeContents | 回傳當前物件的內容描述,如果含有檔案描述符,回傳1,否則回傳0,幾乎所有的情況都回傳0 | CONTENTS_FILE_DESCRIPTOR |
Serializable和Parcelable比較
Serializable | Parcelable |
|---|---|
| Java序列化介面 | Android序列化介面 |
| 使用簡單 | 使用較麻煩 |
| 效率低 | 效率高 |
3.3 Binder
3.3.1 Binder是什么?
- 直觀上:Android 的一個類,實作了
IBinder介面,**IPC角度:Android中的一種跨行程通信**,- 實作方式角度:一種虛擬的物理設備,
- Android
Framework角度::ServiceManager連接各種Manager(ActivityManager、WindowManager等)的橋梁,- Android應用層角度:客戶端和服務端進行通信的媒介,
3.3.2 為什么是Binder?
Android系統是基于Linux內核的,Linux已經提供了管道、訊息佇列、記憶體共享和Socket等IPC機制,為什么Android還要提供Binder來實作IPC?
| 優勢 | 描述 |
|---|---|
| 性能 | 只需要一次資料拷貝,性能上僅次于共享記憶體 |
| 穩定性 | 基于C/S架構,職責明確,架構清晰,穩定性好 |
| 安全性 | 為每個APP分配UID,行程的UID事鑒別行程身份的重要標志 |
3.3.3 Binder IPC底層通信原理
3.3.3.1.其他IPC機制完成一次行程間通信是怎么樣的?
? 在不同的行程之間,訊息發送方將要發送的資料存放在記憶體快取區中,通過系統呼叫進入內核態,然后內核程式在內核空間分配記憶體,開辟一塊內核快取區,呼叫
copy*from*user()函式將資料從用戶空間的記憶體快取區拷貝到內核空間的內核快取區中,同樣的,接收方行程在接收資料時在自己的用戶空間開辟一塊記憶體快取區,然后內核程式呼叫copy*to*user() 函式將資料從內核快取區拷貝到接收行程的記憶體快取區,

3.3.3.2 Binder IPC完成一次行程間通信又是怎么樣的?
- 首先 Binder 驅動在內核空間創建一個資料接收快取區;
- 接著在內核空間開辟一塊內核快取區,建立內核快取區和內核中資料接收快取區之間的映射關系,以及內核中資料接收快取區和接收行程用戶空間地址的映射關系;
- 發送方行程通過系統呼叫
copy*from*user() 將資料 copy 到內核中的內核快取區,由于內核快取區和接收行程的用戶空間存在記憶體映射,因此也就相當于把資料發送到了接收行程的用戶空間,這樣便完成了一次行程間的通信,

各種IPC方式資料拷貝次數
IPC | 資料拷貝次數 |
|---|---|
| 共享記憶體 | 0 |
Binder | 1 |
訊息佇列/管道/Socket | 2 |
3.3.4 Binder通信程序
Binder框架定義了四個角色:Server,Client,ServiceManager以及Binder驅動,其中Server,Client,ServiceManager運行于用戶空間,驅動運行于內核空間,這四個角色的關系和互聯網類似:Server是服務器,Client是客戶終端,ServiceManager是域名服務器(DNS),驅動是路由器,
-
首先,一個行程使用
BINDER*SET*CONTEXT_MGR命令通過Binder驅動將自己注冊成為ServiceManager; -
Server通過驅動向ServiceManager中注冊Binder(Server中的Binder物體),表明可以對外提供服務,驅動為這個Binder創建位于內核中的物體節點以及ServiceManager對物體的參考,將名字以及新建的參考打包傳給ServiceManager,ServiceManger將其填入查找表, -
Client通過名字,在Binder驅動的幫助下從ServiceManager中獲取到對Binder物體的參考,通過這個參考就能實作和Server行程的通信,這里參考自:寫給 Android 應用工程師的 Binder 原理剖析

問:當服務端行程例外終止的話,造成Binder死亡的話,怎么辦?
在客戶端系結遠程服務成功后,給Binder設定死亡代理,當Binder死亡的時候,我們會收到通知,從而重新發起連接請求,
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:這里重新系結遠程Service
}
}
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
四、Android中的IPC模式
4.1 Bundle
概念:由于Bundle實作了Parcelable介面,可以方便的在不同行程間傳輸,
- 范圍:
Activity、Service、Receiver間傳遞,- 使用:通過
Intent發送,
擴展使用:A行程要啟動B行程并把在A行程計算完的資料傳遞給B行程,如何把不支持Bundle的資料由A行程傳入B中?
答:將原本在A行程的計算任務轉移到B行程的后臺Service中去執行,通過Intent啟動B行程的一個Service,讓計算任務在Service完成,計算完成 后再去啟動目標組件,并把資料傳遞給目標組件,
4.2 檔案共享
概念:兩個行程通過讀/寫同一個檔案來交換資料,
- 檔案范圍:對檔案格式沒有具體要求,
- 局限性:并發讀/寫,
4.3 Messenger
概念:可以在不同行程中傳遞Message物件,把需要傳遞的資料放進物件中,
底層實作:輕量級的
IPC方案,它的底層實作是AIDL,實作
Message1.服務端行程
- 創建一個
Service處理客戶端連接請求- 創建一個
Handle并通過它創建一個Messenger物件- 在
Service的onBind回傳這個物件的底層Binder2.客戶端行程
- 系結服務端的
Service- 用服務端回傳的
IBinder物件創建一個Messenger(客戶端——>服務端)- 在客戶端創建一個
Handler并由此創建一個Messenger,并通過Message的**replyTo欄位**傳遞給服務器端行程,服務端通過讀取Message得到Messenger物件,進而向客戶端行程傳遞資料,(客戶端 <——>服務端)
//Messenger 服務端代碼
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private final Messenger messenger = new Messenger(new MessengerHandler());
//處理客戶端發送的訊息
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 1:
Log.i("TAG"," "+msg.getData().getString("msg"));
break;
default:
break;
}
super.handleMessage(msg);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder(); //回傳它的Binder物件
}
}
<service android:name=".MessengerService"
android:process=":remote"/> //注冊Service
//客戶端代碼
public class MessengerActivity extends AppCompatActivity {
private Messenger mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//系結服務
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service); //用服務端回傳的IBinder物件創建一個Messenger物件
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","Client");
msg.setData(data);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}

4.4 使用AIDL
AIDL是Android Interface Definition Language的縮寫,意思是Android介面定義語言,用于讓某個Service與多個應用程式組件之間進行跨行程通信,從而可以實作多個應用程式共享同一個Service的功能,其使用可以簡單的概括為服務端和客戶端,類似于Socket一樣,服務端服務于所有客戶端,支持一對多服務,
4.4.1 Messenger和AIDL比較
Messenger缺點:串行方式處理訊息,無法并發處理,
AIDL:可以并發處理請求,
AIDL通信流程
- 服務端
- 創建
Service監聽客戶端請求- 創建
AIDL檔案- 在
AIDL檔案中申明暴露給客戶端的介面- 在
Service實作這個AIDL介面- 客戶端
- 系結服務端的Service
- 將服務端回傳的Binder物件轉成
AIDL介面所屬的型別- 呼叫
AIDL方法
4.4.2AIDL能夠支持哪些資料型別?

注意:除了基本資料型別,其它型別的引數必須標上方向:in、out或inout,用于表示在跨行程通信中資料的流向,
4.4.3 關鍵類和關鍵方法
AIDL介面:繼承IInterface,Stub類:Binder的實作類,服務端通過這個類來提供服務,Proxy類:服務器的本地代理,客戶端通過這個類呼叫服務器的方法,asInterface():客戶端呼叫,將服務端的回傳的Binder物件,轉換成客戶端所需要的AIDL介面型別物件,
回傳物件:
- 若客戶端和服務端位于同一行程,則直接回傳
Stub物件本身;- 否則,回傳的是系統封裝后的
Stub.proxy物件,
asBinder():回傳代理Proxy的Binder物件,onTransact():運行服務端的Binder執行緒池中,當客戶端發起跨行程請求時,遠程請求會通過系統底層封裝后交由此方法來處理,transact():運行在客戶端,當客戶端發起遠程請求的同時將當前執行緒掛起,之后呼叫服務端的onTransact()直到遠程請求回傳,當前執行緒才繼續執行,
4.4.4 產生ANR的情形
-
客戶端:
- 呼叫服務端的方法是運行在服務端的
Binder執行緒池中,若主執行緒所呼叫的方法里執行了較耗時的任務,同時會導致客戶端執行緒長時間阻塞,易導致客戶端ANR, - 在
onServiceConnected()和onServiceDisconnected()里直接呼叫服務端的耗時方法,易導致客戶端ANR,
- 呼叫服務端的方法是運行在服務端的
-
服務端:
- 服務端的方法本身就運行在服務端的**
Binder執行緒中,可在其中執行耗時操作,而無需再開啟子執行緒**, - 回呼客戶端
Listener的方法是運行在客戶端的Binder執行緒中,若所呼叫的方法里執行了較耗時的任務,易導致服務端ANR,
- 服務端的方法本身就運行在服務端的**
解決客戶端頻繁呼叫服務器方法導致性能極大損耗的辦法:實作觀察者模式,
即當客戶端關注的資料發生變化時,再讓服務端通知客戶端去做相應的業務處理,
4.4.5解注冊失敗的問題
- 原因:
Binder進行物件傳輸實際是通過序列化和反序列化進行,即Binder會把客戶端傳遞過來的物件重新轉化并生成一個新的物件,雖然在注冊和解注冊的程序中使用的是同一個客戶端傳遞的物件,但經過Binder傳到服務端后會生成兩個不同的物件,另外,多次跨行程傳輸的同一個客戶端物件會在服務端生成不同的物件,但它們在底層的Binder物件是相同的, - 解決辦法:當客戶端解注冊的時候,遍歷服務端所有的
Listener,找到和解注冊Listener具有相同的Binder物件的服務端Listener,刪掉即可,
需要用到
RemoteCallBackList:Android系統專門提供的用于洗掉跨行程listener的介面,其內部自動實作了執行緒同步的功能,
4.5 使用ContentProvider
4.5.1 什么是ContentProvider?
ContentProviderAndroid中提供的專門用于不同應用間進行資料共享的方法,它天生就適合行程間通信,
4.5.2 如何自定義一個ContentProvider?
- 用一個物體類繼承
ContentProvider, - 實作
onCreate、query、update、insert、delete和getType等六種抽象方法,、
注意:
除了onCreate()運行在UI執行緒中,其他的query()、update()、insert()、delete()和getType()都運行在Binder執行緒池中,
CRUD四大操作存在多執行緒并發訪問,要注意在方法內部要做好執行緒同步,
一個SQLiteDatabase內部對資料庫的操作有同步處理,但多個SQLiteDatabase之間無法同步,
4.6 使用Socket
4.6.1 什么是Socket?
Socket也稱為“套接字”,是網路通信的概念,分為流式套接字和用戶資料報套接字兩種,
- 流套接字:基于
TCP協議,采用流的方式提供可靠的位元組流服務,- 資料流套接字:基于
UDP協議,采用資料報文提供資料打包發送的服務,
4.6.2 怎么實作Socket通信?
-
服務端:
- 創建一個
Service,在執行緒中建立TCP服務、監聽相應的埠等待客戶端連接請求; - 與客戶端連接時,會生成新的
Socket物件,利用它可與客戶端進行資料傳輸; - 與客戶端斷開連接時,關閉相應的
Socket并結束執行緒,
- 創建一個
-
客戶端:
- 開啟一個執行緒、通過
Socket發出連接請求; - 連接成功后,讀取服務端訊息;
- 斷開連接,關閉
Socket,
- 開啟一個執行緒、通過
五、IPC方式
各種IPC方式的優缺點比較:
| 名稱 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
Bundle | 簡單易用 | 只能傳輸Bundle支持的資料型別 | 四大組件間的行程間通信 |
| 檔案共享 | 簡單易用 | 不適合高并發場景,無法做到行程間的即時通信 | 無并發訪問,交換簡單資料且實時性不高 |
AIDL | 支持一對多并發和實時通信 | 使用稍復雜,需要處理執行緒同步 | 一對多且有RPC需求 |
Messenger | 支持一對多串行通信 | 不能很好處理高并發,不支持RPC,只能傳輸Bundle支持的資料型別 | 低并發的一對多 |
ContentProvider | 支持一對多并發資料共享 | 可理解為受約束的AIDL | 一對多行程間資料共享 |
| Socket | 支持一對多并發資料共享 | 實作細節繁瑣 | 網路資料交換 |
六、Binder連接池
6.1 典型的AIDL使用流程
- 創建一個
Service和一個AIDL介面- 創建一個類繼承自
AIDL介面中的Stub類并實作Stub中的抽象方法- 在Service的
onBind方法中回傳這個類的物件
6.2 當有多個不同的業務需要使用AIDL來進行通信,該怎么處理?
可以將所有的AIDL放在同一個Service中去管理,它的作業機制是這樣的:
每個業務模塊創建自己的AIDL介面,向服務端提供自己的唯一標識和對應的Binder物件,對服務端來說,只需一個Service,服務端提供一個queryBinder介面,可以根據業務模塊特征回傳相應的Binder物件,Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service去執行,避免重復創建Service,
實作方式:
- 為每個業務模塊創建
AIDL介面并具體實作,- 為
Binder連接池創建AIDL介面IBinderPool.aidl并具體實作,- 遠程服務
BinderPoolService的實作,在onBind()回傳實體化的IBinderPool實作類物件,Binder連接池的具體實作,來系結遠程服務,- 客戶端的呼叫,

本文參考:
-
寫給 Android 應用工程師的 Binder 原理剖析
-
《Android開發藝術探索》
-
進階之路 | 奇妙的 IPC 之旅
-
Android Bander設計與實作 - 設計篇
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/23885.html
標籤:其他
上一篇:導航系統
下一篇:開發微信小游戲用什么游戲引擎好
