再談Android Binder跨進程通訊原理

在談Android的跨進程通訊問題上時,總會問到Android的IPC機制,是指兩個進程之間進行數據交換的過程。按操做系統的中的描述,線程是CPU調度最小的單元,同時線程是一種有限的系統資源,而進程是指一個執行單元,在PC和移動設備上指一個程序或者一個應用。java

在談IPC機制時候核心的就是Binder的運做原理,本文將從如下幾點來說Android額Binder原理。
這裏寫圖片描述android

什麼是Binder

關於Binder的定義,網上是這樣的:Binder是跨進程通訊方式、它實現了IBinder接口,是鏈接 ServiceManager的橋樑。不過這句話有點抽象,那Binder到底是什麼呢?或許下面的圖能解答你心中的疑惑。
這裏寫圖片描述web

基礎概念

進程空間分配

在Linux的進程模型中,進程空間被分爲用戶空間和內核空間(Kernel),即把進程內用戶和內核隔離開來,其中用戶空間的數據不可共享,而內核空間是可共享的,因此進程間的通訊主要經過內核進行交互,稱爲系統調用。模型示意圖以下:
這裏寫圖片描述安全

Android跨進程通訊( IPC )

在Android的進程模型中,一個進程不能直接操做或者訪問另外一個進程,即Android的進程是相互獨立、隔離的。但Android的進程通訊繼承自Linux,因此Android的進程通訊能夠經過內核空間進行數據交互,在進而經過進程 的用戶空間與內核空間進行數據交互,最終時間進程之間的通訊。模型示意圖以下:
這裏寫圖片描述多線程

而Binder的做用就是鏈接 兩個進程內核空間的通道。架構

Binder 跨進程通訊原理

Binder跨進程通訊機制模型基於 Client - Server 模式,其原理模型以下圖所示:
這裏寫圖片描述
經過該模型能夠發現:
Client進程、Server進程 & Service Manager 進程之間的交互都必須經過Binder驅動(使用 open 和 ioctl文件操做函數),而非直接交互。併發

其中:Client進程、Server進程 & Service Manager進程屬於進程空間的用戶空間,不可進行進程間交互;Binder驅動 屬於 進程空間的 內核空間,可進行進程間 & 進程內交互。
這裏寫圖片描述ide

說明:Binder驅動 & Service Manager進程 屬於 Android基礎架構(即系統已經實現好了);而Client 進程 和 Server 進程 屬於Android應用層(須要開發者本身實現)。因此,在進行跨進程通訊時,開發者只需自定義Client & Server 進程 並 顯式使用上述3個步驟,最終藉助 Android的基本架構功能就可完成進程間通訊。
這裏寫圖片描述svg

說明:Binder請求的線程管理函數

  • Server進程會建立不少線程來處理Binder請求;
  • 管理Binder模型的線程是採用Binder驅動的線程池,並由Binder驅動自身進行管理,而不是由Server進程來管理的;
  • 一個進程的Binder線程數默認最大是16,超過的請求會被阻塞等待空閒的Binder線程,因此,在進程間通訊時處理併發問題時,如使用ContentProvider時,它的CRUD(建立、檢索、更新和刪除)方法只能同時有16個線程同時工做。

Binder機制具體實現

Binder機制在 Android中的實現主要依靠 Binder類,其實現了IBinder 接口。例如,下面是Client進程調用Server進程的實現加法函數的實例。即Client進程 須要傳兩個整數給 Server進程,Server進程把相加後的結果返回給Client進程。

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信息。可使用下面的圖來表示:
這裏寫圖片描述

3,使用服務

Client進程 根據獲取到的 Service信息(Binder代理對象),經過Binder驅動 創建與 該Service所在Server進程通訊的鏈路,並開始使用服務。具體的,Client進程將參數發送到Server進程,Server進程 根據Client進程要求調用 目標方法,並將目標方法的結果返回給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();
          }
}

若是用一個流程圖來表示的話,棲流程以下:
這裏寫圖片描述

那爲何Android會重寫進程的通訊機制,而不直接使用Linux提供的進程通訊呢,主要有如下幾點好處。
高效:Binder數據拷貝只須要一次,而管道、消息隊列、Socket都須要2次;經過驅動在內核空間拷貝數據,不須要額外的同步處理;
安全性高:Binder 機制爲每一個進程分配了 UID/PID 來做爲鑑別身份的標示,而且在 Binder 通訊時會根據 UID/PID 進行有效性檢測;傳統的進程通訊方式對於通訊雙方的身份並無作出嚴格的驗證。如,Socket通訊 ip地址是客戶端手動填入,容易出現僞造
使用簡單:採用Client/Server 架構,實現面向對象的調用方式,即在使用Binder時就和調用一個本地對象實例同樣。

本文同步分享在 博客「xiangzhihong8」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索