1. 程式人生 > >Android中Binder機制實現程序間通訊

Android中Binder機制實現程序間通訊

           基本上不管是何種開發都會涉及到程序間通訊的問題,即IPC,而安卓系統的IPC方式主要是Binder,先列舉幾種IPC的方式,對比Binder看看。

      Linux裡幾種比較常見的IPC方式比如有:共享記憶體,訊號,Socket,管道(pipe),訊息佇列等。

     先從效能上說起:

     其中共享記憶體,顧名思義就是多個程序共享一塊記憶體,大家有什麼改變就直接在這塊共同記憶體裡改,這樣看起來十分方便,資料也無需任何的拷貝,但是共享一塊記憶體,安全性上顯然會出很多問題,必須做好程序同步等問題,所以控制起來比較複雜。

     而Socket,即套接字,我們一般都是在網路請求,網路通訊的時候使用它,如果使用它來實現同一機器上的程序間通訊,由於他傳輸效率較低,顯然開銷會很大,不適合我們這裡所說的程序間的通訊。

    其次就是管道,訊息佇列等,利用他們實現傳輸通訊,都需要把資料先從傳送方的快取區copy到核心開闢的快取區,然後再從核心開闢的快取區copy到接收方的快取區,這樣經過兩次的copy才能實現程序間的通訊,所以傳輸效能上並不優秀。

      再從安全性上考慮:

     傳統的IPC,並沒有給傳送方程序分配UID和PID(即使用者ID和程序ID),而是在傳送的時候由使用者填入資料包,這就給程式留下風險,即存在資料在放鬆過程中PID被修改的可能,也就是接收方無法獲取可靠的傳送方的UID和PID(無法鑑定身份),而安卓為每一個應用程式到分配好UID,這樣當這個程序產生時他就已經有了一個唯一的PID,所以是可靠的。

    從以上的對比 我們可以看出Binder相較於其他IPC方式的優點,接下來就來看看Binder機制是如何實現程序間的通訊的。

    先來貼一張圖:

   

這個就是通過Binder實現服務端程序與客戶端程序之間通訊的這麼一個架構。

其中包括三部分,服務端,Binder驅動,客戶端。

Binder伺服器端:就是一個繼承於Binder的物件,它裡面定義實現了各種方法(服務)

Binder驅動:當伺服器端建立一個Binder物件的時候,Binder驅動裡會相應的建立一個mRemote物件,和服務端一樣,都是個                                Binder物件。

客戶端:客戶端可以通過獲取Binder驅動的mRemote物件的引用,然後就可以呼叫Binder物件的服務了。

Binder機制下的兩個程序之間的通訊基本就是上面那樣一個過程:即服務端定義服務,客戶端通過獲取服務端的物件引用的方式(通過中介Binder驅動),然後就可以呼叫服務端定義的各種服務。

然後還有一個問題:那麼客戶端是如何獲取到這個Binder引用的呢

先來看看我們自定義一個service的時候,如何來和這個service通訊:

服務端定義Servcie的時候,裡面一般會定義個Binder類(這個Binder才是真正提供服務的),然後Service內部的OnBind()方法會把這個Binder返回,客戶端呼叫bindService(),即與服務端的服務繫結,會呼叫到服務端的onBind()方法,而bindService()這個方法裡面有個引數是ServiceConnnection mConnection,客戶端建立一個ServiceConnection就可以在裡面會獲取到連線後服務端Binder的

</pre><span style="font-size:14px;">引用,從而實現客戶端獲取到服務端Binder引用,下面我們看一下客戶端ServiceConnection:</span><p></p><p></p><pre code_snippet_id="1655977" snippet_file_name="blog_20160421_2_4458442" name="code" class="html"><span style="font-size:14px;"><span style="font-size:18px;">private ServiceConnection mConnection = new ServiceConnection() {  
    // 當與service的連線建立後被呼叫  
    public void onServiceConnected(ComponentName className, IBinder service) {  //注意這裡的IBinder即獲取到的Binder引用
       
            //獲取到Binder引用後就可以調服務端的服務了
    }  
  
    // 當與service的連線意外斷開時被呼叫  
    public void onServiceDisconnected(ComponentName className) {  
        
    }  
};  </span></span>

看了上面這些我們應該知道,Binder機制實現程序間通訊的關鍵就是獲取到服務端的代理,只要獲取到服務端的代理,那麼就可以呼叫服務端的方法,實現通訊,所以關鍵所在就是這個代理如何獲取,接下來看看:

安卓裡面管理這些服務的有一個東西叫做ServiceManager(他本身也是個Service),當我們想要獲取某個服務的代理時,就必須通過他,ServiceManager掌握著所有Service的控制代碼,即你只要找他,他就能給你找到你要的service的控制代碼,然後有了控制代碼就可以得到BpBinder(Binder代理)了,也就是可以獲取到你想要的服務的代理了,下面是典型的獲取服務端service代理的過程(獲取AMS代理的過程):

IBinder b=ServiceManager.getService("activity");

然後轉化為AMS代理:

IActivityManager am=asInterface(b);

可以知道,如果想要獲取某個服務端的代理,那麼通過ServiceManager就十分簡單了,但是有一個問題:serviceManager本身也是一個service,那麼他自己是如何被客戶端獲取的呢,因為只有先獲取了ServiceManager我們才能通過他很方面的來獲取其他的service.上面我們知道服務端代理的建立過程是我們先拿到控制代碼,然後就可以建立它,而ServiceManager預設控制代碼為0,即已經為他預定了一個控制代碼,所以他只要通過new BpBinder(0)就可以得到BpBinder,也就是Binder代理,而其他的服務,你無法知道控制代碼值是多少,你只能通過ServiceManager了。

上面介紹了客戶端獲取服務端代理的過程,那麼服務端也是需要和客戶端進行通訊的,即服務端也想調客戶端的方法,也就是服務端如何獲取客戶端的Binder代理呢,因為既然客戶端已經可以與服務端通訊了,可以調服務端的方法了,那麼客戶端就可以把自己這邊的Binder代理作為引數傳給服務端端了,這也是另一種獲取Binder代理的方法。這樣雙方進行雙向的通訊就沒有問題了,各自持有對方的Binder代理,通訊就非常容易了。下面放一張網上的圖來梳理一下這種雙向通訊:


再貼一張網上的整個Binder的原理圖:


下面舉個很形象簡單明瞭的例子來理解一下Binder機制:

總的來說Binder分為一下四個部分:

1.client客戶端    2.service服務端   3.Binder驅動  4.ServerManager

用我們平常網頁請求伺服器資料的例子再形象不過了,

瀏覽器就相當於這裡的client客戶端,伺服器就相當於service服務端,而路由器就相當於這裡的Binder驅動(負責傳輸),而DNS伺服器就相當於這裡的ServerManager(手裡握有每個server的控制代碼和實體的對映表)。這應該很明白了。

下面詳解一下這四個部分的配合完成跨程序傳輸的全過程:

首先我們知道所有想要獲取service的客戶端都是向serviceManager要(這就像我們想要訪問一個網頁,需要先通過DNS伺服器解析地址一樣),serviceManager本身也是一個service,所以我們先搞懂我們是如何獲取到serviceManger的代理物件的?

  我們知道客戶端與服務端Binder通訊,實際上上是客戶端獲取到伺服器端service的一個代理物件,而這一步一般分兩步完成,先是獲取到伺服器端的BpBinder物件,然後呼叫asInterface()轉化為service的一個代理物件。而獲取serviceManger的代理物件也是這兩步,服務端真正的serviceManger實際上使用C實現的,我們平常用的只不過是java層獲取到的一個serviceManger的代理物件:

得到ServiceManager的Java層代理物件   sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); 

首先BinderInternal.getContextObject()就是獲取到serviceManger的BpBinder物件(這是Native層的事了),一般其他的service是需要在serviceManager裡註冊自己,然後客戶端才可以通過serviceManager來獲取到BpBinder,然而serviceManager這個服務是不需要註冊的,預設給他一個控制代碼了,就是0,所以通過BinderInternal.getContextObject()直接可以獲取到BpBinder,然後通過asInterface(),這樣java層就得到了serviceManager的代理物件了,也就是serviceManagerProxy.

   上面已經很詳細的講解了客戶端如何獲取serviceManager代理物件的過程,有了serviceManager,那麼上面說的Binder機制的四個部分就都具備了,接下來說說通過serviceManger,普通的服務端和客戶端的通訊過程。

首先服務需要在serviceManager裡註冊,即需要呼叫serviceManagerProxy的addService()來註冊自己,而addService()裡面要傳兩個引數:1.service的名字 2.service的binder實體,所以addService()裡面實際上就是把service名字和binder實體封裝在Parcel包裡,然後呼叫transact()來發送這個包到Binder驅動裡,Binder驅動接收到這個包後,如果發現這個Binder是新傳遞來的,那麼就會為期在核心空間裡建立一個相應的Binder實體節點和一個對該節點的引用,建立完畢後,Binder驅動就會把該引用傳遞給serverManager,serverManager收到後就會從中取出Binder的名字和引用插入到一張資料表裡,也就是對映表。

    接下來就是客戶端如何通過serverManager(serverManager在java層的代理物件可以通過封裝的方法getIServiceManager()來得到,它裡面也就是上面我們說的如何得到serverManager的代理物件來獲取服務端的BpBinder物件並轉化為service的代理物件的過程了。客戶端通過serverManager的getservice(引數為服務名稱)就可以獲取到相應服務的BpBinder物件了,那麼getService()裡面做了些上面呢?他裡面是通過parcel包的形式把我們的請求通過 transact()的方式傳遞給Native層的serverManager的BpBinder物件,然後Native層的這個serverManager負責來找到你想要的服務的BpBinder,並給你返回去。這就是客戶端獲取服務BpBinder的過程,然後只要通過asInterface()就可以轉BpBinder為server的代理物件了。

   至此客戶端獲取到服務端的代理物件,可以很方便的進行通訊了。

基本上這就是利用Binder機制進行通訊的過程了,當然這邊沒有對Binder驅動的實現,以及Native層進行更深入的討論。

下面分享兩篇關於Binder我覺得很不錯的寫的很細緻的文章:

http://m.oschina.net/blog/149578

http://www.360doc.cn/article/168576_425018852.html

 以上就是安卓中Binder機制實現程序間通訊的過程。