Binder的一次拷貝與通信原理

1 概述

Android系統(tǒng)中,涉及到多進程間的通信底層都是依賴于Binder IPC機制。例如當(dāng)進程A中的Activity要向進程B中的Service通信,這便需要依賴于Binder IPC。不僅于此,整個Android系統(tǒng)架構(gòu)中,大量采用了Binder機制作為IPC(進程間通信)方案。

當(dāng)然也存在部分其他的IPC方式,如管道、SystemV、Socket等。那么Android為什么不使用這些原有的技術(shù),而是要使開發(fā)一種新的叫Binder的進程間通信機制呢?

為什么要使用Binder?

  • 性能方面
    在移動設(shè)備上(性能受限制的設(shè)備,比如要省電),廣泛地使用跨進程通信對通信機制的性能有嚴(yán)格的要求,Binder相對于傳統(tǒng)的Socket方式,更加高效。Binder數(shù)據(jù)拷貝只需要一次,而管道、消息隊列、Socket都需要2次,共享內(nèi)存方式一次內(nèi)存拷貝都不需要,但實現(xiàn)方式又比較復(fù)雜。

  • 安全方面
    傳統(tǒng)的進程通信方式對于通信雙方的身份并沒有做出嚴(yán)格的驗證,比如Socket通信ip地址是客戶端手動填入,很容易進行偽造,而Binder機制從協(xié)議本身就支持對通信雙方做身份校檢,因而大大提升了安全性。

1.1 基本概念介紹

這里我們先從 Linux 中進程間通信涉及的一些基本概念開始介紹,然后逐步展開,向大家說明傳統(tǒng)的進程間通信的原理。


上圖展示了 Liunx 中跨進程通信涉及到的一些基本概念:

  • 進程隔離
    簡單的說就是操作系統(tǒng)中,進程與進程間內(nèi)存是不共享的。兩個進程就像兩個平行的世界,A 進程沒法直接訪問 B 進程的數(shù)據(jù),這就是進程隔離的通俗解釋。A 進程和 B 進程之間要進行數(shù)據(jù)交互就得采用特殊的通信機制:進程間通信(IPC)。

  • 進程空間劃分:用戶空間(User Space)/內(nèi)核空間(Kernel Space)
    現(xiàn)在操作系統(tǒng)都是采用的虛擬存儲器,對于 32 位系統(tǒng)而言,它的尋址空間(虛擬存儲空間)就是 2 的 32 次方,也就是 4GB。操作系統(tǒng)的核心是內(nèi)核,獨立于普通的應(yīng)用程序,可以訪問受保護的內(nèi)存空間,也可以訪問底層硬件設(shè)備的權(quán)限。為了保護用戶進程不能直接操作內(nèi)核,保證內(nèi)核的安全,操作系統(tǒng)從邏輯上將虛擬空間劃分為用戶空間(User Space)和內(nèi)核空間(Kernel Space)。針對 Linux 操作系統(tǒng)而言,將最高的 1GB 字節(jié)供內(nèi)核使用,稱為內(nèi)核空間;較低的 3GB 字節(jié)供各進程使用,稱為用戶空間。

簡單的說就是,內(nèi)核空間(Kernel)是系統(tǒng)內(nèi)核運行的空間,用戶空間(User Space)是用戶程序運行的空間。為了保證安全性,它們之間是隔離的。

  • 系統(tǒng)調(diào)用:用戶態(tài)/內(nèi)核態(tài)
    雖然從邏輯上進行了用戶空間和內(nèi)核空間的劃分,但不可避免的用戶空間需要訪問內(nèi)核資源,比如文件操作、訪問網(wǎng)絡(luò)等等。為了突破隔離限制,就需要借助系統(tǒng)調(diào)用來實現(xiàn)。系統(tǒng)調(diào)用是用戶空間訪問內(nèi)核空間的唯一方式,保證了所有的資源訪問都是在內(nèi)核的控制下進行的,避免了用戶程序?qū)ο到y(tǒng)資源的越權(quán)訪問,提升了系統(tǒng)安全性和穩(wěn)定性。

    Linux 使用兩級保護機制:0 級供系統(tǒng)內(nèi)核使用,3 級供用戶程序使用。

    當(dāng)一個任務(wù)(進程)執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時,稱進程處于內(nèi)核運行態(tài)(內(nèi)核態(tài))。此時處理器處于特權(quán)級最高的(0級)內(nèi)核代碼中執(zhí)行。當(dāng)進程處于內(nèi)核態(tài)時,執(zhí)行的內(nèi)核代碼會使用當(dāng)前進程的內(nèi)核棧。每個進程都有自己的內(nèi)核棧。

    當(dāng)進程在執(zhí)行用戶自己的代碼的時候,我們稱其處于用戶運行態(tài)(用戶態(tài))。此時處理器在特權(quán)級最低的(3級)用戶代碼中運行。

    系統(tǒng)調(diào)用主要通過如下兩個函數(shù)來實現(xiàn):

  copy_from_user() //將數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間
  copy_to_user() //將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間

1.2 Linux 下的傳統(tǒng) IPC 通信原理

理解了上面的幾個概念,我們再來看看傳統(tǒng)的 IPC 方式中,進程之間是如何實現(xiàn)通信的。

通常的做法是消息發(fā)送方將要發(fā)送的數(shù)據(jù)存放在內(nèi)存緩存區(qū)中,通過系統(tǒng)調(diào)用進入內(nèi)核態(tài)。然后內(nèi)核程序在內(nèi)核空間分配內(nèi)存,開辟一塊內(nèi)核緩存區(qū),調(diào)用 copyfromuser() 函數(shù)將數(shù)據(jù)從用戶空間的內(nèi)存緩存區(qū)拷貝到內(nèi)核空間的內(nèi)核緩存區(qū)中。同樣的,接收方進程在接收數(shù)據(jù)時在自己的用戶空間開辟一塊內(nèi)存緩存區(qū),然后內(nèi)核程序調(diào)用 copytouser() 函數(shù)將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收進程的內(nèi)存緩存區(qū)。這樣數(shù)據(jù)發(fā)送方進程和數(shù)據(jù)接收方進程就完成了一次數(shù)據(jù)傳輸,我們稱完成了一次進程間通信。如下圖:


這種傳統(tǒng)的 IPC 通信方式有兩個問題:

  1. 性能低下,一次數(shù)據(jù)傳遞需要經(jīng)歷:內(nèi)存緩存區(qū) --> 內(nèi)核緩存區(qū) --> 內(nèi)存緩存區(qū),需要 2 次數(shù)據(jù)拷貝;
  2. 接收數(shù)據(jù)的緩存區(qū)由數(shù)據(jù)接收進程提供,但是接收進程并不知道需要多大的空間來存放將要傳遞過來的數(shù)據(jù),因此只能開辟盡可能大的內(nèi)存空間或者先調(diào)用 API 接收消息頭來獲取消息體的大小,這兩種做法不是浪費空間就是浪費時間。

2. Binder 跨進程通信原理

理解了 Linux IPC 相關(guān)概念和通信原理,接下來我們正式介紹下 Binder IPC 的原理。

2.1 動態(tài)內(nèi)核可加載模塊 && 內(nèi)存映射

正如前面所說,跨進程通信是需要內(nèi)核空間做支持的。傳統(tǒng)的 IPC 機制如管道、Socket 都是內(nèi)核的一部分,因此通過內(nèi)核支持來實現(xiàn)進程間通信自然是沒問題的。但是 Binder 并不是 Linux 系統(tǒng)內(nèi)核的一部分,那怎么辦呢?這就得益于 Linux 的動態(tài)內(nèi)核可加載模塊(Loadable Kernel Module,LKM)的機制;模塊是具有獨立功能的程序,它可以被單獨編譯,但是不能獨立運行。它在運行時被鏈接到內(nèi)核作為內(nèi)核的一部分運行。這樣,Android 系統(tǒng)就可以通過動態(tài)添加一個內(nèi)核模塊運行在內(nèi)核空間,用戶進程之間通過這個內(nèi)核模塊作為橋梁來實現(xiàn)通信。

在 Android 系統(tǒng)中,這個運行在內(nèi)核空間,負責(zé)各個用戶進程通過 Binder 實現(xiàn)通信的內(nèi)核模塊就叫 Binder 驅(qū)動(Binder Dirver)

那么在 Android 系統(tǒng)中用戶進程之間是如何通過這個內(nèi)核模塊(Binder 驅(qū)動)來實現(xiàn)通信的呢?難道是和前面說的傳統(tǒng) IPC 機制一樣,先將數(shù)據(jù)從發(fā)送方進程拷貝到內(nèi)核緩存區(qū),然后再將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收方進程,通過兩次拷貝來實現(xiàn)嗎?顯然不是,否則也不會有開篇所說的 Binder 在性能方面的優(yōu)勢了。

這就不得不通道 Linux 下的另一個概念:內(nèi)存映射。

Binder IPC 機制中涉及到的內(nèi)存映射通過 mmap() 來實現(xiàn),mmap() 是操作系統(tǒng)中一種內(nèi)存映射的方法。內(nèi)存映射簡單的講就是將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間。映射關(guān)系建立后,用戶對這塊內(nèi)存區(qū)域的修改可以直接反應(yīng)到內(nèi)核空間;反之內(nèi)核空間對這段區(qū)域的修改也能直接反應(yīng)到用戶空間。

內(nèi)存映射能減少數(shù)據(jù)拷貝次數(shù),實現(xiàn)用戶空間和內(nèi)核空間的高效互動。兩個空間各自的修改能直接反映在映射的內(nèi)存區(qū)域,從而被對方空間及時感知。也正因為如此,內(nèi)存映射能夠提供對進程間通信的支持。

2.2 Binder IPC 實現(xiàn)原理

Binder IPC 正是基于內(nèi)存映射(mmap)來實現(xiàn)的,但是 mmap() 通常是用在有物理介質(zhì)的文件系統(tǒng)上的。

比如進程中的用戶區(qū)域是不能直接和物理設(shè)備打交道的,如果想要把磁盤上的數(shù)據(jù)讀取到進程的用戶區(qū)域,需要兩次拷貝(磁盤-->內(nèi)核空間-->用戶空間);通常在這種場景下 mmap() 就能發(fā)揮作用,通過在物理介質(zhì)和用戶空間之間建立映射,減少數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代I/O讀寫,提高文件讀取效率。

而 Binder 并不存在物理介質(zhì),因此 Binder 驅(qū)動使用 mmap() 并不是為了在物理介質(zhì)和用戶空間之間建立映射,而是用來在內(nèi)核空間創(chuàng)建數(shù)據(jù)接收的緩存空間。

一次完整的 Binder IPC 通信過程通常是這樣:

  1. 首先 Binder 驅(qū)動在內(nèi)核空間創(chuàng)建一個數(shù)據(jù)接收緩存區(qū);
  2. 接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū),建立內(nèi)核緩存區(qū)和內(nèi)核中數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系,以及內(nèi)核中數(shù)據(jù)接收緩存區(qū)和接收進程用戶空間地址的映射關(guān)系;
  3. 發(fā)送方進程通過系統(tǒng)調(diào)用 copyfromuser() 將數(shù)據(jù) copy 到內(nèi)核中的內(nèi)核緩存區(qū),由于內(nèi)核緩存區(qū)和接收進程的用戶空間存在內(nèi)存映射,因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進程的用戶空間,這樣便完成了一次進程間的通信。

3. Binder 通信模型

介紹完 Binder IPC 的底層通信原理,接下來我們看看實現(xiàn)層面是如何設(shè)計的。

一次完整的進程間通信必然至少包含兩個進程,通常我們稱通信的雙方分別為客戶端進程(Client)和服務(wù)端進程(Server),由于進程隔離機制的存在,通信雙方必然需要借助 Binder 來實現(xiàn)。

3.1 Client/Server/ServiceManager/驅(qū)動

前面我們介紹過,Binder 是基于 C/S 架構(gòu)的。由一系列的組件組成,包括 Client、Server、ServiceManager、Binder 驅(qū)動。其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅(qū)動運行在內(nèi)核空間。其中 Service Manager 和 Binder 驅(qū)動由系統(tǒng)提供,而 Client、Server 由應(yīng)用程序來實現(xiàn)。Client、Server 和 ServiceManager 均是通過系統(tǒng)調(diào)用 open、mmap 和 ioctl 來訪問設(shè)備文件 /dev/binder,從而實現(xiàn)與 Binder 驅(qū)動的交互來間接的實現(xiàn)跨進程通信。

Server創(chuàng)建了Binder實體,為其取一個字符形式,可讀易記的名字,將這個Binder連同名字以數(shù)據(jù)包的形式通過Binder驅(qū)動發(fā)送給ServiceManager,通知ServiceManager注冊一個名字為XX的Binder,它位于Server中。驅(qū)動為這個穿過進程邊界的Binder創(chuàng)建位于內(nèi)核中的實體結(jié)點以及ServiceManager對實體的引用,將名字以及新建的引用打包給ServiceManager。ServiceManager收數(shù)據(jù)包后,從中取出名字和引用填入一張查找表中。但是一個Server若向ServiceManager注冊自己Binder就必須通過0這個引用和ServiceManager的Binder通信。Server向ServiceManager注冊了Binder實體及其名字后,Client就可以通過名字獲得該Binder的引用了。Clent也利用保留的0號引用向ServiceManager請求訪問某個Binder:我申請名字叫XX的Binder的引用。ServiceManager收到這個連接請求,從請求數(shù)據(jù)包里獲得Binder的名字,在查找表里找到該名字對應(yīng)的條目,從條目中取出Binder引用,將該引用作為回復(fù)發(fā)送給發(fā)起請求的Client。

當(dāng)然,不是所有的Binder都需要注冊給ServiceManager廣而告之的。Server端可以通過已經(jīng)建立的Binder連接將創(chuàng)建的Binder實體傳給Client,當(dāng)然這條已經(jīng)建立的Binder連接必須是通過實名Binder實現(xiàn)。由于這個Binder沒有向ServiceManager注冊名字,所以是匿名Binder。Client將會收到這個匿名Binder的引用,通過這個引用向位于Server中的實體發(fā)送請求。匿名Binder為通信雙方建立一條私密通道,只要Server沒有把匿名Binder發(fā)給別的進程,別的進程就無法通過窮舉或猜測等任何方式獲得該Binder的引用,向該Binder發(fā)送請求。

Client進程:使用服務(wù)的進程。

Server進程:提供服務(wù)的進程。

ServiceManager進程:ServiceManager的作用是將字符形式的Binder名字轉(zhuǎn)化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實體的引用。

Binder驅(qū)動:驅(qū)動負責(zé)進程之間Binder通信的建立,Binder在進程之間的傳遞,Binder引用計數(shù)管理,數(shù)據(jù)包在進程之間的傳遞和交互等一系列底層支持。

Binder運行機制

圖中Client/Server/ServiceManage之間的相互通信都是基于Binder機制。既然基于Binder機制通信,那么同樣也是C/S架構(gòu),則圖中的3大步驟都有相應(yīng)的Client端與Server端。

注冊服務(wù)(addService):Server進程要先注冊Service到ServiceManager。該過程:Server是客戶端,ServiceManager是服務(wù)端。

獲取服務(wù)(getService):Client進程使用某個Service前,須先向ServiceManager中獲取相應(yīng)的Service。該過程:Client是客戶端,ServiceManager是服務(wù)端。

使用服務(wù):Client根據(jù)得到的Service信息建立與Service所在的Server進程通信的通路,然后就可以直接與Service交互。該過程:client是客戶端,server是服務(wù)端。

圖中的Client,Server,Service Manager之間交互都是虛線表示,是由于它們彼此之間不是直接交互的,而是都通過與Binder驅(qū)動進行交互的,從而實現(xiàn)IPC通信方式。其中Binder驅(qū)動位于內(nèi)核空間,Client,Server,Service Manager位于用戶空間。Binder驅(qū)動和Service Manager可以看做是Android平臺的基礎(chǔ)架構(gòu),而Client和Server是Android的應(yīng)用層,開發(fā)人員只需自定義實現(xiàn)client、Server端,借助Android的基本平臺架構(gòu)便可以直接進行IPC通信。

Binder運行的實例解釋

首先我們看看我們的程序跨進程調(diào)用系統(tǒng)服務(wù)的簡單示例,實現(xiàn)浮動窗口部分代碼:

//獲取WindowManager服務(wù)引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//布局參數(shù)layoutParams相關(guān)設(shè)置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

注冊服務(wù)(addService):在Android開機啟動過程中,Android會初始化系統(tǒng)的各種Service,并將這些Service向ServiceManager注冊(即讓ServiceManager管理)。這一步是系統(tǒng)自動完成的。

獲取服務(wù)(getService):客戶端想要得到具體的Service直接向ServiceManager要即可??蛻舳耸紫认騍erviceManager查詢得到具體的Service引用,通常是Service引用的代理對象,對數(shù)據(jù)進行一些處理操作。即第2行代碼中,得到的wm是WindowManager對象的引用。

使用服務(wù):通過這個引用向具體的服務(wù)端發(fā)送請求,服務(wù)端執(zhí)行完成后就返回。即第6行調(diào)用WindowManager的addView函數(shù),將觸發(fā)遠程調(diào)用,調(diào)用的是運行在systemServer進程中的WindowManager的addView函數(shù)。

使用服務(wù)的具體執(zhí)行過程


  • client通過獲得一個server的代理接口,對server進行調(diào)用。
  • 代理接口中定義的方法與server中定義的方法時一一對應(yīng)的。
  • client調(diào)用某個代理接口中的方法時,代理接口的方法會將client傳遞的參數(shù)打包成Parcel對象。
  • 代理接口將Parcel發(fā)送給內(nèi)核中的binder driver。
  • server會讀取binder driver中的請求數(shù)據(jù),如果是發(fā)送給自己的,解包Parcel對象,處理并將結(jié)果返回。
  • 整個的調(diào)用過程是一個同步過程,在server處理的時候,client會block住。因此client調(diào)用過程不應(yīng)在主線程

4. binder介紹

Android源碼中,binder的核心庫是在native層實現(xiàn),但在java層和native層都有接口供應(yīng)用程序使用。如果單從binder角度出發(fā),Binder架構(gòu)圖如下:

(1) binder驅(qū)動層

Android因此添加了binder驅(qū)動,其設(shè)備節(jié)點為/dev/binder,主設(shè)備號為10,binder驅(qū)動程序在內(nèi)核中的頭文件和代碼路徑如下:
kernel/drivers/staging/binder.h
kernel/drivers/staging/binder.c

binder驅(qū)動層的主要作用是完成實際的binder數(shù)據(jù)傳輸。

(2) binder adapter層

主要是IPCThreadState.cpp和ProcessState.cpp,源碼位于frameworks/native/libs/binder目錄下,這兩個類都采用了單例模式,主要負責(zé)和驅(qū)動直接交互。

a、ProcessState,進程相關(guān)的類,負責(zé)打開binder設(shè)備,進行一些初始化設(shè)置并做內(nèi)存映射;

void ProcessState::startThreadPool() 
該方法啟動的新線程,并通過joinThreadPool讀取binder設(shè)備,查看是否有請求

b、IPCThreadState,線程相關(guān)的類,負責(zé)直接和binder設(shè)備通信,使用ioctl讀寫binder驅(qū)動數(shù)據(jù)。

status_t IPCThreadState::talkWithDriver(bool doReceive) 
該方法調(diào)用的是ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)從/dev/binder讀取。

status_t IPCThreadState::executeCommand(int32_t cmd)
該方法是對talkWithDriver返回的數(shù)據(jù)進行處理。

void IPCThreadState::joinThreadPool(bool isMain)
該方法創(chuàng)建線程并進行talkWithDriver以及executeCommand。

(3) Binder核心層

Binder核心層主要是IBinder及它的兩個子類,即BBinder和BpBinder,分別代表了最基本的服務(wù)端及客戶端。源碼位于frameworks/native/libs/binder目錄下。

binder service服務(wù)端實體類會繼承BnInterface,而BnInterface會繼承自BBinder,服務(wù)端可將BBinder對象注冊到servicemananger進程。

客戶端程序和驅(qū)動交互時只能得到遠程對象的句柄handle,它可以調(diào)用調(diào)用ProcessState的getStrongProxyForHandle函數(shù),利用句柄handle建立BpBinder對象,然后將它轉(zhuǎn)為IBinder指針返回給調(diào)用者。這樣客戶端每次調(diào)用IBinder指針的transact方法,其實是執(zhí)行BpBinder的transact方法。

(4) Binder框架層

a、Native Binder框架層包含以下類(frameworks/native/libs/binder):IInterface,BnInterface,BpInterface,等。

b、Java框架層包含以下類(frameworks/base/core/java/android/os):

IBinder,Binder,IInterface,ServiceManagerNative,ServiceManager,BinderInternal,IServiceManager,ServiceManagerProxy

Java框架層的類的部分方法的實現(xiàn)在本地代碼里(frameworks/base/core/jni)。

4.1 Binder類分層

整個Binder從kernel至,native,JNI,F(xiàn)ramework層所涉及的類如下:


【初始化】
Zygote啟動時會有一個虛擬機注冊過程,該過程調(diào)用AndroidRuntime::startReg方法來完成jni方法的注冊。調(diào)用register_Android_os_Binder完成binder的native方法注冊。

int register_Android_os_Binder(JNIEnv* env)
{
    // 注冊Binder類的jni方法
    if (int_register_Android_os_Binder(env) < 0)  
        return -1;
 
    // 注冊BinderInternal類的jni方法
    if (int_register_Android_os_BinderInternal(env) < 0)
        return -1;
 
    // 注冊BinderProxy類的jni方法
    if (int_register_Android_os_BinderProxy(env) < 0)
        return -1;              
    ...
    return 0;
}

4.2 binder調(diào)用順序

使用AIDL進行跨進程的通信,實際上就是使用binder,必須要繼承binder接口,java和C++都有對應(yīng)的IBinder,proxy,stub等,通過jni進程數(shù)據(jù)的交互,binder的核心在native層。整個調(diào)用關(guān)系如下。


5. Binder的Proxy-Stub模式

5.1 Java空間

IBinder/Binder/BinderProxy是Binder機制的核心api, 而IInterface和AIDL就是為了方便開發(fā)者使用Binder進行進程間通信。先看IBinder接口:

public interface IBinder 
{
    . . . . . .
    public String getInterfaceDescriptor() throws RemoteException;
    public boolean pingBinder();
    public boolean isBinderAlive();
    public IInterface queryLocalInterface(String descriptor);  //返回IInterface類型
    public void dump(FileDescriptor fd, String[] args) throws RemoteException;
    public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException;  //通信函數(shù)
    
    public interface DeathRecipient 
    {
        public void binderDied();
    }
    public void linkToDeath(DeathRecipient recipient, int flags)throws RemoteException;
    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
}

Binder與BinderProxy的定義如下,都實現(xiàn)了IBinder接口

public class Binder implements IBinder {
 
    ...
    public Binder() {
        init();
 
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Binder> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    }
    
    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    
    public String getInterfaceDescriptor() {
        return mDescriptor;
    }
 
    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }
 
    protected boolean onTransact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (code == INTERFACE_TRANSACTION) {
            reply.writeString(getInterfaceDescriptor());
            return true;
        } else if (code == DUMP_TRANSACTION) {
            ParcelFileDescriptor fd = data.readFileDescriptor();
            String[] args = data.readStringArray();
            if (fd != null) {
                try {
                    dump(fd.getFileDescriptor(), args);
                } finally {
                    try {
                        fd.close();
                    } catch (IOException e) {
                        // swallowed, not propagated back to the caller
                    }
                }
            }
            // Write the StrictMode header.
            if (reply != null) {
                reply.writeNoException();
            } else {
                StrictMode.clearGatheredViolations();
            }
            return true;
        }
        return false;
    }
    
    public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);//調(diào)用onTransact()函數(shù)
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
    
    public void linkToDeath(DeathRecipient recipient, int flags) {
    }
 
    public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
        return true;
    }
    
    protected void finalize() throws Throwable {
        try {
            destroy();
        } finally {
            super.finalize();
        }
    }
 
    private native final void init();
    private native final void destroy();
 
    // Entry point from android_util_Binder.cpp's onTransact
    private boolean execTransact(int code, long dataObj, long replyObj, int flags) {
        ...
        try {
            res = onTransact(code, data, reply, flags);//調(diào)用onTransact()函數(shù)
        } catch (RemoteException e) {
            if ((flags & FLAG_ONEWAY) != 0) {
                Log.w(TAG, "Binder call failed.", e);
            } else {
                reply.setDataPosition(0);
                reply.writeException(e);
            }
            res = true;
        } 
        ...
        return res;
    }
}
 
final class BinderProxy implements IBinder {
    ...
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
 
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
        return transactNative(code, data, reply, flags);
    }
 
    public native String getInterfaceDescriptor() throws RemoteException;
    public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
    public native void linkToDeath(DeathRecipient recipient, int flags)
            throws RemoteException;
    public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
 
    BinderProxy() {
        mSelf = new WeakReference(this);
    }    
   ...
}

最后,在來看IInterface接口,很簡單,就一個方法,返回值是IBinder。

public interface IInterface
{
    public IBinder asBinder();
}

四者的UML類圖如下所示:


這就是BInder機制Proxy-Stub模式原始配方:
在這個類圖的最頂層,有兩個接口,IInterface和IBinder。IBinder代表跨進程傳輸?shù)哪芰?,而IInterface則用來輔助實現(xiàn)具體的傳輸業(yè)務(wù)。Binder是IBinder的實現(xiàn)類,因此它具備跨進程傳輸?shù)哪芰?,它實際上就是遠程Server端的Binder對象本身。與此對應(yīng)的BinderProxy則是遠程Binder的代理對象,給Client進程用的。在跨越進程的時候,Binder驅(qū)動會自動完成這兩個對象的轉(zhuǎn)換。

當(dāng)我們寫了一個AIDL文件之后,如下:

interface ICompute {
    int add(int a,int b);
}

此時,會生成一個相應(yīng)的java文件,如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\Code\\Github\\Android\\Demo\\src\\com\\tfygg\\demo\\service\\ICompute.aidl
 */
package com.tfygg.demo.service;
 
public interface ICompute extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.tfygg.demo.service.ICompute {
        private static final java.lang.String DESCRIPTOR = "com.tfygg.demo.service.ICompute";
 
        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
 
        /**
         * Cast an IBinder object into an com.tfygg.demo.service.ICompute
         * interface, generating a proxy if needed.
         */
        public static com.tfygg.demo.service.ICompute asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.tfygg.demo.service.ICompute))) {
                return ((com.tfygg.demo.service.ICompute) iin);
            }
            return new com.tfygg.demo.service.ICompute.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_add: {
                data.enforceInterface(DESCRIPTOR);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.add(_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            }
            return super.onTransact(code, data, reply, flags);
        }
 
        private static class Proxy implements com.tfygg.demo.service.ICompute {
            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 add(int a, int b) 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.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
 
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
 
    public int add(int a, int b) throws android.os.RemoteException;
}

對照著生成出來的ICompute.java文件,繪制如下Binder模型圖:

上圖中綠色部分就是生成的java文件的內(nèi)容,我們發(fā)現(xiàn)AIDL的Binder模型是基于原始配方的擴展。當(dāng)我們寫完ICompute.aidl之后,ICompute,Stub,Proxy已經(jīng)自動生成出來,其作用如下:
(1)ICompute接口繼承了IInterface,并寫了add(a,b)的方法,代表Server端進程具備計算兩數(shù)相加的能力。此方法將在Stub的具體實現(xiàn)類中重寫。
(2)Stub是一個抽象類,他本質(zhì)是一個Binder,他存在的目的之一是約定好asInterface,asBinder,onTransact方法,減少我們開發(fā)具體Binder對象的工作量。此外,Stub即需要跨進程傳輸,又需要約定遠端服務(wù)端具備的能力,因此他需要同時實現(xiàn)IInterface和IBinder接口,通過上文的類圖可以看得出來。
(3)Proxy是代理對象,它在Client進程中使用,它持有BinderProxy對象,BinderProxy能幫我們訪問服務(wù)端Binder對象的能力。

5.2 C空間

在C空間中,仍然是一樣的Binder配方,不同的是,C空間沒有了AIDL,而是使用模板輔助實現(xiàn)了Proxy-stub。所以,在C空間中也有IBinder.h,Binder。UML類圖如下:


先看IBinder,其定義截選如下:

class IBinder : public virtual RefBase
{
public:
    . . . . . .
    IBinder();
    virtual sp<IInterface>  queryLocalInterface(const String16& descriptor);
    virtual const String16& getInterfaceDescriptor() const = 0;
 
    virtual bool            isBinderAlive() const = 0;
    virtual status_t        pingBinder() = 0;
    virtual status_t        dump(int fd, const Vector<String16>& args) = 0;
    virtual status_t        transact(uint32_t code, const Parcel& data,
                                     Parcel* reply, uint32_t flags = 0) = 0;
 
    class DeathRecipient : public virtual RefBase
    {
    public:
        virtual void binderDied(const wp<IBinder>& who) = 0;
    };
    virtual status_t        linkToDeath(const sp<DeathRecipient>& recipient,
                                        void* cookie = NULL, uint32_t flags = 0) = 0;
    virtual status_t        unlinkToDeath(const wp<DeathRecipient>& recipient,
                                          void* cookie = NULL, uint32_t flags = 0,
                                          wp<DeathRecipient>* outRecipient = NULL) = 0;
 
    virtual bool            checkSubclass(const void* subclassID) const;
    
    typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
    virtual void            attachObject(const void* objectID, void* object,
                                         void* cleanupCookie, object_cleanup_func func) = 0;
    virtual void*           findObject(const void* objectID) const = 0;
    virtual void            detachObject(const void* objectID) = 0;
 
    virtual BBinder*        localBinder();
    virtual BpBinder*       remoteBinder();
 
protected:
    virtual          ~IBinder();
private:
};

有接口必然有實現(xiàn),BBinder和BpBinder都是IBinder的實現(xiàn)類,這里就總結(jié)一下他們的區(qū)別:

(1)pingBinder, BBinder直接返回OK,而BpBinder需要運行一個transact函數(shù),這個函數(shù)很重要。
(2)linkToDeath()是用來在服務(wù)掛的時候通知客戶端的,那服務(wù)端當(dāng)然不需要自己監(jiān)視自己咯,所以BBinder直接返回非法,而Bpbinder需要通過requestDeathNotification()要求某人完成這個事情。

奇怪的是BBinder和BpBinder都沒有實現(xiàn)queryLocalInterface() 接口啊,那肯定另有他人實現(xiàn)這個類了,這個人就是IInterface.h??蛻舫绦蛲ㄟ^queryLocalInterface() 可以知道服務(wù)端都提供哪些服務(wù)。
在IInterface.h中定義的BnInterface和BpInterface是兩個重要的模版,這是為各種程序中使用的。

template  
class BnInterface : public INTERFACE, public BBinder  
{  
public:  
    virtual sp  queryLocalInterface(const String16& _descriptor);  
    virtual String16        getInterfaceDescriptor() const;  
protected:  
    virtual IBinder*        onAsBinder();  
};  
     BnInterface模版的定義如下所示:  
template  
class BpInterface : public INTERFACE, public BpRefBase  
{  
public:  
                            BpInterface(const sp& remote);  
protected:  
    virtual IBinder*    onAsBinder();  
};  

這兩個模版在使用的時候,起到得作用實際上都是雙繼承:使用者定義一個接口INTERFACE,然后使用BnInterface和BpInterface兩個模版結(jié)合自己的接口,構(gòu)建自己的BnXXX和BpXXX兩個類。
DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE兩個宏用于幫助BpXXX類的實現(xiàn):

#define DECLARE_META_INTERFACE(INTERFACE)                               /  
    static const String16 descriptor;                                   /  
    static sp asInterface(const sp& obj);        /  
    virtual String16 getInterfaceDescriptor() const;                    /  
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       /  
    const String16 I##INTERFACE::descriptor(NAME);                      /  
    String16 I##INTERFACE::getInterfaceDescriptor() const {             /  
        return I##INTERFACE::descriptor;                                /  
    }                                                                   /  
    sp I##INTERFACE::asInterface(const sp& obj)  /  
    {                                                                   /  
        sp intr;                                          /  
        if (obj != NULL) {                                              /  
            intr = static_cast(                          /  
                obj->queryLocalInterface(                               /  
                        I##INTERFACE::descriptor).get());               /  
            if (intr == NULL) {                                         /  
                intr = new Bp##INTERFACE(obj);                          /  
            }                                                           /  
        }                                                               /  
        return intr;                                                    /  
    }  

在定義自己的類的時候,只需要使用DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE兩個接口,并結(jié)合類的名稱,就可以實現(xiàn)BpInterface中asInterface()和getInterfaceDescriptor()兩個函數(shù)。
此外,IInterface還起到了轉(zhuǎn)換的作用,IXXXService繼承自IInterface,而IInterface中的asBinder()方法,會將自身,也就是IXXXService轉(zhuǎn)換成一個IBinder對象,而asInterface接口則是將IIBinder轉(zhuǎn)換為IXXXService接口。

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}

由此可知,使用interface_cast的作用就是將IBinder實例轉(zhuǎn)化成IXXXService實例。

可以看出,C空間的IBinder接口和java空間類似,下圖可以看出C空間的binder的Proxy-stub

部分摘自
https://blog.csdn.net/augfun/article/details/82343249
https://blog.csdn.net/AndroidStudyDay/article/details/93749470
https://blog.csdn.net/tfygg/article/details/51626632

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

推薦閱讀更多精彩內(nèi)容