理解 Android 的 Binder 機制

wjn_002 8年前發布 | 7K 次閱讀 安卓開發 Android開發 移動開發

可以說 Binder 是 Android 底層系統的一個特色了,它很好地解決了進程間通訊的問題。其實網上有很多介紹 Binder 的文章,那么本文還是想將 Binder 這部分內容細化一下,更適合于初學者閱讀。

Binder 產生的背景

首先我們說說為什么會出現 Binder 這個東西。作為 iOS 開發者,我還是情不自禁地想去談談 iOS app,事實上,iOS 的每一個 app 都是一個獨立的進程,它沒有 Android 那種比較開放的多進程通訊能力,甚至 App 與 Extension (如通知中心插件)之間都不能有一種非常直接的通訊方式,當然不是說 iOS 沒有 IPC 技術,其實 mach 內核也是有著不錯的 IPC 技術的,但這不是本文的重點。Android 則不太一樣,Android apps 基本上都需要各式各樣的 IPC 需求,甚至啟動一個 Activity 也需要用到 IPC,有一些 IPC 調用也許你并不知曉,可能對開發者最可見的就是用 AIDL 去寫一個 Remote Service 接口了。

Android 很多核心功能都是由一系列 Services 支持的,比如 ActivityService、WindowService 等等等等,應用需要頻繁地與這些 Services 發生交互,正是基于這種場景,就亟需一種好的 IPC 解決方案。

你可能會想,為什么不是 Local Socket ?或者 Shared Memory ,那是因為安全性無法得到保障。Android 的權限系統需要一種可靠的方式來保證各種 Services 的訪問是在權限系統的監控下進行的,上述提到的解決方案就做不到了,因為不管是套接字還是共享內存,現有的 Linux 內核都不存在一種檢驗雙方身份的方法存在,任何通過套接字或者共享內存走的數據都可以偽造,而在這個基礎上做任何驗證,代價都是相當高的。Android 的選擇是基于內核,重新開發一套 IPC 機制,讓它固有這些特性,也就是讓系統可以在 Ring0 級保障交互雙方身份的正確性,并且這種基于內核的方案效率還很高。

既然要基于內核,就一定要對內核動手腳,Android 采用驅動的方式實現這個技術,而不是直接修改 Linux 內核。這樣你就可以假設,手機中有一個“設備”,應用之間通過這個設備來交互,而這個設備自身有一套身份校驗機制,這樣就比基于用戶態的 IPC 方案來的安全得多,也快得多了。

Binder 是怎么工作的

我們暫且不需要深入理解 Binder 驅動底層的實現,也不需要知道 Binder 驅動提供了什么接口,我們就來看看一個 app 是如何通過 Binder 這個機制來實現跨進程通信的。

到這里,你可以把 Binder 驅動看作一個機器,它連接著所有 Services(假設 app 只調用 Services 提供的接口)。

一個 app(客戶端)想要找一個 Service 辦點事,它就要去操作這個機器,而這個機器會檢測 app 的身份,如果身份合法,則可以繼續操作。假設 app 要調用 A 服務的 foo 函數,那么它可以告訴這個機器,這個機器隨后就會檢查連在它身上的所有 Services,找到 app 需要的那一個,讓它執行 foo 函數,并且再將返回值告訴 app,這樣 app 就成功隔著進程操作到 A 服務了。

這當然是很抽象的說法,系統在 Framework 層做了很好地封裝,讓我們可以友好地使用 Binder 驅動來進行 IPC 操作,下面我們就直接看應用層所提供的接口是如何工作的。

探究與 Binder 相關的 Java 類

要說這些類,我們首先要用到它們,最簡單的方式就是去創建一個 Service,讓它運行在單獨的進程中,然后編寫 AIDL,實現一個接口,再到 Activity 中去使用這個接口。 (注意:本文不介紹 Remote Service 與 AIDL 的相關知識,如果讀者對這部分內容還不夠了解,請先將它們弄懂再回來看本文)

AIDL 代碼生成器為我們創建了一個 java 文件,這里面涉及到幾個類:

這些就是在應用層使用 Binder 所需的所有類了,看類圖有些錯綜,但實際還是很簡單的。

AIDL 生成的就是 IMyService 這個接口,而 Stub 和 Proxy 則是這個接口的兩個內部類,分別是 Binder 類和 BinderProxy 類的子類( Proxy 類雖然是用組合方式來持有 BinderProxy 的,但實際就是在直接用這個類,只不過做了一層封裝,讓其更易使用而已), Stub 和 Proxy 都實現了 IMyService 。

所以 IInterface 到底是什么,它就是一個用于表達 Service 提供的功能的一個契約,也就是說 IInterface 里有的方法, Service 都能提供,調用者你別管用的是 BinderProxy 還是什么,只要拿到 IInterface ,你就可以直接調用里面的方法,它就是一個接口。

同時 Stub 雖然實現了 IMyService ,但是并沒有實現厘米的任何方法,它是一個抽象類,開發者需要自己子類化 Stub 去實現具體的功能。

Proxy 實現了 IMyService ,并且實現了里面的方法,這些方法的實現我們下面再說。

為什么 IMyService 要分 Stub 和 Proxy 呢?這是為了要適用于本地調用和遠程調用兩種情況。如果 Service 運行在同一個進程,那就直接用 Stub ,因為它直接實現了 Service 提供的功能,不需要任何 IPC 過程。如果 Service 運行在其他進程,那客戶端使用的就是 Proxy ,這里這個 Proxy 的功能大家應該能想到了吧,就是把參數封裝后發送給 Binder 驅動,然后執行一系列 IPC 操作最后再取出結果返回。

好,到這里請求用 Proxy 發出去了,Service 怎么接受請求并作出響應呢,這就需要 Stub 了,還記得我們的 Stub 是繼承自 Binder 的嗎? Binder 有一個 onTransact 方法,而 Stub 重寫了這個函數,這個函數三個重要參數: int code 、 Parcel data 、 Parcel reply ,分別對應了被調函數編號、參數包、響應包。當 Proxy 發起了一個請求,服務端中相應的響應線程就會通過 JNI 調用到 Stub 類,然后執行里面的 execTransact 方法,進而轉到 onTransact 方法。(這一系列調用鏈大家可以從源碼中分析出來,我這里作為拋磚引玉,就不帶大家分析 native 層的代碼了)

我們來看一眼這個 onTransact :

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code)    {
        case INTERFACE_TRANSACTION:
        {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_increaseCounter:
        {
            data.enforceInterface(DESCRIPTOR);
            int _arg0;
            _arg0 = data.readInt();
            int _result = this.increaseCounter(_arg0);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

是不是非常清晰易懂,就是根據傳過來的數據包來做相應的操作,然后把結果寫回數據包,Binder 驅動會來幫我們做好這些數據包的分發工作。而這段代碼是運行在 Service 本地進程中的,它可以直接調用實現好的 Stub 類中的相關方法(本例子中是 increaseCounter 方法)。

下面我們趁熱打鐵再來看一眼 Proxy 類中的 increaseCounter 是怎么實現的:

@Override
public int increaseCounter(int increment) throws RemoteException{
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeInt(increment);
        mRemote.transact(Stub.TRANSACTION_increaseCounter, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readInt();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

正好與 Stub 中的處理能夠對應起來,其實這兩段代碼就是整個 IPC 的核心了,Binder 驅動和 Binder 類在底層幫我們做好了其他一切事情。

休息一下。

下面我們來思考另一件事情,如何判斷 Service 運行在同一進程還是不同進程?

我們知道, Service 有一個 onBind 方法,這里面就返回了我們實現好的 Stub 類,而客戶端 bind service 時拿到的又是一個 IBinder 對象,我們每次只需要調用 Stub 的 asInterface 靜態方法,把這個 IBinder 對象傳進去就能拿到 Stub 類或者 Proxy 類了,看起來十分智能!那么這個 asInterface 又蘊藏什么玄機呢?我們來看一眼實現代碼:

public static IBackgroundService asInterface(IBinder obj){
    if ((obj==null)) {
        return null;
    }
    IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof IBackgroundService))) {
        return ((IBackgroundService) iin);
    }
    return new IBackgroundService.Stub.Proxy(obj);
}

是不是有點莫名其妙, queryLocalInterface 是什么鬼?

我們再來看看 queryLocalInterface 的實現:

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

它判斷了一下 descriptor 參數是否與自身 owner 的 descriptor 一致,如果一致就直接返回 owner ,那么 owner 和 descriptor 是在哪被設置的呢,就是在 Stub 的構造函數中被設置的。

于是乎,如果 Service 運行在同一進程,那么客戶端拿到的 IBinder 就是 Stub 類,而 Stub 的 queryLocalInterface 又會返回自己;而 Service 運行在單獨進程中時,客戶端拿到的 IBinder 就是系統提供好的 BinderProxy , BinderProxy 中的 queryLocalInterface 默認直接返回 null ,根據代碼, asInterface 就會構造一個 Proxy 返回給客戶端,那么接下來的故事就是上面我們講過的了。

自己利用 Binder 來進行 IPC

有了上面的基礎,其實我們完全不需要 AIDL 了有木有,自己用 Binder 類和 BinderProxy 類就完全可以實現 Service 與客戶端的通訊,下面我就速速寫一個簡單的例子。

Service 中的 onBinder 方法我這樣實現:

客戶端就這樣:

我們甚至不必按照 AIDL 的通信規范,自己處理 data 和 reply 也是完全可以的,但這只是為了演示 Binder 的原理,日常開發中還是不要這么做了吧。

小結

Binder 的設計非常優秀,分析 Binder 的實現也是一件十分有趣的事情,本文作為對 Binder 深入研究的「引入」,站在了一個很高的角度俯視整套系統,并沒有對其中的細節做深入探討,如果大家對內核開發或者操作系統底層有興趣的話,也可以去看看 Binder 驅動的實現,相信也會有不少收獲,本文到這里就先結束了 ;-)

 

來自:http://www.jianshu.com/p/f4f722d3e51d

 

 本文由用戶 wjn_002 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!