談談Android Binder機制及AIDL使用

Binder原理

一、概述

Android系統中,涉及到多進程間的通訊底層都是依賴於Binder IPC機制。例如當進 程A中的Activity要向進程B中的Service通訊,這便須要依賴於Binder IPC。不只於 此,整個Android系統架構中,大量採用了Binder機制做爲IPC(進程間通訊, Interprocess Communication)方案。 android

固然也存在部分其餘的IPC方式,如管道、SystemV、Socket等。那麼Android爲什 麼不使用這些原有的技術,而是要使開發一種新的叫Binder的進程間通訊機制呢?git

順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)github

爲何要使用Binder?
性能方面 在移動設備上(性能受限制的設備,好比要省電),普遍地使用跨進程通訊對通訊 機制的性能有嚴格的要求,Binder相對於傳統的Socket方式,更加高效。Binder數 據拷貝只須要一次,而管道、消息隊列、Socket都須要2次,共享內存方式一次內 存拷貝都不須要,但實現方式又比較複雜。面試

安全方面
傳統的進程通訊方式對於通訊雙方的身份並無作出嚴格的驗證,好比Socket通訊 的IP地址是客戶端手動填入,很容易進行僞造。然而,Binder機制從協議自己就支 持對通訊雙方作身份校檢,從而大大提高了安全性。安全

二、 Binder

IPC原理
從進程角度來看IPC(Interprocess Communication)機制
談談Android Binder機制及AIDL使用
每一個Android的進程,只能運行在本身進程所擁有的虛擬地址空間。例如,對應一 個4GB的虛擬地址空間,其中3GB是用戶空間,1GB是內核空間。固然內核空間的 大小是能夠經過參數配置調整的。對於用戶空間,不一樣進程之間是不能共享的,而 內核空間倒是可共享的。Client進程向Server進程通訊,偏偏是利用進程間可共享 的內核內存空間來完成底層通訊工做的。Client端與Server端進程每每採用ioctl等方 法與內核空間的驅動進行交互。架構

Binder原理
Binder通訊採用C/S架構,從組件視角來講,包含Client、Server、ServiceManager 以及Binder驅動,其中ServiceManager用於管理系統中的各類服務。架構圖以下所 示:
談談Android Binder機制及AIDL使用
Binder通訊的四個角色 ide

Client進程: 使用服務的進程。
Server進程: 提供服務的進程。
ServiceManager進程: ServiceManager的做用是將字符形式的Binder名字轉化成 Client中對該Binder的引用,使得Client可以經過Binder名字得到對Server中Binder 實體的引用。
Binder驅動: 驅動負責進程之間Binder通訊的創建,Binder在進程之間的傳遞, Binder引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持。函數

Binder運行機制
圖中Client/Server/ServiceManage之間的相互通訊都是基於Binder機制。既然基於 Binder機制通訊,那麼一樣也是C/S架構,則圖中的3大步驟都有相應的Client端與 Server端。佈局

註冊服務(addService): Server進程要先註冊Service到ServiceManager。該過 程:Server是客戶端,ServiceManager是服務端。
獲取服務(getService): Client進程使用某個Service前,須先向ServiceManager中 獲取相應的Service。該過程:Client是客戶端,ServiceManager是服務端。
使用服務: Client根據獲得的Service信息創建與Service所在的Server進程通訊的通 路,而後就能夠直接與Service交互。該過程:Client是客戶端,Server是服務端。性能

圖中的ClientServerService Manager之間交互都是虛線表示,是因爲它們彼此 之間不是直接交互的,而是都經過與Binder驅動進行交互的,從而實現IPC通訊 (Interprocess Communication)方式。其中Binder驅動位於內核空間,ClientServerService Manager位於用戶空間。Binder驅動和Service Manager能夠看作 是Android平臺的基礎架構,而Client和Server是Android的應用層,開發人員只需自 定義實現Client、Server端,藉助Android的基本平臺架構即可以直接進行IPC通 信。

Binder運行的實例解釋
首先咱們看看咱們的程序跨進程調用系統服務的簡單示例,實現浮動窗口部分代 碼:

//獲取WindowManager服務引用 
  WindowManager wm = (WindowManager) getSystemService(getApplicati on().WINDOW_SERVICE); 
  //佈局參數layoutParams相關設置略... 
  View view = LayoutInflater.from(getApplication()).inflate(R.layo ut.float_layout, null); 
  //添加view 
  wm.addView(view, layoutParams);

註冊服務(addService): 在Android開機啓動過程當中,Android會初始化系統的各類 Service,並將這些Service向ServiceManager註冊(即讓ServiceManager管理)。 這一步是系統自動完成的。
獲取服務(getService): 客戶端想要獲得具體的Service直接向ServiceManager要 便可。客戶端首先向ServiceManager查詢獲得具體的Service引用,一般是Service 引用的代理對象,對數據進行一些處理操做。即第2行代碼中,獲得的wm是 WindowManager對象的引用。
使用服務: 經過這個引用向具體的服務端發送請求,服務端執行完成後就返回。即 第6行調用WindowManageraddView函數,將觸發遠程調用,調用的是運行在 systemServer進程中的WindowManageraddView函數。
使用服務的具體執行過程
談談Android Binder機制及AIDL使用

  1. Client經過得到一個Server的代理接口,對Server進行調用。
  2. 代理接口中定義的方法與Server中定義的方法是一一對應的。
  3. Client調用某個代理接口中的方法時,代理接口的方法會將Client傳遞的參數打 包成Parcel對象。
  4. 代理接口將Parcel發送給內核中的Binder Driver。
  5. Server會讀取Binder Driver中的請求數據,若是是發送給本身的,解包Parcel 對象,處理並將結果返回。
  6. 整個的調用過程是一個同步過程,在Server處理的時候,Client會Block住。因 此Client調用過程不該在主線程。

AIDL的使用

1.AIDL的簡介

AIDL (Android Interface Definition Language) 是一種接口定義語言,用於生成能夠 在Android設備上兩個進程之間進行進程間通訊(Interprocess Communication, IPC) 的代碼。若是在一個進程中(例如Activity)要調用另外一個進程中(例如Service) 對象的操做,就可使用AIDL生成可序列化的參數,來完成進程間通訊。

簡言之,AIDL可以實現進程間通訊,其內部是經過Binder機制來實現的,後面會 具體介紹,如今先介紹AIDL的使用。

2.AIDL的具體使用

AIDL的實現一共分爲三部分,一部分是客戶端,調用遠程服務。一部分是服務端, 提供服務。最後一部分,也是最關鍵的是AIDL接口,用來傳遞的參數,提供進程間 通訊。
先在服務端建立AIDL部分代碼。
AIDL文件 經過以下方式新建一個AIDL文件
談談Android Binder機制及AIDL使用
默認生成格式

interface IBookManager { 
      /**
       * Demonstrates some basic types that you can use as paramet ers 
       * and return values in AIDL. 
       */ 
     void basicTypes(int anInt, long aLong, boolean aBoolean, flo at aFloat, double aDouble, String aString);
   }

默認以下格式,因爲本例要操做Book類,實現兩個方法,添加書本和返回書本列 表。
定義一個Book類,實現Parcelable接口。

public class Book implements Parcelable { 
     public int bookId; 
     public String bookName; 

     public Book() { 
     }

     public Book(int bookId, String bookName) { 
        this.bookId = bookId; 
        this.bookName = bookName; 
     }

     public int getBookId() { 
        return bookId; 
     }

     public void setBookId(int bookId) { 
        this.bookId = bookId; 
     }
     public String getBookName() { 
        return bookName; 
     }
     public void setBookName(String bookName) { 
        this.bookName = bookName; 
     }

     @Override 
     public int describeContents() { 
        return 0; 
     }

     @Override 
     public void writeToParcel(Parcel dest, int flags) { 
        dest.writeInt(this.bookId); 
        dest.writeString(this.bookName); 
     }

     protected Book(Parcel in) { 
        this.bookId = in.readInt(); 
        this.bookName = in.readString(); 
     }
     public static final Parcelable.Creator<Book> CREATOR = new P arcelable.Creator<Book>() {
        @Override 
        public Book createFromParcel(Parcel source) { 
           return new Book(source); 
        }

        @Override 
        public Book[] newArray(int size) { 
           return new Book[size]; 
        } 
    }; 
  }

因爲AIDL只支持數據類型:基本類型(int,long,char,boolean等),String, CharSequence,List,Map,其餘類型必須使用import導入,即便它們可能在同一 個包裏,好比上面的Book。 最終IBookManager.aidl 的實現

// Declare any non-default types here with import statements import com.lvr.aidldemo.Book; 
  interface IBookManager { 
     /**
       * Demonstrates some basic types that you can use as paramet ers 
       * and return values in AIDL. 
       */ 
    void basicTypes(int anInt, long aLong, boolean aBoolean, flo at aFloat, double aDouble, String aString); 
    void addBook(in Book book); List<Book> getBookList(); 
  }

注意: 若是自定義的Parcelable對象,必須建立一個和它同名的AIDL文件,並在其 中聲明它爲parcelable類型。

Book.aidl

// Book.aidl 
  package com.lvr.aidldemo; 

  parcelable Book;

以上就是AIDL部分的實現,一共三個文件。 而後Make Project ,SDK爲自動爲咱們生成對應的Binder類。 在以下路徑下:
談談Android Binder機制及AIDL使用
其中該接口中有個重要的內部類Stub ,繼承了Binder 類,同時實現了 IBookManager接口。 這個內部類是接下來的關鍵內容。

public static abstract class Stub extends android.os.Binder impl ements com.lvr.aidldemo.IBookManager{}

服務端 服務端首先要建立一個Service用來監聽客戶端的鏈接請求。而後在Service 中實現Stub 類,並定義接口中方法的具體實現。

//實現了AIDL的抽象函數 
  private IBookManager.Stub mbinder = new IBookManager.Stub() { 
     @Override 
     public void basicTypes(int anInt, long aLong, boolean aBoole an, float aFloat, double aDouble, String aString) throws RemoteE xception {
        //什麼也不作 
     }

     @Override 
     public void addBook(Book book) throws RemoteException { 
        //添加書本 
        if (!mBookList.contains(book)) { 
             mBookList.add(book); } 
     }

     @Override 
     public List<Book> getBookList() throws RemoteException { 
       return mBookList; 
    } 
  };

當客戶端鏈接服務端,服務端就會調用以下方法:

public IBinder onBind(Intent intent) { 
     return mbinder; 
  }

就會把Stub實現對象返回給客戶端,該對象是個Binder對象,能夠實現進程間通 信。 本例就不真實模擬兩個應用之間的通訊,而是讓Service另外開啓一個進程來 模擬進程間通訊。

<service 
      android:name=".MyService" 
      android:process=":remote"> 
      <intent-filter> 
          <category android:name="android.intent.category.DEFAULT" /> 
          <action android:name="com.lvr.aidldemo.MyService" /> 
      </intent-filter> 
  </service>

android:process=":remote"設置爲另外一個進程。 &lt;action android:name="com.lvr.aidldemo.MyService"/&gt; 是爲了能讓其餘apk隱式 bindService。經過隱式調用的方式來鏈接service,須要把category設爲default, 這是由於,隱式調用的時候,intent中的category默認會被設置爲default。

客戶端
首先將服務端工程中的aidl文件夾下的內容整個拷貝到客戶端工程的對應位置下, 因爲本例的使用在一個應用中,就不須要拷貝了,其餘狀況必定不要忘記這一步。
客戶端須要作的事情比較簡單,首先須要綁定服務端的Service。

Intent intentService = new Intent(); 
  intentService.setAction("com.lvr.aidldemo.MyService"); 
  intentService.setPackage(getPackageName()); 
  intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
  MyClient.this.bindService(intentService, mServiceConnection, BIN 
  D_AUTO_CREATE); 
  Toast.makeText(getApplicationContext(), "綁定了服務", Toast.LENGTH _SHORT).show();

將服務端返回的Binder對象轉換成AIDL接口所屬的類型,接着就能夠調用AIDL中的 方法了。

if (mIBookManager != null) { 
     try {
          mIBookManager.addBook(new Book(18, "新添加的書")); 
          Toast.makeText(getApplicationContext(), mIBookManager.ge 
  tBookList().size() + "", Toast.LENGTH_SHORT).show(); 
     } catch (RemoteException e) {
     e.printStackTrace(); 
     } 
  }
3.AIDL的工做原理

Binder機制的運行主要包括三個部分:註冊服務、獲取服務和使用服務。 其中註冊 服務和獲取服務的流程涉及C的內容,因爲我的能力有限,就不予介紹了。
本篇文章主要介紹使用服務時,AIDL的工做原理。

①.Binder對象的獲取
Binder是實現跨進程通訊的基礎,那麼Binder對象在服務端和客戶端是共享的,是 同一個Binder對象。在客戶端經過Binder對象獲取實現了IInterface接口的對象來調 用遠程服務,而後經過Binder來實現參數傳遞。

那麼如何維護實現了IInterface接口的對象和獲取Binder對象呢?
服務端獲取Binder對象並保存IInterface接口對象 Binder中兩個關鍵方法:

public class Binder implement IBinder { 
     void attachInterface(IInterface plus, String descriptor) 
     IInterface queryLocalInterface(Stringdescriptor) //從IBinder 中繼承而來 
    ......
   }

Binder具備被跨進程傳輸的能力是由於它實現了IBinder接口。系統會爲每一個實現了 該接口的對象提供跨進程傳輸,這是系統給咱們的一個很大的福利。

Binder具備的完成特定任務的能力是經過它的IInterface的對象得到的,咱們能夠 簡單理解attachInterface方法會將(descriptor,plus)做爲(key,value)對存入 Binder對象中的一個Map對象中,Binder對象可經過attachInterface方法持有一個 IInterface對象(即plus)的引用,並依靠它得到完成特定任務的能力。 queryLocalInterface方法能夠認爲是根據key值(即參數 descriptor)查找相應的 IInterface對象。 在服務端進程,經過實現 private IBookManager.Stub mbinder = new IBookManager.Stub() {}抽象類,得到Binder對象。 並保存了IInterface對象。

public Stub() { 
     this.attachInterface(this, DESCRIPTOR); 
  }

客戶端獲取Binder對象並獲取IInterface接口對象 經過bindService得到Binder對象

MyClient.this.bindService(intentService, mServiceConnection, BIN D_AUTO_CREATE);

而後經過Binder對象得到IInterface對象。

private ServiceConnection mServiceConnection = new ServiceConnec tion() { 
     @Override 
     public void onServiceConnected(ComponentName name, IBinder b inder) {
       //經過服務端onBind方法返回的binder對象獲得IBookManager的實例, 獲得實例就能夠調用它的方法了 
       mIBookManager = IBookManager.Stub.asInterface(binder); 
     }
     @Override 
     public void onServiceDisconnected(ComponentName name) { 
     mIBookManager = null; 
     } 
  };

其中 asInterface(binder)方法以下:

public static com.lvr.aidldemo.IBookManager asInterface(android. os.IBinder obj) { 
     if ((obj == null)) { 
         return null; 
     }
     android.os.IInterface iin = obj.queryLocalInterface(DESCRIPT OR);
     if (((iin != null) && (iin instanceof com.lvr.aidldemo.IBook Manager))) { 
         return ((com.lvr.aidldemo.IBookManager) iin); 
     }
     return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj); 
   }

先經過 queryLocalInterface(DESCRIPTOR);查找到對應的IInterface對象,而後 判斷對象的類型,若是是同一個進程調用則返回IBookManager對象,因爲是跨進 程調用則返回Proxy對象,即Binder類的代理對象。

②.調用服務端方法
得到了Binder類的代理對象,而且經過代理對象得到了IInterface對象,那麼就能夠 調用接口的具體實現方法了,來實現調用服務端方法的目的。 以addBook方法爲例,調用該方法後,客戶端線程掛起,等待喚醒:

@Override public void addBook(com.lvr.aidldemo.Book book) th rows android.os.RemoteException 
  { 
      .......... 
      //第一個參數:識別調用哪個方法的ID 
     //第二個參數:Book的序列化傳入數據 
     //第三個參數:調用方法後返回的數據 //最後一個不用管 
     mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply , 0); 
     _reply.readException(); 
     }
     .......... 
  }

省略部分主要完成對添加的Book對象進行序列化工做,而後調用 transact 方 法。

Proxy對象中的transact調用發生後,會引發系統的注意,系統意識到Proxy對象想 找它的真身Binder對象(系統其實一直存着Binder和Proxy的對應關係)。因而系統 將這個請求中的數據轉發給Binder對象,Binder對象將會在onTransact中收到Proxy 對象傳來的數據,因而它從data中取出客戶端進程傳來的數據,又根據第一個參數 肯定想讓它執行添加書本操做,因而它就執行了響應操做,並把結果寫回reply。代 碼概略以下:

case TRANSACTION_addBook: { 
      data.enforceInterface(DESCRIPTOR); 
      com.lvr.aidldemo.Book _arg0; 
      if ((0 != data.readInt())) { 
          _arg0 = com.lvr.aidldemo.Book.CREATOR.createFromParcel(d ata);
      } else { 
          _arg0 = null; 
      }
     //這裏調用服務端實現的addBook方法 
     this.addBook(_arg0); 
     reply.writeNoException(); 
     return true; 
  }

而後在 transact 方法得到 _reply 並返回結果,本例中的addList方法沒有返回 值。

客戶端線程被喚醒。所以調用服務端方法時,應開啓子線程,防止UI線程堵塞,導 致ANR。
順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

PDF和源碼獲取

談談Android Binder機制及AIDL使用

相關文章
相關標籤/搜索