Android Binder機制及AIDL使用

Binder原理

1、概述

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

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

為什麼要使用Binder?

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

安全方面

傳統的進程通信方式對於通信雙方的身份並沒有做出嚴格的驗證,比如Socket通信 的IP地址是客戶端手動填入,很容易進行偽造。然而,Binder機制從協議本身就支 持對通信雙方做身份校檢,從而大大提升了安全性。

2、 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通信的四個角色

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是服務端。

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

Binder運行的實例解釋

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

<code>  //獲取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);
/<code>

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

使用服務的具體執行過程
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使用

默認生成格式

<code>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);
   }
/<code>

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

<code> 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 CREATOR = new P arcelable.Creator() {
        @Override 
        public Book createFromParcel(Parcel source) { 
           return new Book(source); 
        }
        
        @Override 
        public Book[] newArray(int size) { 
           return new Book[size]; 
        } 
    }; 
  }
/<code>

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

<code>  // 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 getBookList(); 
  }
/<code>

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

Book.aidl
<code>  // Book.aidl 
  package com.lvr.aidldemo; 

  parcelable Book;
/<code>

以上就是AIDL部分的實現,一共三個文件。 然後Make Project ,SDK為自動為我們生成對應的Binder類。 在如下路徑下:


Android Binder機制及AIDL使用


其中該接口中有個重要的內部類Stub ,繼承了Binder 類,同時實現了 IBookManager接口。 這個內部類是接下來的關鍵內容。

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

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

<code> //實現了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 getBookList() throws RemoteException { 
       return mBookList; 
    } 
  };
/<code>

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

<code>  public IBinder onBind(Intent intent) { 
     return mbinder; 
  }
/<code>

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

<code> 
       
           
           
       
  
/<code>

android:process=":remote"設置為另一個進程。 是為了能讓其他apk隱式 bindService。通過隱式調用的方式來連接service,需要把category設為default, 這是因為,隱式調用的時候,intent中的category默認會被設置為default。

客戶端

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

<code>  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();
/<code>

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

<code> 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(); 
     } 
  }
/<code>

3.AIDL的工作原理

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

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

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

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

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對象。

<code> public Stub() { 
     this.attachInterface(this, DESCRIPTOR); 
  }
/<code>

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

<code> MyClient.this.bindService(intentService, mServiceConnection, BIN D_AUTO_CREATE);
/<code>

然後通過Binder對象獲得IInterface對象。

<code> 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; 
     } 
  };
/<code>

其中 asInterface(binder)方法如下:

<code> 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); 
   }
/<code>

先通過queryLocalInterface(DESCRIPTOR);查找到對應的IInterface對象,然後 判斷對象的類型,如果是同一個進程調用則返回IBookManager對象,由於是跨進 程調用則返回Proxy對象,即Binder類的代理對象。②.調用服務端方法獲得了Binder類的代理對象,並且通過代理對象獲得了IInterface對象,那麼就可以 調用接口的具體實現方法了,來實現調用服務端方法的目的。 以addBook方法為例,調用該方法後,客戶端線程掛起,等待喚醒:

<code>  @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(); 
     }
     .......... 
  }
/<code>

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

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

<code> 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; 
  }
/<code>

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

PDF和源碼獲取

Android Binder機制及AIDL使用

資料免費領取方式:轉發後關注我後臺私信關鍵詞【資料】獲取!


分享到:


相關文章: