談談Android Binder機制及AIDL使用

Binder原理

1、概述

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

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

順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找
https://github.com/xiangjiana/Android-MS
更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。
可以點擊關於我聯繫我獲取

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

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

2、 Binder

IPC原理
從進程角度來看IPC(Interprocess Communication)機制

每個Android的進程,只能運行在自己進程所擁有的虛擬地址空間。例如,對應一 個4GB的虛擬地址空間,其中3GB是用戶空間,1GB是內核空間。當然內核空間的 大小是可以通過參數配置調整的。對於用戶空間,不同進程之間是不能共享的,而 內核空間卻是可共享的。Client進程向Server進程通信,恰恰是利用進程間可共享 的內核內存空間來完成底層通信工作的。Client端與Server端進程往往採用ioctl等方 法與內核空間的驅動進行交互。

Binder原理
Binder通信採用C/S架構,從組件視角來說,包含Client、Server、ServiceManager 以及Binder驅動,其中ServiceManager用於管理系統中的各種服務。架構圖如下所 示:

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

圖中的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函數。
使用服務的具體執行過程

  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文件

默認生成格式

  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類。 在如下路徑下:

其中該接口中有個重要的內部類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"設置爲另一個進程。 <action android:name="com.lvr.aidldemo.MyService"/> 是爲了能讓其他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

更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。
可以點擊關於我聯繫我獲取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章