1. 程式人生 > >Android跨程序通訊:binder機制原理

Android跨程序通訊:binder機制原理

個人閱讀收穫

通過binder驅動我們可以減少一次io操作,從而減少了我們程序通訊的花費的資源,加快了程序間通訊的速度。我們使用到了Linux的mmap()操作,從而實現了程序間的接收快取區與程序的空間區的對映,從而少了一次io操作。我們的客戶端會發送資訊通過我們io操作講檔案傳送到核心快取區,因為上述說到 我們在註冊服務的時候我們已經將,核心快取區和使用者空間,交換快取區已經進行了對映操作了。

還有一點就是我們平時binder通訊中,返回的binder物件,其實是binder的一個代理物件,而真正的binder實體是永存在核心空間當中的,這一點是剛獲。

前言

  • 如果你接觸過 跨程序通訊 (IPC
    ),那麼你對Binder一定不陌生
  • 雖然 網上有很多介紹 Binder的文章,可是存在一些問題:淺顯的討論Binder機制 或 一味講解 Binder原始碼、邏輯不清楚,最終導致的是讀者們還是無法形成一個完整的Binder概念
  • 本文采用 清晰的圖文講解方式,按照 大角度 -> 小角度 去分析Binder,即:
    1. 先從 機制、模型的角度 去分析 整個Binder跨程序通訊機制的模型
    2. 再 從原始碼實現角度,分析 BinderAndroid中的具體實現

從而全方位地介紹 Binder,希望你們會喜歡。

請儘量在PC端而不要在移動端看,否則圖片可能看不清。

目錄

目錄

1. Binder到底是什麼?

  • 中文即 粘合劑,意思為粘合了兩個不同的程序
  • 網上有很多對Binder的定義,但都說不清楚:Binder是跨程序通訊方式、它實現了IBinder介面,是連線 ServiceManager的橋樑blabla,估計大家都看暈了,沒法很好的理解

  • 我認為:對於Binder的定義,在不同場景下其定義不同

定義

在本文的講解中,按照 大角度 -> 小角度 去分析Binder,即:

  • 先從 機制、模型的角度 去分析 整個Binder跨程序通訊機制的模型

    其中,會詳細分析模型組成中的 Binder驅動

  • 再 從原始碼實現角度,分析 BinderAndroid中的具體實現

從而全方位地介紹 Binder,希望你們會喜歡。

2. 知識儲備

在講解Binder前,我們先了解一些Linux的基礎知識

2.1 程序空間劃分

  • 一個程序空間分為 使用者空間 & 核心空間(Kernel),即把程序內 使用者 & 核心 隔離開來
  • 二者區別:

    1. 程序間,使用者空間的資料不可共享,所以使用者空間 = 不可共享空間
    2. 程序間,核心空間的資料可共享,所以核心空間 = 可共享空間

    所有程序共用1個核心空間

  • 程序內 使用者空間 & 核心空間 進行互動 需通過 系統呼叫,主要通過函式:

    1. copy_from_user():將使用者空間的資料拷貝到核心空間
    2. copy_to_user():將核心空間的資料拷貝到使用者空間

示意圖

2.2 程序隔離 & 跨程序通訊( IPC )

  • 程序隔離 為了保證 安全性 & 獨立性,一個程序 不能直接操作或者訪問另一個程序,即Android的程序是相互獨立、隔離的

  • 跨程序通訊( IPC ) 即程序間需進行資料互動、通訊

  • 跨程序通訊的基本原理

示意圖

a. 而Binder的作用則是:連線 兩個程序,實現了mmap()系統呼叫,主要負責 建立資料接收的快取空間 & 管理資料接收快取 b. 注:傳統的跨程序通訊需拷貝資料2次,但Binder機制只需1次,主要是使用到了記憶體對映,具體下面會詳細說明

2.5 記憶體對映

3. Binder 跨程序通訊機制 模型

3.1 模型原理圖

Binder 跨程序通訊機制 模型 基於 Client - Server 模式示意圖

3.2 模型組成角色說明

示意圖

此處重點講解 Binder驅動的作用 & 原理:

  • 簡介

示意圖

示意圖

3.3 模型原理步驟說明

示意圖

3.4 額外說明

說明1:Client程序、Server程序 & Service Manager 程序之間的互動 都必須通過Binder驅動(使用 openioctl檔案操作函式),而非直接互動

原因: 1. Client程序、Server程序 & Service Manager程序屬於程序空間的使用者空間,不可進行程序間互動 2. 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相加)

    即:

    1. Client程序 需要傳兩個整數給 Server程序
    2. Server程序 需要把相加後的結果 返回給Client程序
  • 具體步驟 下面,我會根據Binder 跨程序通訊機制 模型的步驟進行分析

步驟1:註冊服務

  • 過程描述Server程序 通過Binder驅動 向 Service Manager程序 註冊服務
  • 程式碼實現Server程序 建立 一個 Binder 物件

    1. Binder 實體是 Server程序 在 Binder 驅動中的存在形式
    2. 該物件儲存 ServerServiceManager 的資訊(儲存在核心空間中)
    3. 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程序通訊的鏈路,並開始使用服務

  • 過程描述

    1. Client程序 將引數(整數a和b)傳送到Server程序
    2. Server程序 根據Client程序要求呼叫 目標方法(即加法函式)
    3. 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. 優點

對比 LinuxAndroid基於Linux)上的其他程序通訊方式(管道、訊息佇列、共享記憶體、 訊號量、Socket),Binder 機制的優點有:示意圖

6. 總結

  • 本文主要詳細講解 跨程序通訊模型 Binder機制 ,總結如下:

定義

特別地,對於從模型結構組成的Binder驅動來說:

示意圖

  • 整個Binder模型的原理步驟 & 原始碼分析

示意圖