目錄
目錄
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
機制 ,總結如下:
定義
原理圖