輕松理解 Android Binder,只需要讀這一篇

在 Android 系統中,Binder 起著非常重要的作用,它是整個系統 IPC 的基石。網上已經有很多文章講述 Binder 的原理,有的講的比較淺顯,沒有觸及到關鍵,有的講的太過于深入底層,難以理解,本文會比較全面,以一個比較輕松的方式,從面到點,大處著眼,小處著手的形式去講述 Binder 在 Android 中是如何使用的。理解 Binder 的基本原理,對學習 Android 也有很大的幫助,很多問題也能夠得到解釋,例如 ContentProvider 中的 CRUD 是否是線程安全的?又例如在使用 AIDL 的時候,在 Service 中實現的接口是否是線程安全的?

本文分為以下幾個部分去介紹

  • Android 整體架構
  • Binder IPC 的架構
  • 手動實現 Binder IPC
  • 使用 AIDL 實現 Binder IPC

如果覺得文章太長,可以先只看「小結」部分,小結會把每個部分的重點總結出來,有些部分可以跳過。

Android 整體架構

不識廬山真面目,只緣身在此山中,所以我們先來大概看下 Android 這座大山的整體輪廓。我們先從 Android 的整體架構來看看 Binder 是處于什么地位,這張圖引自 Android 項目開源網站:https://source.android.com

從下往上依次為

  • 內核層:Linux 內核和各類硬件設備的驅動,這里需要注意的是,Binder IPC 驅動也是在這一層實現,比較特殊
  • 硬件抽象層:封裝「內核層」硬件驅動,提供可供「系統服務層」調用的統一硬件接口
  • 系統服務層:提供核心服務,并且提供可供「應用程序框架層」調用的接口
  • Binder IPC 層:作為「系統服務層」與「應用程序框架層」的 IPC 橋梁,互相傳遞接口調用的數據,實現跨進層的通訊
  • 應用程序框架層:這一層可以理解為 Android SDK,提供四大組件,View 繪制體系等平時開發中用到的基礎部件

在一個大的項目里面,分層是非常重要的,處于最底層的接口最具有「通用性」,接口粒度最細,越往上層通用性降低。理論上來說上面的每一層都可以「開放」給開發者調用,例如開發者可以直接調用硬件抽象層的接口去操作硬件,或者直接調用系統服務層中的接口去直接操作系統服務,甚至是像 Windows 開發一樣,開發者可以在內核層寫程序,運行在內核中。不過開放帶來的問題就是開發者權利太大,對于系統的穩定性是沒有任何好處的,一個病毒制作者寫了一個內核層的病毒,系統也許永遠也起不來了。所以谷歌的做法是將開發者的權利收攏到了「應用程序框架層」,開發者只能調用這一層提供的接口。

上面的層次中,內核層與硬件抽象層均用 C/C++ 實現,系統服務層是以 Java 實現,硬件抽象層編譯為 so 文件,以 JNI 的形式供系統服務層使用。系統服務層中的服務隨系統的啟動而啟動,只要不關機,就會一直運行。這些服務干什么事情呢?其實很簡單,就是完成一個手機該有的核心功能如短信的收發管理、電話的接聽、掛斷以及應用程序的包管理、Activity 的管理等等。每一個服務均運行在一個獨立進程中,因為是以 Java 實現,所以本質上來說就是運行在一個獨立進程的 Dalvik 虛擬機中。問題就來了,開發者的 APP 運行在一個新的進程空間,如何調用到系統服務層中的接口呢?答案是 IPC(Inter-Process Communication),進程間通訊,縮寫與 RPC(Remote Procedure Call)是不一樣的,實現原理也是不一樣的。每一個系統服務在應用層序框架層都有一個 Manager 與之對應,方便開發者調用其相關的功能,具體關系大致如下

IPC 的方式有很多種,例如 socket、共享內存、管道、消息隊列等等,我們就不去深究為何要使用 Binder 而不使用其他方式去做,到目前為止,這座大山的面目算是有個大概的輪廓了。

小結

  • Android 從下而上分了內核層、硬件抽象層、系統服務層、Binder IPC 層、應用程序框架層
  • Android 中「應用程序框架層」以 SDK 的形式開放給開發者使用,「系統服務層」中的核心服務隨系統啟動而運行,通過應用層序框架層提供的 Manager 實時為應用程序提供服務調用。系統服務層中每一個服務運行在自己獨立的進程空間中,應用程序框架層中的 Manager 通過 Binder IPC 的方式調用系統服務層中的服務。

這一小節完了,看個美女放松下,繼續下一小節

Binder IPC 的架構

下面我們就來看看 Binder IPC 的架構是怎樣的

Binder IPC 屬于 C/S 結構,Client 部分是用戶代碼,用戶代碼最終會調用 Binder Driver 的 transact 接口,Binder Driver 會調用 Server,這里的 Server 與 service 不同,可以理解為 Service 中 onBind 返回的 Binder 對象,請注意區分下。

  • Client:用戶需要實現的代碼,如 AIDL 自動生成的接口類
  • Binder Driver:在內核層實現的 Driver
  • Server:這個 Server 就是 Service 中 onBind 返回的 IBinder 對象

需要注意的是,上面綠色的色塊部分都是屬于用戶需要實現的部分,而藍色部分是系統去實現了。也就是說 Binder Driver 這塊并不需要知道,Server 中會開啟一個線程池去處理客戶端調用。為什么要用線程池而不是一個單線程隊列呢?試想一下,如果用單線程隊列,則會有任務積壓,多個客戶端同時調用一個服務的時候就會有來不及響應的情況發生,這是絕對不允許的。

對于調用 Binder Driver 中的 transact 接口,客戶端可以手動調用,也可以通過 AIDL 的方式生成的代理類來調用,服務端可以繼承 Binder 對象,也可以繼承 AIDL 生成的接口類的 Stub 對象。這些細節下面繼續接著說,這里暫時不展開。

切記,這里 Server 的實現是線程池的方式,而不是單線程隊列的方式,區別在于,單線程隊列的話,Server 的代碼是線程安全的,線程池的話,Server 的代碼則不是線程安全的,需要開發者自己做好多線程同步。

小結

  • Binder IPC 屬于 C/S 架構,包括 Client、Driver、Server 三個部分
  • Client 可以手動調用 Driver 的 transact 接口,也可以通過 AIDL 生成的 Proxy 調用
  • Server 中會啟動一個「線程池」來處理 Client 的調用請求,處理完成后將結果返回給 Driver,Driver 再返回給 Client

這里就回答了開篇提問的兩個問題:Service 中通過 AIDL 提供的接口并不是線程安全的,同理 ContentProvider 底層也是使用 Binder,同樣不是線程安全的,至于是否需要做多線程保護,看業務而定,最好是做好多線程同步,以防萬一。

手動實現 Binder IPC

通過上面的講解,大家應該對整體的流程已經有了清楚的認識,下面我們先來看看如何手動實現 Binder IPC,即不使用 AIDL 的方式。對應上面的 Client、Driver、Server,在 Activity、Service 中分別是什么呢?

上文說的 Server 其實就是 Service 中 onBind 返回的 IBinder 對象。

Server

假如我們要做一個上報數據的功能,運行在 Service 中,在后臺上報數據,接口定義如下

public interface IReporter {

    int report(String values, int type);
}

那如何拿到它的 Server 對象呢?答案是通過 Service 的 onBind 方法返回,實現如下

BindService.java

public class BinderService extends Service {

    public static final int REPORT_CODE = 0;

    public interface IReporter {
        int report(String values, int type);
    }

    public final class Reporter extends Binder implements IReporter {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            return super.onTransact(code, data, reply, flags);
        }

        @Override
        public int report(String values, int type) {
            return type;
        }
    }

    private Reporter mReporter;

    public BinderService() {
        mReporter = new Reporter();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mReporter;
    }
}

這里我暫時不寫 onTransact 的實現部分,最主要的是繼承 Binder 對象,這個是 Android SDK 提供的基類,它實現了 IBinder 接口,并且封裝了底層的 Binder Driver,看看它是如何初始化的

Binder.java

public Binder() {
    init();

    ...
}

...

private native final void init();

這里調用 native 的一個 init 方法,我們就不去深究了,知道它是對底層的 Binder Driver 的封裝即可。當客戶端發起請求的時候,Binder Driver 會調用它的 execTransact 方法,并在內部調用到 onTransact 方法,用戶端代碼可以重載該方法去實現自己的業務邏輯代碼。我們的實現方式如下

BindService.java

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code) {
        case REPORT_CODE:
            data.enforceInterface("reporter");
            String values = data.readString();
            Log.i("IReporter", "data is '" + values + "'");
            int type = data.readInt();
            int result = report(values, type);
            reply.writeInterfaceToken("reporter");
            reply.writeInt(result);
            return true;
    }
    return super.onTransact(code, data, reply, flags);
}

這里的主要過程就是:獲取到 data 中傳遞過來的參數 values 和 type,調用自己實現的 report 函數,將返回值寫到 reply 中。

注意,這里實現的兩個關鍵點就是

  • Reporter 類繼承 Binder 類,重載 onTransact 函數,實現自己的業務邏輯
  • 在 Service 的 onBind 中返回 Reporter 類的實例

這里看不到半點線程池的影子對吧,其實是在 Binder 內部的 native 方法中去實現了的,記住你寫的代碼要保持線程安全就對了。

Driver

該部分已經被 Binder 類給封裝了,暴露給開發者的已經是很簡單的使用方式了,即繼承 Binder,實現 onTransact 即可。

Client

那 Client 是什么呢?也就是我們想使用 IReport 接口來做數據上報的地方,一般都在 Activity 里面,主要實現如下

MainActivity.java

private IBinder mReporterBind;

private class BindConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mReporterBind = service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mReporterBind = null;
    }
}

...

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...
    
    intent = new Intent(this, BinderService.class);
    bindService(intent, new BindConnection(), BIND_AUTO_CREATE);
}
    

這樣就拿到了后臺 Service 中的 onBind 返回的 IBinder 對象,也就是上面 Binder IPC 架構中的 mRemote 對象。至于 bindService 中做了什么,有興趣的讀者可以再去研究,這里知道它返回的就是 mRemote 對象即可。

MainActivity.java

Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("reporter");
data.writeString("this is a test string.");
data.writeInt(type);

mReporterBind.transact(BinderService.REPORT_CODE, data, reply, 0);
reply.enforceInterface("reporter");
int result = reply.readInt();

data.recycle();
reply.recycle();

通過 Parcel.obtain() 獲取發送包對象、應答包對象,寫入數據,調用 IBinder 的 transact 接口,即 mRemote.transact() 的調用。

小結

  • 一切復雜的邏輯均已經被封裝在實現了 IBinder 接口的 Binder 類中
  • Activity 中通過 bindService 拿到 Binder Driver 中的 mRemote 對象(IBinder 的實例),然后「組包」,然后「調用 transact 接口」按序發送數據包
  • Service 中繼承 Binder 類,「重載 onTransact 函數」,實現參數的「解包」,發送返回包等,在 onBind 中返回具體的實現類 如上文中的 Reporter

總的說來就是 Client 組包,調用 transact 發送數據,Server 接到調用,解包,返回,下面使用 AIDL 的流程本質也是一樣。

使用 AIDL 實現 Binder IPC

上面的例子我們看到,定義了 IReporter 接口,但是其實 Client 中并沒有用到,因為數據的組包和解包其實是手動編碼的,并不能直接調用接口,所以其實定義接口的意義等于 0,基于此,Android 給了我們更好用的方式那就是 AIDL,定義如下

IReporter.aidl

package com.android.binder;

interface IReporter {

    int report(String values, int type);
}

Server

AidlService.java

public class AidlService extends Service {

    public static final class Reporter extends IReporter.Stub {

        @Override
        public int report(String values, int type) throws RemoteException {
            return type;
        }
    }

    private Reporter mReporter;

    public AidlService() {
        mReporter = new Reporter();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mReporter;
    }
}

這里與手動實現的方式不同的是,一個繼承了 Binder,一個繼承了 AIDL 自動生成的 Stub 對象,它是什么呢?我們可以看下它的定義

IReporter.java

public interface IReporter extends android.os.IInterface
{

    public static abstract class Stub extends android.os.Binder implements com.android.binder.IReporter {
        ...
        
        @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_report:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.report(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
    }

...

}

其實和我們上文的寫法是一樣的,自動生成的 IReporter 類自動給我們處理了一些參數的組包和解包而已,在 case 語句中調用了 this.report 即可調用到自己的業務邏輯部分了。

Driver

與上文一致,還是 Binder 的內部封裝

Client

MainActivity.java

private IReporter mReporterAidl;

private class AidlConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mReporterAidl = IReporter.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mReporterAidl = null;
    }
}

...

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...
    
    Intent intent = new Intent(this, AidlService.class);
    bindService(intent, new AidlConnection(), BIND_AUTO_CREATE);
}

這里與手動實現方式也有區別,即調用了 Stub 對象的 asInterface,具體做了什么呢?

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

先查找本地接口是否存在,判斷是否是本地調用,如果是則直接返回 IReporter 的對象,否則返回 Stub.Proxy 對象,這個 Proxy 對象是做什么的呢?

private static class Proxy implements com.android.binder.IReporter
{
    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 int report(java.lang.String values, int type) throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeString(values);
            _data.writeInt(type);
            mRemote.transact(Stub.TRANSACTION_report, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

基本上已經很明了了,就是一個代理對象,對調用接口參數做組包而已,然后調用了 mRemote.transact 接口,和上文手動實現的方式是一致的。

小結

  • AIDL 自動生成了 Stub 類
  • 在 Service 端繼承 Stub 類,Stub 類中實現了 onTransact 方法實現了「解包」的功能
  • 在 Client 端使用 Stub 類的 Proxy 對象,該對象實現了「組包」并且調用 transact 的功能

有了 AIDL 之后,IReporter 接口就變得有意義了,Client 調用接口,Server 端實現接口,一切「組包」、「解包」的邏輯封裝在了 Stub 類中,一切就是那么完美。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容