【Android - IPC】之Binder機制簡介


參考資料:

1、《Android開發藝術探索》第二章2.3.3 Binder

2、【Android Binder設計與實現-設計篇

3、【Android Binder機制介紹

 

1、 什么是Binder

Binder從不同角度上的定義:

  • 直觀來說,Binder是Android中的一個類,它實現了IBinder接口;
  • 從IPC角度來說,Binder是Android中的一種跨進程通信方式,該通信方式在Linux中沒有;
  • 從Android Framework角度來說,Binder是ServiceManager連接各種Manager和相應ManagerService的橋梁;
  • 從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據了。

Binder機制具體有兩層含義:

  • Binder是一種跨進程通信(IPC,Inter-Process Communication)的手段;
  • Binder是一種遠程過程調用(RPC,Remote Procedure Call)的手段。

 

2、 為什么使用Binder

  Android系統是基於Linux系統的,理論上應該使用Linux內置的IPC方式。Linux中的IPC方式有管道、信號量、共享內存、消息隊列、Socket,Android使用的Binder機制不屬於Linux。Android不繼承Linux中原有的IPC方式,而選擇使用Binder,說明Binder具有一定的優勢。

  Android系統為了向應用開發者提供豐富的功能,廣泛的使用了Client-Server通信方式,如媒體播放、各種傳感器等,Client-Server的通信方式是Android IPC的核心,應用程序只需要作為Client端,與這些Server建立連接,即可使用這些功能服務。

  下面通過一些功能點,一一排除Linux系統中五種IPC方式,解釋為什么Android選擇使用Binder:

  • 從通信方式上說,我們希望得到的是一種Client-Server的通信方式,但在Linux的五種IPC機制中,只有Socket支持這種通信方式。雖然我們可以通過在另外四種方式的基礎上架設一些協議來實現Client-Server通信,但這樣增加了系統的復雜性,在手機這種條件復雜、資源稀缺的環境下,也難以保證可靠性;
  • 從傳輸性能上說,Socket作為一款通用接口,其傳輸效率低,開銷大,主要用在跨網絡的進程間通信和本機上進程間的低速通信;消息隊列和管道采用存儲-轉發方式,即數據先從發送方拷貝到內存開辟的緩存區中,然后再從內核緩存區拷貝到接收方緩存區,至少有兩次拷貝過程;共享內存雖然無需拷貝,但控制復雜,難以使用;而Binder只需要拷貝一次;
  • 從安全性上說,Android作為一個開放式的平台,應用程序的來源廣泛,因此確保只能終端的安全是非常重要的。Linux傳統的IPC沒有任何安全措施,完全依賴上層協議來確保,具體有以下兩點表現:第一,傳統IPC的接收方無法獲得對方可靠的UID/PID(用戶ID/進程ID),從而無法鑒別對方身份,使用傳統IPC時只能由用戶在數據包里填入UID/PID,但這樣不可靠,容易被惡意程序利用;第二,傳統IPC的訪問接入點是開放的,無法建立私有通信,只要知道這些接入點的程序都可以和對端建立連接,這樣無法阻止惡意程序通過猜測接收方的地址獲得連接。

  基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通信方式、傳輸性能和安全性的要求,這就是Binder。

  綜上,Binder是一種基於Client-Server通信模式的通信方式,傳輸過程只需要一次拷貝,可以為發送方添加UID/PID身份,支持實名Binder和匿名Binder,安全性高。

 

3、 Binder的組成

Binder由四部分組成:Binder客戶端、Binder服務端、Binder驅動、服務登記查詢模塊。

  • Binder客戶端:

Binder客戶端是想要使用服務的進程。

  • Binder服務端:

Binder服務端是實際提供服務的進程。

  • Binder驅動:

我們在客戶端先通過Binder拿到一個服務端進程中的一個對象的引用,通過這個引用,直接調用對象的方法獲取結果。在這個引用對象執行方法時,它是先將方法調用的請求傳給Binder驅動;然后Binder驅動再將請求傳給服務端進程;服務端進程收到請求后,調用服務端“真正”的對象來執行所調用的方法;得出結果后,將結果發給Binder驅動;Binder驅動再將結果發給我們的客戶端;最終,我們在客戶端進程的調用就有了返回值。Binder驅動,相當於一個中轉者的角色。通過這個中轉者的幫忙,我們就可以調用其它進程中的對象。

  • Service Manager(服務登記查詢模塊):

我們調用其它進程里面的對象時,首先要獲取這個對象。這個對象其實代表了另外一個進程能給我們提供什么樣的服務(再直接一點,就是:對象中有哪些方法可以讓客戶端進程調用)。首先服務端進程要在某個地方注冊登記一下,告訴系統我有個對象可以公開給其它進程來提供服務。當客戶端進程需要這個服務時,就去這個登記的地方通過查詢來找到這個對象。

 

4、 Binder機制簡述

  上面說到,Binder有兩個含義,分別是進程間通信和遠程過程調用。Android的整個跨進成機制都是基於Binder的,這種機制不但會在底層調用,也會在上層調用,所以必須提供C++和Java兩個層次的支持。下面是兩種情況下Binder的原理圖。

 

進程間通信的Binder原理圖

  上圖是進程間通信時的Binder原理圖。圖中進程A中的小圓圈代表“Binder代理方”BpBinder,用於向遠方發送語義;進程B中的方塊代表“Binder響應方”BBinder,主要用於響應語義。

 

遠程過程調用的Binder原理圖

  上圖是遠程過程調用時的Binder原理圖。此時,進程A不再直接和Binder代理BpBinder打交道,而是通過聚合了BpBinder的“接口代理”BpInterface的成員函數來完成遠程調用;而進程B則通過Binder響應方BBinder的繼承類“接口實現體”BnInterface來響應進程A發來的請求。如此依賴,在遠程過程調用的操作中,客戶端需要完成Binder代理和接口代理,而服務端需要完成接口實現體。

  需要說明的是,上面兩圖中的BpBinder、BpInterface、BBinder和BnInterface都是C++中的層次概念,Java中沒有這些。

  這里只針對Java中的Binder層次進行解釋,不涉及C++的代碼。

 

5、 Binder工作流程

  假設:客戶端的程序Client運行在進程A中,服務端的程序Server運行在進程B中。

  由於進程的隔離性,Client不能讀寫Server中的內容,但內核可以,而Binder驅動就是運行在內核態,因此Binder驅動幫我們進行請求的中轉。

  有了Binder驅動,Client和Server之間就可以打交道了,但是為了實現功能的單一性,我們為Client和Server分別設置一個代理:Client的代理Proxy和Server的代理Stub。這樣,由進程A中的Proxy和進程B中的Stub通過Binder驅動進行數據交流,Server和Client直接調用Stub和Proxy的接口返回數據即可。

  此時,Client直接調用Proxy這個聚合了Binder的類,我們可以使用一系列的Manager來屏蔽掉Binder的實現細節,Client直接調用Manager中的方法獲取數據,這樣做的好處是Client不需要知道Binder具體怎么工作。

  最后還有一個問題,就是Client想要獲得的服務多種多樣,那么它是怎么獲取Proxy或Manager的呢?答案是通過Service Manager進程來獲取的。Service Manager總是第一個啟動的服務,其他服務端進程啟動后,可以在Service Manager中注冊,這樣Client就可以通過Service Manager來獲取服務器的服務列表,進而選擇具體調用的服務器進程方法。

  上面的敘述總結為如下圖所示的工作流程圖:

 

 

6、 Binder示例代碼

  在Android開發中,Binder主要用在Service中,其中普通的Service中的Binder不涉及進程間通信,所以較為簡單,而AIDL和基於AIDL的Messenger涉及到進程間通信,因此,這里選擇使用AIDL來分析Binder的工作機制。

  在這個例子中,我們將通過AIDL的方式將一個Book實體類的對象從服務端傳遞到客戶端進行展示。

  先創建一個Book實體類,為了能在進程間傳遞,需要序列化Book,代碼如下:

public class Book implements Parcelable {
    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

  創建一個Book.aidl文件,代碼如下:

package my.itgungnir.ipc.binder;

parcelable Book;

  創建一個IBookManager.aidl文件,代碼如下:

package my.itgungnir.ipc.binder;

import my.itgungnir.ipc.binder.Book; // 雖然這個文件和Book.aidl文件在同一個包下,但還是要導入類,這就是AIDL的特點

interface IBookManager {
    Book getBook();
}

  創建了這三個文件之后,運行一遍項目,這時會在項目的build/generated/source/aidl/debug/包名目錄下生成一個IBookManager.java的文件,這個類是系統根據我們編寫的IBookManager.aidl文件自動生成的Binder類。這個類的代碼如下:

package my.itgungnir.ipc.binder;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements my.itgungnir.ipc.binder.IBookManager {
        private static final java.lang.String DESCRIPTOR = "my.itgungnir.ipc.binder.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an my.itgungnir.ipc.binder.IBookManager interface,
         * generating a proxy if needed.
         */
        public static my.itgungnir.ipc.binder.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof my.itgungnir.ipc.binder.IBookManager))) {
                return ((my.itgungnir.ipc.binder.IBookManager) iin);
            }
            return new my.itgungnir.ipc.binder.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBook: {
                    data.enforceInterface(DESCRIPTOR);
                    my.itgungnir.ipc.binder.Book _result = this.getBook();
                    reply.writeNoException();
                    if ((_result != null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements my.itgungnir.ipc.binder.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public my.itgungnir.ipc.binder.Book getBook() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                my.itgungnir.ipc.binder.Book _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBook, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        _result = my.itgungnir.ipc.binder.Book.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public my.itgungnir.ipc.binder.Book getBook() throws android.os.RemoteException;
}

  這個類中的結構如下圖所示:

 

  下面來逐一解釋這個類中的每個元素代表的含義:

  • 最外層的getBook()一個抽象方法,就是我們在IBookManager.aidl文件中聲明的方法;
  • TRANSACTION_getBook一個整形的ID,用於表示客戶端請求的是哪個方法;
  • DESCRIPTORBinder的唯一標識,一般用當前Binder的包路徑表示;
  • asInterface()判斷當前進程是服務端進程還是客戶端進程,如果是服務端進程則返回Stub對象,否則返回Stub.Proxy對象;
  • asBinder()返回當前的Binder對象;
  • onTransact(int code, Parcel data, Parcel reply, int flag)這個方法運行在服務端的Binder線程池中,當客戶端發起請求時,就由這個方法來處理請求。服務端通過code獲取客戶端想要訪問的目標方法;通過data來獲取目標方法所需的參數;執行完目標方法后,將返回值寫入到reply中。另外,如果這個方法返回false,則客戶端請求失敗,我們可以通過這一點來判斷客戶端是否有權訪問我們的服務;
  • Proxy#getBook()這個方法運行在客戶端,其內部實現是這樣的:首先創建三個對象,_data用來存儲這個方法的參數信息;_reply用來存儲從服務端返回的數據;_result用來作為返回值返回,然后調用transact()方法發起RPC(遠程過程調用)請求,調用服務端的onTransact()方法,同時當前線程掛起;當RPC過程返回后,當前線程繼續執行,經過一系列處理后返回_result結果。

 

使用Binder還需要注意:

  當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是耗時的,那么不能放到UI線程中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM