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機制實現程序間通訊的過程。