圖文詳解 Android Binder跨程序通訊機制 原理
目錄
目錄
1. Binder到底是什麼?
- 中文即 粘合劑,意思為粘合了兩個不同的程序
-
網上有很多對
Binder
的定義,但都說不清楚:Binder
是跨程序通訊方式、它實現了IBinder
介面,是連線ServiceManager
的橋樑blabla,估計大家都看暈了,沒法很好的理解 -
我認為:對於
Binder
的定義,在不同場景下其定義不同
定義
在本文的講解中,按照 大角度 -> 小角度 去分析Binder
,即:
- 先從 機制、模型的角度 去分析 整個
Binder
跨程序通訊機制的模型其中,會詳細分析模型組成中的
Binder
驅動 - 再 從原始碼實現角度,分析
Binder
Android
中的具體實現
從而全方位地介紹 Binder
,希望你們會喜歡。
2. 知識儲備
在講解Binder
前,我們先了解一些基礎知識
2.1 程序空間分配
- 一個程序空間分為 使用者空間 & 核心空間(
Kernel
),即把程序內 使用者 & 核心 隔離開來 - 二者區別:
- 程序間,使用者空間的資料不可共享,所以使用者空間 = 不可共享空間
- 程序間,核心空間的資料可共享,所以核心空間 = 可共享空間
- 程序內 使用者 與 核心 進行互動 稱為系統呼叫
示意圖
2.2 程序隔離
為了保證 安全性 & 獨立性,一個程序 不能直接操作或者訪問另一個程序,即Android
2.3 跨程序通訊( IPC
)
- 隔離後,由於某些需求,程序間 需要合作 / 互動
- 跨程序間通訊的原理
- 先通過 程序間 的核心空間進行 資料互動
- 再通過 程序內 的使用者空間 & 核心空間進行 資料互動,從而實現 程序間的使用者空間 的資料互動
示意圖
而Binder
,就是充當 連線 兩個程序(核心空間)的通道。
3. Binder 跨程序通訊機制 模型
3.1 模型原理
Binder
跨程序通訊機制 模型 基於 Client - Server
模式,模型原理圖如下:
相信我,一張圖就能解決問題
示意圖
3.2 額外說明
說明1:Client
Server
程序 & Service Manager
程序之間的互動都必須通過Binder
驅動(使用 open
和 ioctl
檔案操作函式),而非直接互動 **
原因:
Client
程序、Server
程序 &Service Manager
程序屬於程序空間的使用者空間,不可進行程序間互動Binder
驅動 屬於 程序空間的 核心空間,可進行程序間 & 程序內互動
所以,原理圖可表示為以下:
虛線表示並非直接互動
示意圖
說明2: Binder
驅動 & Service Manager
程序 屬於 Android
基礎架構(即系統已經實現好了);而Client
程序 和 Server
程序 屬於Android
應用層(需要開發者自己實現)
所以,在進行跨程序通訊時,開發者只需自定義Client
& Server
程序 並 顯式使用上述3個步驟,最終藉助 Android
的基本架構功能就可完成程序間通訊
示意圖
說明3:Binder請求的執行緒管理
Server
程序會建立很多執行緒來處理Binder
請求- 管理
Binder
模型的執行緒是採用Binder
驅動的執行緒池,並由Binder
驅動自身進行管理而不是由
Server
程序來管理的 - 一個程序的
Binder
執行緒數預設最大是16,超過的請求會被阻塞等待空閒的Binder執行緒。所以,在程序間通訊時處理併發問題時,如使用
ContentProvider
時,它的CRUD
(建立、檢索、更新和刪除)方法只能同時有16個執行緒同時工作
- 至此,我相信大家對
Binder
跨程序通訊機制 模型 已經有了一個非常清晰的定性認識 - 下面,我將通過一個例項,分析
Binder
跨程序通訊機制 模型在Android
中的具體程式碼實現方式即分析 上述步驟在
Android
中具體是用程式碼如何實現的
4. Binder機制 在Android中的具體實現原理
Binder
機制在Android
中的實現主要依靠Binder
類,其實現了IBinder
介面下面會詳細說明
-
例項說明:
Client
程序 需要呼叫Server
程序的加法函式(將整數a和b相加)即:
Client
程序 需要傳兩個整數給Server
程序Server
程序 需要把相加後的結果 返回給Client
程序
-
具體步驟
下面,我會根據Binder
跨程序通訊機制 模型的步驟進行分析
步驟1:註冊服務
- 過程描述
Server
程序 通過Binder
驅動 向Service Manager
程序 註冊服務 -
程式碼實現
Server
程序 建立 一個Binder
物件Binder
實體是Server
程序 在Binder
驅動中的存在形式- 該物件儲存
Server
和ServiceManager
的資訊(儲存在核心空間中) Binder
驅動通過 核心空間的Binder
實體 找到使用者空間的Server
物件
-
程式碼分析
Binder binder = new Stub();
// 步驟1:建立Binder物件 ->>分析1
// 步驟2:建立 IInterface 介面類 的匿名類
// 建立前,需要預先定義 繼承了IInterface 介面的介面 -->分析3
IInterface plus = new IPlus(){
// 確定Client程序需要呼叫的方法
public int add(int a,int b) {
return a+b;
}
// 實現IInterface介面中唯一的方法
public IBinder asBinder(){
return null ;
}
};
// 步驟3
binder.attachInterface(plus,"add two int");
// 1. 將(add two int,plus)作為(key,value)對存入到Binder物件中的一個Map<String,IInterface>物件中
// 2. 之後,Binder物件 可根據add two int通過queryLocalIInterface()獲得對應IInterface物件(即plus)的引用,可依靠該引用完成對請求方法的呼叫
// 分析完畢,跳出
<-- 分析1:Stub類 -->
public class Stub extends Binder {
// 繼承自Binder類 ->>分析2
// 複寫onTransact()
@Override
boolean onTransact(int code, Parcel data, Parcel reply, int flags){
// 具體邏輯等到步驟3再具體講解,此處先跳過
switch (code) {
case Stub.add: {
data.enforceInterface("add two int");
int arg0 = data.readInt();
int arg1 = data.readInt();
int result = this.queryLocalIInterface("add two int") .add( arg0, arg1);
reply.writeInt(result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
// 回到上面的步驟1,繼續看步驟2
<-- 分析2:Binder 類 -->
public class Binder implement IBinder{
// Binder機制在Android中的實現主要依靠的是Binder類,其實現了IBinder介面
// IBinder介面:定義了遠端操作物件的基本介面,代表了一種跨程序傳輸的能力
// 系統會為每個實現了IBinder介面的物件提供跨程序傳輸能力
// 即Binder類物件具備了跨程序傳輸的能力
void attachInterface(IInterface plus, String descriptor);
// 作用:
// 1. 將(descriptor,plus)作為(key,value)對存入到Binder物件中的一個Map<String,IInterface>物件中
// 2. 之後,Binder物件 可根據descriptor通過queryLocalIInterface()獲得對應IInterface物件(即plus)的引用,可依靠該引用完成對請求方法的呼叫
IInterface queryLocalInterface(Stringdescriptor) ;
// 作用:根據 引數 descriptor 查詢相應的IInterface物件(即plus引用)
boolean onTransact(int code, Parcel data, Parcel reply, int flags);
// 定義:繼承自IBinder介面的
// 作用:執行Client程序所請求的目標方法(子類需要複寫)
// 引數說明:
// code:Client程序請求方法識別符號。即Server程序根據該標識確定所請求的目標方法
// data:目標方法的引數。(Client程序傳進來的,此處就是整數a和b)
// reply:目標方法執行後的結果(返回給Client程序)
// 注:執行在Server程序的Binder執行緒池中;當Client程序發起遠端請求時,遠端請求會要求系統底層執行回撥該方法
final class BinderProxy implements IBinder {
// 即Server程序建立的Binder物件的代理物件類
// 該類屬於Binder的內部類
}
// 回到分析1原處
}
<-- 分析3:IInterface介面實現類 -->
public interface IPlus extends IInterface {
// 繼承自IInterface介面->>分析4
// 定義需要實現的介面方法,即Client程序需要呼叫的方法
public int add(int a,int b);
// 返回步驟2
}
<-- 分析4:IInterface介面類 -->
// 程序間通訊定義的通用介面
// 通過定義介面,然後再服務端實現介面、客戶端呼叫介面,就可實現跨程序通訊。
public interface IInterface
{
// 只有一個方法:返回當前介面關聯的 Binder 物件。
public IBinder asBinder();
}
// 回到分析3原處
註冊服務後,Binder
驅動持有 Server
程序建立的Binder
實體
步驟2:獲取服務
Client
程序 使用 某個service
前(此處是 相加函式),須 通過Binder
驅動 向ServiceManager
程序 獲取相應的Service
資訊- 具體程式碼實現過程如下:
示意圖
此時,Client
程序與 Server
程序已經建立了連線
步驟3:使用服務
Client
程序 根據獲取到的 Service
資訊(Binder
代理物件),通過Binder
驅動 建立與 該Service
所在Server
程序通訊的鏈路,並開始使用服務
-
過程描述
Client
程序 將引數(整數a和b)傳送到Server
程序Server
程序 根據Client
程序要求呼叫 目標方法(即加法函式)Server
程序 將目標方法的結果(即加法後的結果)返回給Client
程序
-
程式碼實現過程
步驟1: Client
程序 將引數(整數a和b)傳送到Server
程序
// 1. Client程序 將需要傳送的資料寫入到Parcel物件中
// data = 資料 = 目標方法的引數(Client程序傳進來的,此處就是整數a和b) + IInterface介面物件的識別符號descriptor
android.os.Parcel data = android.os.Parcel.obtain();
data.writeInt(a);
data.writeInt(b);
data.writeInterfaceToken("add two int");;
// 方法物件識別符號讓Server程序在Binder物件中根據"add two int"通過queryLocalIInterface()查詢相應的IInterface物件(即Server建立的plus),Client程序需要呼叫的相加方法就在該物件中
android.os.Parcel reply = android.os.Parcel.obtain();
// reply:目標方法執行後的結果(此處是相加後的結果)
// 2. 通過 呼叫代理物件的transact() 將 上述資料傳送到Binder驅動
binderproxy.transact(Stub.add, data, reply, 0)
// 引數說明:
// 1. Stub.add:目標方法的識別符號(Client程序 和 Server程序 自身約定,可為任意)
// 2. data :上述的Parcel物件
// 3. reply:返回結果
// 0:可不管
// 注:在傳送資料後,Client程序的該執行緒會暫時被掛起
// 所以,若Server程序執行的耗時操作,請不要使用主執行緒,以防止ANR
// 3. Binder驅動根據 代理物件 找到對應的真身Binder物件所在的Server 程序(系統自動執行)
// 4. Binder驅動把 資料 傳送到Server 程序中,並通知Server 程序執行解包(系統自動執行)
步驟2:Server
程序根據Client
進要求 呼叫 目標方法(即加法函式)
// 1. 收到Binder驅動通知後,Server 程序通過回撥Binder物件onTransact()進行資料解包 & 呼叫目標方法
public class Stub extends Binder {
// 複寫onTransact()
@Override
boolean onTransact(int code, Parcel data, Parcel reply, int flags){
// code即在transact()中約定的目標方法的識別符號
switch (code) {
case Stub.add: {
// a. 解包Parcel中的資料
data.enforceInterface("add two int");
// a1. 解析目標方法物件的識別符號
int arg0 = data.readInt();
int arg1 = data.readInt();
// a2. 獲得目標方法的引數
// b. 根據"add two int"通過queryLocalIInterface()獲取相應的IInterface物件(即Server建立的plus)的引用,通過該物件引用呼叫方法
int result = this.queryLocalIInterface("add two int") .add( arg0, arg1);
// c. 將計算結果寫入到reply
reply.writeInt(result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
// 2. 將結算結果返回 到Binder驅動
步驟3:Server
程序 將目標方法的結果(即加法後的結果)返回給Client
程序
// 1. Binder驅動根據 代理物件 沿原路 將結果返回 並通知Client程序獲取返回結果
// 2. 通過代理物件 接收結果(之前被掛起的執行緒被喚醒)
binderproxy.transact(Stub.ADD, data, reply, 0);
reply.readException();;
result = reply.readInt();
}
}
- 總結
下面,我用一個原理圖 & 流程圖來總結步驟3的內容
原理圖
流程圖
5. 優點
對比 Linux
(Android
基於Linux
)上的其他程序通訊方式(管道/訊息佇列/共享記憶體/訊號量/Socket),Binder
機制的優點有:
-
高效
Binder
資料拷貝只需要一次,而管道、訊息佇列、Socket
都需要2次- 通過驅動在核心空間拷貝資料,不需要額外的同步處理
-
安全性高
Binder
機制為每個程序分配了UID/PID
來作為鑑別身份的標示,並且在Binder
通訊時會根據UID/PID
進行有效性檢測- 傳統的程序通訊方式對於通訊雙方的身份並沒有做出嚴格的驗證
- 如,
Socket
通訊ip
地址是客戶端手動填入,容易出現偽造
- 使用簡單
- 採用
Client/Server
架構 - 實現 面向物件 的呼叫方式,即在使用
Binder
時就和呼叫一個本地物件例項一樣
- 採用
6. 總結
- 本文主要詳細講解 跨程序通訊模型
Binder
機制 ,總結如下:
定義
原理圖
好文轉載.......