Android 系統(tǒng)源碼-2:Binder 通信機制

Binder 是 Android 系統(tǒng)中非常重要的組成部分居兆。Android 系統(tǒng)中的許多功能建立在 Binder 機制之上瘦黑。在這篇文章中剧防,我們會對 Android 中的 Binder 在系統(tǒng)架構中的作用進行分析植锉;然后,我們會從底層的實現(xiàn)角度簡要說明為什么 Android 要開發(fā)出一套獨立的跨進程通信機制峭拘;最后俊庇,我們會給出一個 AIDL 的使用示例來說明如何使用 Binder 來進行通信狮暑。

1心例、什么是 Binder? 為什么說它對 Android 系統(tǒng)至關重要止后?

“什么是 Binder溜腐? 為什么說它對 Android 系統(tǒng)至關重要挺益?” 在回答這個問題之前,我們先來說下其他的東西望众。

不知道你有沒有思考過這么一個問題:為什么當我們在 Android 中啟動一個頁面的時候需要調用 startActivity() 方法匪补,然后還要傳入一個 Intent? 如果我們不使用這種傳遞值的方式烂翰,直接寫成靜態(tài)的變量有沒有問題夯缺?這也是之前有人問過我的一個問題。

對上面的兩個問題甘耿,我們先回答第二個踊兜。使用靜態(tài)的變量傳遞值在大部分情況下是可以的,當然要注意在使用完了值之后要及時釋放資源佳恬,不然會占用太多內存捏境,甚至 OOM. 但是,在特殊的情況下它是無法適用的毁葱,即跨進程的情況下垫言。這是因為,靜態(tài)的變量的作用范圍只是其所在的進程倾剿,在其他進程訪問的時候屬于跨進程訪問骏掀,當然訪問不到了。對于第一個問題柱告,Android 中的一個 Activity 的啟動過程遠比我們想象的復雜截驮,其中就涉及跨進程的通信過程。當我們調用 startActivity() 方法之后际度,我們的所有的 “意圖” 會經過層層過濾坡锡,直到一個稱之為 AMS 的地方被處理帆锋。處理完之后,再跨進程調用你啟動頁面時的進程進行后續(xù)處理实辑,即回調 onCreate() 等生命周期方法。

一個 Activity 的啟動過程涉及 Android 中兩種重要的通信機制残黑,Binder 和 Handler,我們會在以后的文章中對此進行分析穷劈。

下面我們通過一個簡單的圖來說明一下 Activity 的啟動過程:

一個Activity的啟動過程

當我們調用 startActivity() 方法的時候,首先會從 ServiceManager 中獲取到 ActivityManagerService (就是 AMS)追葡,然后將 ApplicationThread 作為參數(shù)傳遞給 AMS,然后執(zhí)行 AMS 的方法來啟動 Activity. (在我們的應用進程中執(zhí)行另一個進程的方法谬返。)

AMS 是全局的佑刷,在系統(tǒng)啟動的時候被啟動瘫絮。當我們使用它的時候從 ServiceManager 中獲取這個全局的變量即可廷支。當我們調用它的方法的時候垛孔,方法具體的執(zhí)行邏輯將在系統(tǒng)的進程中執(zhí)行。我們傳入的 ApplicationThread 就像一個信使一樣概作。當 AMS 處理完畢,決定回調 Activity 的生命周期方法的時候愚屁,就直接調用 ApplicationThread 的方法(這是在另一個進程中調用我們的應用進程)。這樣就實現(xiàn)了我們的 Activity 的生命周期的回調丘跌。

看了上面的過程,也許有的同學會覺得。Binder 對 Android 系統(tǒng)至關重要捏肢,但是我們并沒有用到 Binder 啊衣屏。實際上,我們只是沒有直接使用 Binder. 以下圖為例,我們說下我們實際開發(fā)過程中是如何使用 Binder 的窘俺。

AIDL Manager

在大多數(shù)情況下,我們都在與各個 Manager 進行交互对途,而實際上這些 Manager 內部是使用 Binder 來進行跨進程通信的。如上所示膳犹,當我們調用 Manager 的時候币呵,Manager 會通過代理類來從 Binder 驅動中得到另一個進程的 Stub 對象,然后我們使用該 Stub 對象扛拨,遠程調用另一個進程的方法求泰。只是這個過程被封裝了,我們沒有感知到而已,而這個跨進程通信 (IPC) 的機制就是 Binder 機制。

至于什么是 Stub 呢逛万?Stub 是 AIDL 規(guī)范中的一部分。AIDL 為我們使用 Binder 提供了一套模板。在 Android 系統(tǒng)中大量使用了這種定義來完成跨進程通信坡氯。稍后我們介紹 AIDL 的時候,你將看到它是如何作用的。

2涮毫、為什么是 Binder 而不是其他通信機制?

Android 是基于 Linux 的,Linux 本身已經具有了許多的 IPC 機制恬叹,比如:管道(Pipe)、信號(Signal)和跟蹤(Trace)、插口(Socket)停做、消息隊列(Message)只厘、共享內存(Share Memory)和信號量(Semaphore)。那么忘蟹,為什么 Android 要特立獨行地搞出一套 IPC 機制呢?這當然是有原因的:

  1. 效率上 :Socket 作為一款通用接口搁凸,其傳輸效率低媚值,開銷大,主要用在跨網(wǎng)絡的進程間通信和本機上進程間的低速通信护糖。消息隊列和管道采用存儲-轉發(fā)方式卜壕,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內核開辟的緩存區(qū)中辜羊,然后再從內核緩存區(qū)拷貝到接收方緩存區(qū),至少有兩次拷貝過程。共享內存雖然無需拷貝施戴,但控制復雜,難以使用罚勾。Binder 只需要一次數(shù)據(jù)拷貝,性能上僅次于共享內存揽浙。

  2. 穩(wěn)定性:Binder 基于 C|S 架構敞曹,客戶端(Client)有什么需求就丟給服務端(Server)去完成,架構清晰湃窍、職責明確又相互獨立钉鸯,自然穩(wěn)定性更好。 共享內存雖然無需拷貝汪榔,但是控制負責,難以使用颅拦。從穩(wěn)定性的角度講月趟,Binder 機制是優(yōu)于內存共享的。

  3. 安全性:Binder 通過在內核層為客戶端添加身份標志 UID|PID是复,來作為身份校驗的標志青抛,保障了通信的安全性芋忿。 傳統(tǒng) IPC 訪問接入點是開放的戈钢,無法建立私有通道。比如是尔,命名管道的名稱殉了,SystemV 的鍵值,Socket 的 ip 地址或文件名都是開放的拟枚,只要知道這些接入點的程序都可以和對端建立連接薪铜,不管怎樣都無法阻止惡意程序通過猜測接收方地址獲得連接。

除了上面的原因之外恩溅,Binder 還擁有許多其他的特性隔箍,比如:1).采用引用計數(shù),當某個 Binder 不再被任何客戶端引用的時候脚乡,會通知它的持有者可以將其釋放蜒滩,這適用于 Android 這種常常因為資源不足而回收資源的應用場景。2).它內部維護了一個線程池奶稠;3).可以像觸發(fā)本地方法一樣觸發(fā)遠程的方法俯艰。4).支持同步和異步 (oneway) 的觸發(fā)模型;5).可以使用 AIDL 模板進行描述和開發(fā)锌订。

3蟆炊、Binder 模型,Binder 中的 4 個主要角色

在 Binder 模型中共有 4 個主要角色瀑志,它們分別是:Client涩搓、Server污秆、Binder 驅動和 ServiceManager. Binder 的整體結構是基于 C|S 結構的,以我們啟動 Activity 的過程為例昧甘,每個應用都會與 AMS 進行交互良拼,當它們拿到了 AMS 的 Binder 之后就像是拿到了網(wǎng)絡接口一樣可以進行訪問。如果我們將 Binder 和網(wǎng)絡的訪問過程進行類比充边,那么 Server 就是服務器,Client 是客戶終端浇冰,ServiceManager 是域名服務器(DNS)贬媒,驅動是路由器。其中 Server肘习、Client 和 ServiceManager 運行于用戶空間际乘,驅動運行于內核空間

當我們的系統(tǒng)啟動的時候漂佩,會在啟動 SystemServer 進程的時候啟動各個服務脖含,也包括上面的 AMS. 它們會被放進一個哈希表中,并且哈希表的鍵是字符串投蝉。這樣我們就可以通過服務的字符串名稱來找到對應的服務养葵。這些服務就是一個個的 Binder 實體,對于 AMS 而言瘩缆,也就是 IActivityManager.Stub 實例关拒。這些服務被啟動的之后就像網(wǎng)絡中的服務器一樣一直等待用戶的訪問。

對于這里的 ServiceManager庸娱,它也是一種服務夏醉,但是它比較特殊,它會在所有其他的服務之前被注冊涌韩,并且只被注冊一次畔柔。它的作用是用來根據(jù)字符串的名稱從哈希表中查找服務,以及在系統(tǒng)啟動的時候向哈希表中注冊服務臣樱。

Binder 模型

所以靶擦,我們可以使用上面的這張圖來描述整個 Binder 模型:首先,在系統(tǒng)會將應用程序所需的各種服務通過 Binder 驅動注冊到系統(tǒng)中(ServiceManager 先被注冊雇毫,之后其他服務再通過 ServiceManager 進行注冊)玄捕,然后當某個客戶端需要使用某個服務的時候,也需要與 Binder 驅動進行交互棚放,Binder 會通過服務的名稱到 ServiceManager 中查找指定的服務枚粘,并將其返回給客戶端程序進行使用。

4飘蚯、Binder 的原理

上面我們梳理了 Binder 的模型馍迄,以及為什么系統(tǒng)設計一套通信機制的原因福也。那么你是否也好奇神乎其神的 Binder 究竟是怎么實現(xiàn)的呢?這里我們來梳理下 Binder 內部實現(xiàn)的原理攀圈。

首先暴凑,Binder 的實現(xiàn)過程是非常復雜的,在《Android 系統(tǒng)源碼情景分析》一書中有 200 頁的篇幅都在講 Binder. 在這里我們不算詳細地講解它的具體的實現(xiàn)原理赘来,我們只對其中部分內容做簡單的分析现喳,并且不希望涉及大量的代碼。

4.1 inder 相關的系統(tǒng)源碼的結構

然后犬辰,我們需要介紹下 Binder 相關的核心類在源碼中的位置嗦篱,

-framework
    |--base
        |--core
            |--java--android--os  
                              |--IInterface.java
                              |--IBinder.java
                              |--Parcel.java
                              |-- IServiceManager.java
                              |--ServiceManager.java
                              |--ServiceManagerNative.java
                              |--Binder.java  
            |--jni
                |--android_os_Parcel.cpp
                |--AndroidRuntime.cpp
                |--android_util_Binder.cpp
    |--native
        |--libs--binder         
                  |--IServiceManager.cpp
                  |--BpBinder.cpp
                  |--Binder.cpp             // Binder 的具體實現(xiàn)
                  |--IPCThreadState.cpp
                  |--ProcessState.cpp
        |--include--binder                  // 主要是一些頭文件
                      |--IServiceManager.h
                      |--IInterface.h
        |--cmds--servicemanager
                    |--service_manager.c    // 用來注冊服務的 ServiceManager
                    |--binder.c
-kernel-drivers-staging-android
                         |--binder.c        
                         |--uapi-binder.h

4.2 Binder 實現(xiàn)過程中至關重要的幾個函數(shù)

當我們查看 binder.c 的源碼的時候,或者查看與 Binder 相關的操作的時候幌缝,經尘拇伲看到幾個操作 ioctl, mmap 和 open. 那么這幾個操作符是什么含義呢?

首先狮腿,open 函數(shù)用來打開文件的操作符腿宰,在使用的時候需要引入頭文件呕诉,#include <sys/types.h>缘厢、#include <sys/stat.h>#include <fcntl.h>,其函數(shù)定義如下甩挫,

int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);

這里的 pathname 表示文件路徑贴硫;flag 表示打開方式;mode 表示打開的模式和權限等伊者;若所有欲核查的權限都通過了檢查則返回 0, 表示成功, 只要有一個權限被禁止則返回-1.

然后是 ioctl 指令英遭,使用的時候需要引入 #include <sys/ioctl.h> 頭文件,ioctl 是設備驅動程序中對設備的 I/O 通道進行管理的函數(shù)亦渗,用于向設備發(fā)控制和配置命令挖诸。其函數(shù)定義如下:

int ioctl(int fd, ind cmd, …); 

其中 fd 是用戶程序打開設備時使用 open 函數(shù)返回的文件標示符法精,cmd 是用戶程序對設備的控制命令多律,至于后面的省略號,那是一些補充參數(shù)搂蜓,一般最多一個狼荞,這個參數(shù)的有無和 cmd 的意義相關。

最后是 mmap 函數(shù)帮碰,它用來實現(xiàn)內存映射相味。使用的時候需要引入頭文件 #include <sys/mman.h>. 與之對應的還有 munmap 函數(shù)。它們的函數(shù)定義如下殉挽,

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);

這里的參數(shù)的含義是:

  1. start:映射區(qū)的開始地址丰涉,設置為0時表示由系統(tǒng)決定映射區(qū)的起始地址拓巧;
  2. length:映射區(qū)的長度。長度單位是以字節(jié)為單位昔搂,不足一內存頁按一內存頁處理玲销;
  3. prot:期望的內存保護標志,不能與文件的打開模式沖突摘符。是以下的某個值贤斜,可以通過 o r運算合理地組合在一起;
  4. flags:指定映射對象的類型逛裤,映射選項和映射頁是否可以共享瘩绒。它的值可以是一個或者多個以下位的組合體;
  5. fd:有效的文件描述詞带族。一般是由 open() 函數(shù)返回锁荔,其值也可以設置為-1,此時需要指定 flags 參數(shù)中的 MAP_ANON,表明進行的是匿名映射蝙砌;
  6. off_toffset:被映射對象內容的起點阳堕。

成功執(zhí)行時,mmap() 返回被映射區(qū)的指針择克,munmap() 返回0恬总。失敗時,mmap() 返回 MAP_FAILED[其值為(void *)-1]肚邢,munmap() 返回 -1.

4.3 ServiceManger 啟動

Binder 中的 ServiceManager 并非 Java 層的 ServiceManager壹堰,而是 Native 層的。啟動 ServiceManager 由 init 進程通過解析 init.rc 文件而創(chuàng)建骡湖。啟動的時候會找到上述源碼目錄中的 service_manager.c 文件中贱纠,并調用它的 main() 方法,

// platform/framework/native/cmds/servicemanager.c
int main(int argc, char** argv)
{
    struct binder_state *bs;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }
    // 1. 打開 binder 驅動
    bs = binder_open(driver, 128*1024);
    // ...
    // 2. 將當前的 ServiceManger 設置成上下文
    if (binder_become_context_manager(bs)) {
        return -1;
    }
    // ...
    // 3. 啟動 binder 循環(huán)响蕴,進入不斷監(jiān)聽狀態(tài)
    binder_loop(bs, svcmgr_handler);
    return 0;
}

ServcieManager 啟動的過程就是上面三個步驟谆焊,無需過多說明。下面我們給出這三個方法具體實現(xiàn)的浦夷。在下面的代碼中你將看到我們之前介紹的三個函數(shù)的實際應用辖试。相應有了前面的鋪墊之后你理解起來不成問題 :)

// platform/framework/native/cmds/servicemanager.c
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }
    // 打開設備驅動
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        goto fail_open;
    }
    // 向驅動發(fā)送指令,獲取binder版本信息
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }
    bs->mapsize = mapsize;
    // 通過系統(tǒng)調用军拟,mmap 內存映射剃执,mmap 必須是 page 的整數(shù)倍
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        goto fail_map;
    }
    return bs;
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}

在上面的代碼中,先使用 open() 函數(shù)打開設備驅動(就是一個打開文件的操作)懈息,然后使用 ioctl() 函數(shù)向上面的設備驅動發(fā)送指令以獲取設備信息肾档。最后,通過 mmap() 函數(shù)實現(xiàn)內存映射,并將上述的文件描述符傳入怒见。這里的 binder_state 是一個結構體俗慈,定義如下。其實就是用來描述 binder 的狀態(tài)遣耍。從上面我們也能看到它的三個變量的賦值過程闺阱。

// platform/framework/native/cmds/servicemanager.c
struct binder_state
{
    int fd;
    void *mapped;
    size_t mapsize;
};

當然,在上面的代碼中舵变,我們又見到了久違的 goto 指令酣溃。它們主要用來處理發(fā)生一些異常的情況。

打開了驅動之后纪隙,注冊為上下文的方法更加簡單赊豌,

// platform/framework/native/cmds/servicemanager.c
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}

就是一個 ioctl 函數(shù),使用指令 BINDER_SET_CONTEXT_MGR 將當前的 ServiceManager 注冊為上下文绵咱。

最后就是啟動 Binder 循環(huán)了碘饼。它的邏輯也沒有想象中得復雜,就是啟動了 for 循環(huán)悲伶,

// platform/framework/native/cmds/servicemanager.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;
    // 將 BC_ENTER_LOOPER 命令發(fā)送給 binder 驅動艾恼,內部調用 ioctl 函數(shù)
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // 使用 iotcl 函數(shù)讀取
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        if (res < 0) {
            break;
        }
        // 解析
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            break;
        }
        if (res < 0) {
            break;
        }
    }
}

從上面看出,函數(shù)將會在 binder_write() 中將命令發(fā)送給 Binder 驅動麸锉,以啟動循環(huán)钠绍。其實內部也是調用 ioctl 函數(shù)實現(xiàn)的。然后程序會啟動一個循環(huán)來不斷讀取淮椰、解析五慈。這是服務器很典型的操作了纳寂。

當然主穗,我們上面分析的是 ServiceManager 中向 Binder 寫命令的過程,而驅動如何解析呢毙芜?當然是在驅動中實現(xiàn)了忽媒,詳細的過程可以查看 Binder 驅動部分的源碼。

4.4 Binder 的跨進程通信過程

下面我們以 AMS 作為例子來講解下 Binder 跨進程通信的實現(xiàn)過程腋粥。首先晦雨,當我們調用 startActivity() 方法的時候,最終將會進入 ActivityManager 以獲取 AMS隘冲,

    // platform/framework/base/core/java/android/app/ActivityManager.java
    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
    final IActivityManager am = IActivityManager.Stub.asInterface(b);
    return am;

這里會使用 ServiceManger 來按名稱查找 AMS闹瞧,查找到 Binder 對象之后將其轉換成 AMS 就可以使用了。之前展辞,我們也說過用來查找 AMS 的 SeerviceManager 本身也是一種服務奥邮。所以,它這里的方法也是通過 Binder 來實現(xiàn)的。那么洽腺,我們就從這里的 getService() 方法入手脚粟。

    // platform/framework/base/core/java/android/os/ServiceManager.java
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) { /* ... */ }
        return null;
    }

這里會先嘗試從緩存當中取 Binder,取不到的話就從遠程進行獲取蘸朋。這里使用 rawGetService() 方法來從遠程獲取 Binder核无,代碼如下,

    // platform/framework/base/core/java/android/os/ServiceManager.java
    private static IBinder rawGetService(String name) throws RemoteException {
        final IBinder binder = getIServiceManager().getService(name);
        // ...      
        return binder;
    }

    // platform/framework/base/core/java/android/os/ServiceManager.java
    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }

rawGetService() 方法中會使用 ServiceManagerNativegetService() 方法從遠程獲取 Binder. 這里的 ServiceManagerNative 本質上只是一個代理類藕坯,它實際的邏輯是由 BinderInternal.getContextObject() 返回的 Binder 實現(xiàn)的团南。

也許你已經暈了,怎么那么多 Binder……我來說明下炼彪。當要查找 AMS 的時候實際上是一個跨進程的調用過程已慢,也就是實際的查找的邏輯是在另一個進程實現(xiàn),因此需要 Binder 來通信霹购。而查找 AMS 的遠程對象實際上就是我們上面所說的 ServiceManager (Native 層的而不是 Java 層的佑惠,Java 層的 ServiceManager 是一個代理類,是用來從遠程獲取服務的)齐疙。

因此膜楷,按照上面的描述,BinderInternal.getContextObject() 返回的就應該是遠程的 Binder 對象贞奋。于是方法進入 Native 層赌厅,

// platform/framework/base/core/jni/android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}

這里的 ProcessState::self() 是否熟悉呢?你是否還記得在上一篇文章中轿塔,我們介紹 Android 系統(tǒng)啟動過程的時候介紹過它特愿。我們曾經使用它來開啟 Binder 的線程池。這里的 self() 方法其實是用來獲取一個單例對象的勾缭。我們可以直接由 getContextObject() 進入 getStrongProxyForHandle() 方法揍障。從下面的方法中我們可以看出,這里調用了 BpBindercreate() 方法創(chuàng)建了一個 BpBinder 實例并返回俩由,也就是我們的 ServiceManager.

// plaftorm/framework/native/libs/binder/ProcessState.cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    AutoMutex _l(mLock);
    handle_entry* e = lookupHandleLocked(handle);
    if (e != nullptr) {
        IBinder* b = e->binder;
        if (b == nullptr || !e->refs->attemptIncWeak(this)) {
            // ...
            // 調用 BpBinder
            b = BpBinder::create(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }

當我們拿到了 ServiceManager 的 Binder 之后就可以調用它的 getService() 方法來獲取服務了毒嫡,

    // platform/framework/base/core/java/android/os/ServiceManagerNative.java
    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

這里的 mRemote 就是之前返回的 BpBinder,這里調用它的 transact() 方法幻梯,并傳入了一個方法標記 GET_SERVICE_TRANSACTION.

// platform/framework/native/libs/binder/BpBinder.cpp
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

顯然這里會調用 IPCThreadState 的 self() 方法先獲取一個單例的對象兜畸,然后調用它的 transact() 方法繼續(xù)方法的執(zhí)行。

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle, uint32_t code, 
    const Parcel& data, Parcel* reply, uint32_t flags)
{
    status_t err;
    // ...
    err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
    // ...
    if ((flags & TF_ONE_WAY) == 0) { // OneWay 類型的調用碘梢,同步的
        // ...
        if (reply) {
            // 等待相應
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            if (reply) alog << indent << *reply << dedent << endl;
            else alog << "(none requested)" << endl;
        }
    } else { // 異步的
        err = waitForResponse(nullptr, nullptr);
    }
    return err;
}

上面會調用 writeTransactionData() 方法用來將數(shù)據(jù)寫入到 Parcel 中咬摇。然后將會進入 waitForResponse() 方法處理與 ServiceManager 交互的結果。而真實的交互發(fā)生的地方位于 talkWithDriver() 方法煞躬,

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }

    binder_write_read bwr;
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data();

    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }

    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        // 通過 ioctl 讀寫操作肛鹏,與 Binder Driver 進行交互
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
    } while (err == -EINTR);
    // ...
    return err;
}

binder_write_read 結構體用來與 Binder 設備交換數(shù)據(jù)的結構, 通過 ioctl 與 mDriverFD 通信,是真正與 Binder 驅動進行數(shù)據(jù)讀寫交互的過程。先向service manager進程發(fā)送查詢服務的請求(BR_TRANSACTION)龄坪。然后昭雌,service manager 會在之前開啟的循環(huán)中監(jiān)聽到,并使用 svcmgr_handler() 方法進行處理健田。

// platform/framework/native/cmds/servicemanager.c
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    // ...
    switch(txn->code) {
        case SVC_MGR_GET_SERVICE:
        case SVC_MGR_CHECK_SERVICE:
            s = bio_get_string16(msg, &len);
            if (s == NULL) {
                return -1;
            }
            handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
            if (!handle)
                break;
            bio_put_ref(reply, handle);
            return 0;
        case SVC_MGR_ADD_SERVICE: // ...
        case SVC_MGR_LIST_SERVICES: // ...
    }
    return 0;
}

顯然烛卧,這里會從 binder_transaction_data 中取出 code,即 SVC_MGR_GET_SERVICE妓局,然后使用 do_find_service() 方法查找服務总放。然后再 binder_send_reply() 應答發(fā)起者將結果返回即可。

4.5 Binder 高效通信的原因

上面我們梳理了 Binder 通信的過程好爬,從上面我們似乎并沒有看到能證明 Binder 高效的證據(jù)局雄。那么 Binder 究竟靠什么實現(xiàn)高效的呢?

實際上存炮,Binder 之所以高效炬搭,從我們上面的代碼還真看不出來。因為穆桂,我們上面的代碼并沒有涉及 Binder 驅動部分宫盔。正如我們之前描述的那樣,ServiceManager享完、客戶端和服務器實際是靠 Binder 驅動這個中間媒介進行交互的灼芭。而 Binder 高效的地方就發(fā)生在 Binder 驅動部分。

圖片來自-寫給 Android 應用工程師的Binder原理剖析-https://zhuanlan.zhihu.com/p/35519585

圖片來自 《寫給 Android 應用工程師的Binder原理剖析》

就像圖片描述的那樣般又,當兩個進程之間需要通信的時候彼绷,Binder 驅動會在兩個進程之間建立兩個映射關系:內核緩存區(qū)和內核中數(shù)據(jù)接收緩存區(qū)之間的映射關系,以及內核中數(shù)據(jù)接收緩存區(qū)和接收進程用戶空間地址的映射關系茴迁。這樣寄悯,當把數(shù)據(jù)從 1 個用戶空間拷貝到內核緩沖區(qū)的時候,就相當于拷貝到了另一個用戶空間中笋熬。這樣只需要做一次拷貝热某,省去了內核中暫存這個步驟腻菇,提升了一倍的性能胳螟。實現(xiàn)內存映射靠的就是上面的 mmap() 函數(shù)。

4筹吐、Binder 的使用

4.1 代理模式

Binder 本質上只是一種底層通信方式糖耸,和具體服務沒有關系。為了提供具體服務丘薛,Server 必須提供一套接口函數(shù)以便 Client 通過遠程訪問使用各種服務嘉竟。這時通常采用代理設計模式:將接口函數(shù)定義在一個抽象類中,ServerClient 都會以該抽象類為基類實現(xiàn)所有接口函數(shù)。所不同的是 Server 端是真正的功能實現(xiàn)舍扰,而 Client 端是對這些函數(shù)遠程調用請求的包裝倦蚪。為了簡化這種設計模式,Android 中提供了 AIDL 供我們使用边苹。下文中我們會介紹 AIDL 相關的內容以及它的一些基本的使用方式陵且。

4.2 AIDL

AIDL (Android Interface Definition Language,Android 接口定義語言) 是一種文件格式个束,用來簡化 Binder 的使用慕购。當使用 Binder 的時候,只需要創(chuàng)建一個后綴名為 .aidl 的文件茬底,然后像定義接口一樣定義方法沪悲。定義完畢之后,使用工具 aidl.exe 即可生成 Binder 所需要的各種文件阱表。當然殿如,我們的 AS 已經為我們集成了 aidl.exe,所以最爬,只需要在定義了 AIDL 文件之后握截,編譯即可生成使用 Binder 時所需的文件。當然烂叔,不使用 AIDL谨胞,直接編寫 Binder 所需的 java 文件也是可以的。

AIDL 是一種接口定義語言蒜鸡,它與 Java 中定義接口的方式有所區(qū)別胯努。下面我們通過一個例子來說明 AIDL 的使用方式。

這里我們模擬一個筆記管理的類逢防,通過在 Activity 中與一個遠程的 Service 進行交互來實現(xiàn) IPC 的效果叶沛。這里,我們先要定義數(shù)據(jù)實體 Note忘朝,它只包含兩個字段灰署,并且實現(xiàn)了 Parcelable。這里 Note 所在的目錄是 me.shouheng.advanced.aidl局嘁,然后溉箕,我們需要在 src/main 建立一個同樣的包路徑,然后定義所需的 AIDL 文件:

    // INoteManager.aidl
    package me.shouheng.advanced.aidl;
    import me.shouheng.advanced.aidl.Note;
    interface INoteManager {
        Note getNote(long id);
        void addNote(long id, String name);
    }

    // Note.aidl
    package me.shouheng.advanced.aidl;
    parcelable Note;

注意悦昵,在 INoteManager 文件中肴茄,我們定義了遠程服務所需的各種方法。這里只定義了兩個方法但指,一個用來獲取指定 id 的筆記寡痰,一個用來向遠程服務中添加一條筆記記錄抗楔。

這樣定義完了之后,我們可以對項目進行編譯,這樣就可以 build 目錄下面得到為我們生成好的 INoteManager 類文件。以后更舞,我們就可以使用這個文件中生成類和方法來進行遠程通信。但在使用該接口之前反粥,我們還是先來看一下其中都生成了些什么東西:

package me.shouheng.advanced.aidl;

public interface INoteManager extends android.os.IInterface {

    // 交給遠程來實現(xiàn)具體的業(yè)務邏輯
    public static abstract class Stub extends android.os.Binder implements me.shouheng.advanced.aidl.INoteManager {

        private static final java.lang.String DESCRIPTOR = "me.shouheng.advanced.aidl.INoteManager";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        // 使用代理包裝遠程對象
        public static me.shouheng.advanced.aidl.INoteManager asInterface(android.os.IBinder obj) {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof me.shouheng.advanced.aidl.INoteManager))) {
                return ((me.shouheng.advanced.aidl.INoteManager)iin);
            }
            // 返回代理對象
            return new me.shouheng.advanced.aidl.INoteManager.Stub.Proxy(obj);
        }

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

        // 真實地發(fā)送數(shù)據(jù)交換的地方
        @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_getNote: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    // 使用模板方法來實現(xiàn)業(yè)務
                    me.shouheng.advanced.aidl.Note _result = this.getNote(_arg0);
                    reply.writeNoException();
                    if ((_result!=null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addNote: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    // 使用模板方法來實現(xiàn)業(yè)務
                    this.addNote(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        // 代理對象,包裝了遠程對象疲迂,內部調用遠程對象獲取遠程的服務信息
        private static class Proxy implements me.shouheng.advanced.aidl.INoteManager {

            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 me.shouheng.advanced.aidl.Note getNote(long id) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                me.shouheng.advanced.aidl.Note _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    // 實際內部調用遠程對象才顿,在另一個進程實現(xiàn)業(yè)務邏輯
                    mRemote.transact(Stub.TRANSACTION_getNote, _data, _reply, 0);
                    _reply.readException();
                    if ((0!=_reply.readInt())) {
                        _result = me.shouheng.advanced.aidl.Note.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addNote(long id, java.lang.String name) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    _data.writeString(name);
                    // 實際內部調用遠程對象,在另一個進程實現(xiàn)業(yè)務邏輯
                    mRemote.transact(Stub.TRANSACTION_addNote, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        // 方法 id尤蒿,用來標記當前調用的是哪個方法
        static final int TRANSACTION_getNote = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addNote = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public me.shouheng.advanced.aidl.Note getNote(long id) throws android.os.RemoteException;

    public void addNote(long id, java.lang.String name) throws android.os.RemoteException;
}

如果只是看這上面的生成的代碼郑气,也許你仍然無法了解這些生成的類究竟有什么作用。下面就讓我們通過使用上面生成的類來說明 AIDL 的具體工作流程腰池。

首先尾组,我們要定義遠程的服務,并在該服務中實現(xiàn)業(yè)務邏輯:

public class NoteService extends Service {

    private CopyOnWriteArrayList<Note> notes = new CopyOnWriteArrayList<>();

    // 當前服務運行于另一個進程示弓,這里實現(xiàn)業(yè)務邏輯
    private Binder binder = new INoteManager.Stub() {
        @Override
        public Note getNote(long id) {
            return Observable.fromIterable(notes).filter(note -> note.id == id).singleOrError().blockingGet();
        }

        @Override
        public void addNote(long id, String name) {
            notes.add(new Note(id, name));
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        notes.add(new Note(100, "Note 100"));
        notes.add(new Note(101, "Note 101"));
    }

    // 將 binder 返回讳侨,客戶端可以使用連接來獲取并調用
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

這里在 onCreate() 方法中創(chuàng)建了兩條記錄,并且創(chuàng)建了 INoteManager.Stub 的實例奏属,并在 onBind() 方法中將其返回跨跨。然后,我們在一個 Activity 中啟動該遠程服務囱皿,并嘗試從該服務中獲取指定 id 的筆記記錄勇婴。從期望的結果來看齿兔,它的功能有些類似于 ContentProvider,即用來向調用者提供數(shù)據(jù)。

下面是該 Activity 的實現(xiàn)盆偿。這里我們在 onCreate() 方法中啟動上述服務捎稚。并將實例化的 ServiceConnection 作為參數(shù)啟動該服務条霜。在 ServiceConnection 的方法中拆内,我們調用 INoteManager.StubasInterface(IBinder) 方法來講 service 轉換成 INoteManager或南,然后從其中獲取指定 id 的筆記記錄即可蹬癌。

    // 創(chuàng)建服務連接
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 返回代理對象
            INoteManager noteManager = INoteManager.Stub.asInterface(service);
            try {
                // 使用代理對象
                Note note = noteManager.getNote(100);
                LogUtils.d(note);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        Intent intent = new Intent(this, NoteService.class);
        // 綁定服務
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解綁服務
        unbindService(connection);
    }
}

根據(jù) INoteManager.StubasInterface() 方法的定義要门,該方法中會將傳入的 service 包裝成一個 INoteManager.Stub.Proxy 返回,所以缘琅,我們在 onServiceConnected() 方法中實際調用的是該代理類的 getNote() 方法鸽心。而該代理類的 getNote() 方法中又調用了傳入的 mRemote.transact() 方法省骂。而這里的 service 正是我們在 NoteService 中創(chuàng)建的 binder怠惶。也就是說,當我們在 onServiceConnected() 中調用 getNote() 方法的時候轧粟,實際上調用了 INoteManager.Stubtransact() 方法策治。

所以,從上面我們看出:

  1. 這里就像是在當前進程中調用了另一個進程的方法一樣兰吟。這個調用的過程是通過 Binder 來實現(xiàn)的通惫。
  2. 當調用 INoteManager.Stubtransact() 方法的時候,通過傳入了一個整型的 code 來作為要觸發(fā)的方法的標識混蔼,這就是我們上面提到的方法的編號履腋。

于是,我們可以通過下面的這張圖來總結在上面使用 AIDL 的過程中各部分扮演的角色:

AIDL

也就是客戶端通過 Proxy 訪問 Binder 驅動,然后 Binder 驅動調用 Stub遵湖,而 Stub 中調用我們的業(yè)務邏輯悔政。這里的 ProxyStub 用來統(tǒng)一接口函數(shù),Proxy 用來告訴我們遠程服務中有哪些可用的方法奄侠,而具體的業(yè)務邏輯則由 Stub 來實現(xiàn)卓箫。Binder 的進程通信就發(fā)生在 ProxyStub 之間载矿。

總結

以上就是 Binder 的工作原理垄潮,如有疑問,歡迎評論區(qū)交流闷盔。

參考資料

源代碼Android-references

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末弯洗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子逢勾,更是在濱河造成了極大的恐慌牡整,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溺拱,死亡現(xiàn)場離奇詭異逃贝,居然都是意外死亡,警方通過查閱死者的電腦和手機迫摔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門沐扳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人句占,你說我怎么就攤上這事沪摄。” “怎么了纱烘?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵杨拐,是天一觀的道長。 經常有香客問我擂啥,道長哄陶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任哺壶,我火速辦了婚禮屋吨,結果婚禮上,老公的妹妹穿的比我還像新娘变骡。我一直安慰自己离赫,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布塌碌。 她就那樣靜靜地躺著渊胸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪台妆。 梳的紋絲不亂的頭發(fā)上翎猛,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天胖翰,我揣著相機與錄音,去河邊找鬼切厘。 笑死萨咳,一個胖子當著我的面吹牛,可吹牛的內容都是我干的疫稿。 我是一名探鬼主播培他,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遗座!你這毒婦竟也來了舀凛?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤途蒋,失蹤者是張志新(化名)和其女友劉穎猛遍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體号坡,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡懊烤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宽堆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腌紧。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖日麸,靈堂內的尸體忽然破棺而出寄啼,到底是詐尸還是另有隱情,我是刑警寧澤代箭,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布墩划,位于F島的核電站,受9級特大地震影響嗡综,放射性物質發(fā)生泄漏乙帮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一极景、第九天 我趴在偏房一處隱蔽的房頂上張望察净。 院中可真熱鬧,春花似錦盼樟、人聲如沸氢卡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽译秦。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筑悴,已是汗流浹背们拙。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阁吝,地道東北人砚婆。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像突勇,于是被迫代替她去往敵國和親装盯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容