Binder機制總結

在android中進行跨進程通信通常有以下幾種方式:

  1. 使用Intent
  2. 使用文件共享
  3. 使用Messenger
  4. 使用AIDL
  5. 使用ContentProvider

其中Intent中可以攜帶Bundle,而Bundle實現了Parcelable接口,所以可以在不同的進程間進行傳輸。文件共享就是讀寫文件,比如常用的SharedPreferences就是以XML來存儲的文件。而Messenger,AIDL,ContentProvider都是基於Binder的,所以學習並理解Binder就尤爲重要。

雖然Android繼承使用Linux的內核,但Linux與Android的通信機制不同。

在Linux中使用的IPC通信機制如下:

傳統IPC:無名pipe, signal, trace, 有名管道

AT&T Unix 系統V:共享內存,信號燈,消息隊列

BSD Unix:Socket

而在Android中,並沒有使用這些,取而代之的是Binder機制。Binder機制是採用OpenBinder演化而來,在Android中使用它的原因如下:

採用C/S的通信模式。而在linux通信機制中,目前只有socket支持C/S的通信模式,但socket有其劣勢,具體參看第二條。

有更好的傳輸性能。對比於Linux的通信機制,

socket:是一個通用接口,導致其傳輸效率低,開銷大;

管道和消息隊列:因爲採用存儲轉發方式,所以至少需要拷貝2次數據,效率低;

共享內存:雖然在傳輸時沒有拷貝數據,但其控制機制複雜(比如跨進程通信時,需獲取對方進程的pid,得多種機制協同操作)。

安全性更高。Linux的IPC機制在本身的實現中,並沒有安全措施,得依賴上層協議來進行安全控制。而Binder機制的 UID/PID是由Binder機制本身在內核空間添加身份標識,安全性高;並且Binder可以建立私有通道,這是linux的通信機制所無法實現的 (Linux訪問的接入點是開放的)。

那什麼是Binder呢?

  1.     從IPC角度來說,Binder是Android中一種跨進程通信的方式。
  2.     從Android FrameWork角度來說,Binder是ServiceManager連接各種Manager和相應ManagerService的橋樑。
  3.     從Android應用層的角度來說, BInder是客戶端和服務端進行通信的媒介。

Binder通信機制流程(整體流程)

首先server會通過Binder向ServiceManager註冊自己的服務,Client想要使用遠程服務時會通過Binder去ServiceManager申請服務,申請成功之後便可以取得和Server的聯繫了,可以調用Server端的方法。

這裏面一共有四個角色,分別爲Client,Server,Binder,ServiceManager

  1. Client:客戶端,服務的使用者
  2. Server:服務端,服務的提供者
  3. Binder:月老,牽線的
  4. ServiceManager:通訊錄,記錄每個Server的聯繫方式

需要注意的一點是,Client通過Binder來跨進程取得Server的聯繫方式,但Client與ServerMangaer的通信,Server與ServerManager的通訊也是跨進程,這個問題就像買匡威鞋子一樣

哈哈哈,這個問題就要從系統剛剛啓動說起了,init進程會啓動一個叫ServiceManager的進程,該進程啓動之後會做三件事:

  • 通過open打開設備文件 /dev/binder,將該文件中的內容通過mmap映射到本進程空間中;
  • 通過IO控制命令BINDER_SET_CONTEXT_MGR將當前 進程註冊到Binder驅動中,Binder驅動便會爲他在內核空間創建一個稱爲binder_context_mgr_node的節點出來,這個節點也就是ServiceManager在內核空間的Binder實體了,並且將它的句柄設置爲0,它的創建是在系統啓動的時候,這個節點在Binder驅動中是唯一的,所以也就造成了ServiceManager區別於其他Server了;
  • ServiceManager啓動之後就會無限循環等待Server和Client之間的進程間通信請求了;接着Zygote進程會孵化出一個子進程SystemService,我們的大部分系統服務比如ActivityManagerService、 WindowManagerService、MediaPlayService等都是該進程內的一個線程,這些服務會通過調用 ServiceManager.addService方法添加到ServiceManager中的服務列表svclist中,這樣我們的系統服務就已經都 註冊到了ServiceManager裏面了;

server向ServiceManager註冊服務流程圖

Service向ServerManager中註冊自己會調用Service.addService(String name,IBinder service),傳入的內容是 Server的名字和該Server驅動中對應的對象;

  1. Server首先將自己作爲對象,並且附上一個句柄爲0的值(用於訪問ServiceManager),將這些內容封裝成一個數據包,open有關Binder的設備文件/dev/binder,將封裝好的該數據包發送給Binder驅動;
  2. Binder驅動在收到這個數據包之後,發現裏面存在一個Server對象,首先會在Binder驅動自己裏面新建該Server對應的 Binder實體,並賦予一個大於0的句柄,同時會將該句柄也加入到數據包中,接着將該數據包發送到句柄爲0對應的對象上面,也就是 ServiceManager上面了;
  3. ServiceManager收到轉發給自己的數據包之後,會查看其服務列表svlist中是否已經存在當前Server名字的服務,不存在的話,會將當前服務+當前服務對應於Binder驅動中的句柄加入到列表中;

這樣,系統服務就註冊到ServiceManager中了;

Client向ServiceManager獲取服務流程圖

Client和Binder驅動通信的時候會調用ServiceManager.getService(String name)方法,傳入的參數就是要請求的服務的名字,返回對應請求的Binder。

  1. Client首先會將要獲取的服務的名字以及一個句柄爲0的值(爲了訪問ServiceManager)封裝成一個數據包,open有關Binder的設備文件/dev/binder,將該數據包發送給Binder驅動;
  2. Binder驅動在收到數據包之後發現裏面有句柄爲0的信息,就將該數據包轉發給了ServiceManager來進行處理了;
  3. ServiceManager在收到數據包之後根據服務的名字查看自己的服務列表svclist,找到之後會將其對應的在Binder驅動中的句柄信息也封裝成一個數據包;
  4. 該數據包也會通過Binder驅動被髮送給Client端;
  5. Client端在收到數據包之後,就得到了自己所請求的服務在Binder驅動中的句柄,他會利用這個句柄信息在自己本地創建一個遠程 Server的代理,以後Client發消息都是發給這個代理的,隨後的通信便變成了代理通過Binder驅動與真正的Server進行交互了,以此完成跨進程間的通信;

這樣系統服務是怎麼註冊到ServiceManager裏面以及我們怎麼獲得這些服務對應於Binder驅動的句柄也就是Binder對象的過程講解就已經結束了,接下來便是我們自定義服務是怎麼通過Binder進行進程間通信的呢?

這裏就要用到Android爲我們自定義進程間通信所提供的AIDL文件了,沒有這個文件,你也可以實現跨進程通信,但是序列化,反序列化數據的封裝都將需要你自己來實現,有了AIDL之後這個過程會顯的比較簡單,你並不需要關心序列化反序列化的順序問題,只需要將Server進程想要提供給Client進程訪問的方法定義在一個.aidl文件中即可,我們在此將他命名爲Ixxx.aidl,那麼系統將會爲該AIDL文件自動生成對應的 Ixxx.Java文件;

        簡單說說Ixxx.java的類結構,將是下面這樣的僞代碼形式:

public interface Ixxx extends IInterface{
    public static abstarct class Stub extends Binder implements Ixxx{
        public static Ixxx asInterface(Binder binder){}  
        public Binder asBinder(){}  
        public boolean onTransact(int code,Parcel data,Parcel reply,int flags){}                                                                                                 
        private static class Proxy implements Ixxx{  
            public Binder asBinder(){}  
            //Ixxx接口中的一些實現方法,當然這些實現並不是真正的邏輯實現,而只是通過transcat進行的一些進程切換操作  

        }  

    }  
}  

        可以看到Ixxx.java中存在一個Stub靜態抽象類和Prxoy靜態類,最關鍵的方法就是Stub類中的asInterface了,他會根據我們傳入的Binder對象來判斷是跨進程通信還是進程內部通信,如果是進程內部通信,該方法返回的將是Ixxx.Stub對象;如果是跨進程通信返回的將是Ixxx.Stub.Proxy對象,這個代理對象中將的方法會通過調用transact方法來進行內核態的切換;

  1. 首先,我們需要創建一個AIDL文件,將其命名爲Ixxx.aidl,裏面定義了服務端進程想要提供給客戶端進程的方法列表,系統會爲我們生成一個Ixxx.java文件;
  2. 我們的自定義服務端主要是通過service來實現的,在service裏面實現具體提供給客戶端的方法的操作代碼,並且通過onBind方法返回此服務端對應的Binder對象,而後我們的客戶端通過bindService綁定服務端的時候就可以獲得這個服務端的Binder對象了;
  3. 在客戶端獲得Binder對象之後會調用Ixxx.Stub的asInterface方法將Binder對象傳入,獲得Ixxx對象,在這裏就將跨進程和同進程通信分開了,如果是跨進程通信的話asInterface返回的是Ixxx.Stub.Proxy代理對象,那麼以後客戶端調用服務端的方 法實際上是首先調用的Ixxx.Stub.Proxy代理對象裏面對應於服務端的方法,這個代理對象的方法會通過transact陷入內核態來進行實際上 的進程間通信調用服務端的onTransact方法,在onTransact方法中會根據標誌調用不同的服務端方法;如果是同進程通信的 話,asInterface返回的是Ixxx.Stub對象,則直接調用服務端方法,沒有必要陷入內核態來執行了;

        這也便是我們自定義服務實現進程間通信的簡單過程了;

        在使用AIDL實現Binder通信的過程中,我們應該注意一點的就是,AIDL中不管是服務端方法還是客戶端方法都是運行在各自的Binder線程池中的,同時客戶端的線程會被掛起,如果服務端方法執行比較耗時,就會導致客戶端線程長時間阻塞,導致 ANR 。並且如果我們想要更新UI的話,還需要用到Handler進行切換操作;

匿名Binder

之前在介紹Android使用Binder機制的優點中,提到Binder可以建立點對點的私有通道,匿名Binder就是這種方式。在 Binder通信中,並不是所有用來通信的Binder實體都需要註冊給SM廣而告之的,Server可以通過已建立的實體Binder連接將創建的 Binder實體傳給Client。而這個Binder沒有向SM註冊名字。這樣Server與Client的通信就有很高的隱私性和安全性。

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