關(guān)于Binder幻妓,作為應(yīng)用開發(fā)者你需要知道的全部

首發(fā):關(guān)于Binder,作為應(yīng)用開發(fā)者你需要知道的全部

為什么要理解Binder劫拢?

一般Android應(yīng)用開發(fā)很少直接用到跨進(jìn)程信通信(IPC)肉津,但如果你想知道:

  • App是如何啟動并初始化的?
  • Activity的啟動過程是怎樣的舱沧?
  • 進(jìn)程間是如何通信的妹沙?
  • AIDL的具體原理是什么?
  • 眾多插件化框架的設(shè)計(jì)原理 等等

就必須對Binder有所了解狗唉,無論是四大組件初烘,還是各種系統(tǒng)Service,比如ActivityManagerService分俯、PackageManagerService肾筐,它們的實(shí)現(xiàn)都依賴Binder的通信機(jī)制,可見Binder在Android系統(tǒng)中的重要性缸剪,可以說Binder是邁入高級工程師的第一步吗铐。

Binder機(jī)制很復(fù)雜,想要徹底弄懂比較難杏节,除了需要了解操作系統(tǒng)中的各種知識外唬渗,還需要看懂Binder驅(qū)動層的代碼實(shí)現(xiàn)。最近看了很多關(guān)于Binder的文章奋渔,大部分過于抽象或者過于深入源碼細(xì)節(jié)镊逝,真正淺顯易懂的文章很少。個人認(rèn)為最有參考價值的是以下三篇:(其他參考文章附在文末)

這篇文章主要從宏觀的層面去理解Binder中的各種概念和基本通信過程嫉鲸,只關(guān)注Java層的實(shí)現(xiàn)撑蒜,底層實(shí)現(xiàn)不做介紹。對于應(yīng)用開發(fā)者而言玄渗,理解Binder的基本設(shè)計(jì)原理和通信過程已經(jīng)夠了座菠,想要深入理解Binder需要自行閱讀源碼。

本文主要從三個方面來做分析:

  1. 為什么是Binder藤树?
    • 傳統(tǒng)Linux IPC機(jī)制的缺點(diǎn)
    • Linux的一些基本知識
    • 傳統(tǒng)Linux IPC機(jī)制的通信原理
  2. Binder的基本原理
    • Binder的底層原理
    • Binder的通信模型
    • Binder的代理機(jī)制
    • 對Binder概念的重新理解
  3. 通過代碼來理解Binder
    • 通過AIDL實(shí)例來了解Binder的用法
    • 通過手動編碼實(shí)現(xiàn)ActivityManagerService

1. 為什么是Binder浴滴?

1.1 傳統(tǒng)IPC機(jī)制的缺點(diǎn)

大家都知道Android系統(tǒng)是基于Linux內(nèi)核實(shí)現(xiàn)的,Linux已經(jīng)提供了多種進(jìn)程間通信機(jī)制岁钓,比如:管道升略、消息隊(duì)列微王、共享內(nèi)存和套接字(Socket)等等,為什么還要再實(shí)現(xiàn)一套IPC機(jī)制呢品嚣?主要是基于兩方面的原因:

1.1.1 性能角度

管道骂远、消息隊(duì)列、Socket實(shí)現(xiàn)一次進(jìn)程通信都需要2次內(nèi)存拷貝腰根,效率太低;共享內(nèi)存雖然不需要拷貝內(nèi)存拓型,但管理復(fù)雜额嘿;Binder只需要一次內(nèi)存拷貝,從性能角度來看劣挫,低于共享內(nèi)存方式册养,優(yōu)于其它方式。

IPC機(jī)制 數(shù)據(jù)拷貝次數(shù)
共享內(nèi)存 0
Binder 1
管道压固、消息隊(duì)列球拦、Socket 2

1.1.2 安全性考慮

傳統(tǒng)的IPC機(jī)制沒有安全措施,接收方無法獲得對方可靠的進(jìn)程ID或用戶ID帐我,完全靠上層的協(xié)議來保護(hù)枪汪,比如Socket通信的IP地址是客戶端填入的扶欣,很可能被惡意程序篡改。Android作為面向終端用戶的開源平臺,應(yīng)用市場中有海量的應(yīng)用供用戶選擇罩锐,因此安全性極為重要。Android系統(tǒng)為每個已安裝的App都分配了用戶ID(UID)模闲,UID是鑒別進(jìn)程身份的重要標(biāo)識攀唯,通過UID可以進(jìn)行一系列的權(quán)限校驗(yàn)。另一方面 媚朦,傳統(tǒng)IPC的接入點(diǎn)是開放的氧敢,任何程序都可以根據(jù)協(xié)議進(jìn)行訪問,無法阻止惡意程序的訪問询张,Android需要一種基于C/S架構(gòu)的IPC機(jī)制孙乖,Server端需要能夠?qū)lient的請求進(jìn)行身份校驗(yàn),來保證數(shù)據(jù)的安全性瑞侮。

1.2 Linux的一些基本知識

要知道Binder是如何只用一次內(nèi)存拷貝即實(shí)現(xiàn)跨進(jìn)程通信的的圆,首先需要弄清楚為什么傳統(tǒng)IPC機(jī)制為什么需要兩次內(nèi)存拷貝,這就需要先了解一些操作系統(tǒng)的基礎(chǔ)知識半火。

1.2.1 進(jìn)程隔離

先來看一下維基百科對“進(jìn)程隔離”的定義:

進(jìn)程隔離是為保護(hù)操作系統(tǒng)中進(jìn)程互不干擾而設(shè)計(jì)的一組不同硬件和軟件的技術(shù)越妈。這個技術(shù)是為了避免進(jìn)程A寫入進(jìn)程B的情況發(fā)生。 進(jìn)程的隔離實(shí)現(xiàn)钮糖,使用了虛擬地址空間梅掠。進(jìn)程A的虛擬地址和進(jìn)程B的虛擬地址不同酌住,這樣就防止進(jìn)程A將數(shù)據(jù)信息寫入進(jìn)程B。

也就是說阎抒,進(jìn)程之間的數(shù)據(jù)是不共享的酪我,A進(jìn)程無法直接訪問B進(jìn)程的數(shù)據(jù),以此來保證數(shù)據(jù)的安全性且叁。在進(jìn)程隔離的操作系統(tǒng)中都哭,進(jìn)程之間的交互必須通過IPC機(jī)制。

進(jìn)程隔離的實(shí)現(xiàn)使用了虛擬地址空間逞带,什么是虛擬地址空間呢欺矫?首先需要了解操作系統(tǒng)中的虛擬內(nèi)存概念,它是一種提高編程效率和提高物理內(nèi)存利用效率的一種技術(shù)展氓。簡單來說穆趴,就是應(yīng)用程序看到了都一片連續(xù)完整的內(nèi)存地址空間,而實(shí)際上這些地壇空間是映射到碎片化的物理內(nèi)存中的遇汞,這個映射的過程對應(yīng)用程序來說是透明的未妹。這個概念很重要,對于虛擬內(nèi)存更深入的理解可以參考這篇文章:Linux 虛擬內(nèi)存和物理內(nèi)存的理解

1.2.2 進(jìn)程空間:用戶空間/內(nèi)核空間

現(xiàn)在的操作系統(tǒng)都采用虛擬內(nèi)存空入,對32位的操作系統(tǒng)而言络它,尋址空間是2的32次方,即4G执庐。操作系統(tǒng)的核心是內(nèi)核酪耕,內(nèi)核擁有對底層設(shè)備的所有訪問權(quán)限,因此需要和普通的應(yīng)用程序獨(dú)立開來轨淌,用戶進(jìn)程不能直接訪問內(nèi)核進(jìn)程迂烁。操作系統(tǒng)從邏輯上把虛擬地址空間劃分為用戶空間(User Space)和內(nèi)核空間(Kernel Space)。在32位的Linux操作系統(tǒng)中递鹉,將高位的1GB字節(jié)供內(nèi)核使用盟步,稱之為內(nèi)核空間;剩下的3GB字節(jié)供用戶進(jìn)程使用躏结,稱之為用戶空間却盘。

1.2.3 系統(tǒng)調(diào)用:用戶態(tài)/內(nèi)核態(tài)

因?yàn)橛脩艨臻g的權(quán)限低于內(nèi)核空間,不可避免用戶空間需要訪問內(nèi)核空間的資源媳拴,比如讀寫文件和網(wǎng)絡(luò)訪問黄橘,如何實(shí)現(xiàn)呢?唯一的方式就是通過操作系統(tǒng)提供的系統(tǒng)調(diào)用接口屈溉,通過系統(tǒng)調(diào)用接口塞关,用戶程序可以在內(nèi)核的控制下實(shí)現(xiàn)對內(nèi)核資源的有限訪問,這樣既能滿足應(yīng)用程序的資源請求子巾,也能保障系統(tǒng)安全和穩(wěn)定帆赢。

當(dāng)用戶進(jìn)程執(zhí)行自己的代碼時小压,進(jìn)程當(dāng)前就處于用戶運(yùn)行態(tài)(用戶態(tài)),此時處理器執(zhí)行用戶代碼椰于,權(quán)限較低怠益;當(dāng)用戶進(jìn)程通過系統(tǒng)調(diào)用執(zhí)行內(nèi)核代碼時,進(jìn)程就暫時進(jìn)入了內(nèi)核運(yùn)行態(tài)(內(nèi)核態(tài))瘾婿,此時處理器權(quán)限最高蜻牢,可以執(zhí)行特權(quán)指令。

1.2.4 內(nèi)核模塊/驅(qū)動

前面說了用戶空間可以通過系統(tǒng)調(diào)用訪問內(nèi)核空間偏陪,那用戶空間之間(進(jìn)程間)怎么通信呢孩饼?傳統(tǒng)的IPC機(jī)制都是通過內(nèi)核來支持的,Binder也一樣竹挡,有一個運(yùn)行在內(nèi)核中的Binder驅(qū)動程序負(fù)責(zé)進(jìn)程之間Binder的通信。

驅(qū)動程序一般指的是設(shè)備驅(qū)動程序(Device Driver)立膛,是一種可以使計(jì)算機(jī)和設(shè)備通信的特殊程序揪罕。相當(dāng)于硬件的接口,操作系統(tǒng)只有通過這個接口宝泵。

Binder驅(qū)動是一種虛擬的字符設(shè)備好啰,注冊在/dev/binder中,其定義了一套Binder通信協(xié)議儿奶,負(fù)責(zé)建立進(jìn)程間的Binder通信框往,提供了數(shù)據(jù)包在進(jìn)程之間傳遞的一系列底層支持。應(yīng)用進(jìn)程訪問Binder驅(qū)動也是通過系統(tǒng)調(diào)用實(shí)現(xiàn)的闯捎。

1.3 傳統(tǒng)IPC機(jī)制的通信原理

了解了上面的基礎(chǔ)知識后椰弊,我們來看看傳統(tǒng)IPC機(jī)制是如何實(shí)現(xiàn),通常是下面兩個步驟(共享內(nèi)存機(jī)制除外):

  1. 發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用(copy_from_user)將要發(fā)送的數(shù)據(jù)存拷貝到內(nèi)核緩存區(qū)中瓤鼻。
  2. 接收方開辟一段內(nèi)存空間秉版,內(nèi)核通過系統(tǒng)調(diào)用(copy_to_user)將內(nèi)核緩存區(qū)中的數(shù)據(jù)拷貝到接收方的內(nèi)存緩存區(qū)。
IPC.png

這種傳統(tǒng)IPC機(jī)制存在2個問題:

  1. 需要進(jìn)行2次數(shù)據(jù)拷貝茬祷,第1次是從發(fā)送方用戶空間拷貝到內(nèi)核緩存區(qū)清焕,第2次是從內(nèi)核緩存區(qū)拷貝到接收方用戶空間。
  2. 接收方進(jìn)程不知道事先要分配多大的空間來接收數(shù)據(jù)祭犯,可能存在空間上的浪費(fèi)秸妥。

2. Binder的基本原理

2.1 Binder底層原理

傳統(tǒng)IPC機(jī)制需要拷貝2次內(nèi)存,Binder是如何只用1次內(nèi)存拷貝就實(shí)現(xiàn)進(jìn)程間通信的呢沃粗?前面我們已經(jīng)了解到粥惧,Linux是使用的是虛擬內(nèi)存尋址方式,用戶空間的虛擬內(nèi)存地址是映射到物理內(nèi)存中的陪每,對虛擬內(nèi)存的讀寫實(shí)際上是對物理內(nèi)存的讀寫影晓,這個過程就是內(nèi)存映射镰吵,這個內(nèi)存映射過程是通過系統(tǒng)調(diào)用mmap()來實(shí)現(xiàn)的。

Binder借助了內(nèi)存映射的方法挂签,在內(nèi)核空間和接收方用戶空間的數(shù)據(jù)緩存區(qū)之間做了一層內(nèi)存映射疤祭。這樣一來,從發(fā)送方用戶空間拷貝到內(nèi)核空間緩存區(qū)的數(shù)據(jù)饵婆,就相當(dāng)于直接拷貝到了接收方用戶空間的數(shù)據(jù)緩存區(qū)勺馆,從而減少了一次數(shù)據(jù)拷貝。

binder.png

2.2 Binder通信模型

Binder是基于C/S架構(gòu)的侨核,對于通信雙方來說草穆,發(fā)起請求的進(jìn)程屬于Client,接收請求的進(jìn)程屬于Server搓译,由于存在進(jìn)程隔離悲柱,雙方不能直接通信,Binder是如何實(shí)現(xiàn)的呢些己?

寫給 Android 應(yīng)用工程師的 Binder 原理剖析中舉的網(wǎng)絡(luò)通信例子很貼切豌鸡,Binder的通信過程與網(wǎng)絡(luò)請求類似,網(wǎng)絡(luò)通信過程可以簡化為4個角色:Client段标、Server涯冠、DNS服務(wù)器和路由器。一次完整的網(wǎng)絡(luò)通信大體過程如下:

  1. Client輸入Server的域名

  2. DNS解析域名

    通過域名是無法直接找到相應(yīng)Server的逼庞,必須先通過DNS服務(wù)器將Server的域名轉(zhuǎn)化為具體的IP地址蛇更。

  3. 通過路由器將請求發(fā)送至Server

    Client通過DNS服務(wù)器解析到Server的IP地址后,也還不能直接向Server發(fā)起請求赛糟,需要經(jīng)過路由器的層層中轉(zhuǎn)才還到達(dá)Server派任。

  4. Server返回?cái)?shù)據(jù)

    Server接收到請求并處理后,再通過路由器將數(shù)據(jù)返回給Client璧南。

在Binder機(jī)制中吨瞎,也定義了4個角色:Client、Server穆咐、Binder驅(qū)動和ServiceManager颤诀。

  • Binder驅(qū)動:類似網(wǎng)絡(luò)通信中的路由器,負(fù)責(zé)將Client的請求轉(zhuǎn)發(fā)到具體的Server中執(zhí)行对湃,并將Server返回的數(shù)據(jù)傳回給Client崖叫。
  • ServiceManager:類似網(wǎng)絡(luò)通信中的DNS服務(wù)器,負(fù)責(zé)將Client請求的Binder描述符轉(zhuǎn)化為具體的Server地址拍柒,以便Binder驅(qū)動能夠轉(zhuǎn)發(fā)給具體的Server心傀。Server如需提供Binder服務(wù),需要向ServiceManager注冊拆讯。

具體的通信過程是這樣的:

  1. Server向ServiceManager注冊

    Server通過Binder驅(qū)動向ServiceManager注冊脂男,聲明可以對外提供服務(wù)养叛。ServiceManager中會保留一份映射表:名字為zhangsan的Server對應(yīng)的Binder引用是0x12345。

  2. Client向ServiceManager請求Server的Binder引用

    Client想要請求Server的數(shù)據(jù)時宰翅,需要先通過Binder驅(qū)動向ServiceManager請求Server的Binder引用:我要向名字為zhangsan的Server通信弃甥,請告訴我Server的Binder引用。

  3. 向具體的Server發(fā)送請求

    Client拿到這個Binder引用后汁讼,就可以通過Binder驅(qū)動和Server進(jìn)行通信了淆攻。

  4. Server返回結(jié)果

    Server響應(yīng)請求后,需要再次通過Binder驅(qū)動將結(jié)果返回給Client嘿架。

可以看到瓶珊,Client、Server耸彪、ServiceManager之間的通信都是通過Binder驅(qū)動作為橋梁的伞芹,可見Binder驅(qū)動的重要性。也許你還有一點(diǎn)疑問蝉娜,ServiceManager和Binder驅(qū)動屬于兩個不同的進(jìn)程丑瞧,它們是為Client和Server之間的進(jìn)程間通信服務(wù)的,也就是說Client和Server之間的進(jìn)程間通信依賴ServiceManager和Binder驅(qū)動之間的進(jìn)程間通信蜀肘,這就像是:“蛋生雞,雞生蛋稽屏,但第一個蛋得通過一只雞孵出來”扮宠。Binder機(jī)制是如何創(chuàng)造第一只下蛋的雞呢?

  1. 當(dāng)Android系統(tǒng)啟動后狐榔,會創(chuàng)建一個名稱為servicemanager的進(jìn)程坛增,這個進(jìn)程通過一個約定的命令BINDERSETCONTEXT_MGR向Binder驅(qū)動注冊,申請成為為ServiceManager薄腻,Binder驅(qū)動會自動為ServiceManager創(chuàng)建一個Binder實(shí)體(第一只下蛋的雞)收捣;
  2. 并且這個Binder實(shí)體的引用在所有的Client中都為0,也就說各個Client通過這個0號引用就可以和ServiceManager進(jìn)行通信庵楷。Server通過0號引用向ServiceManager進(jìn)行注冊罢艾,Client通過0號引用就可以獲取到要通信的Server的Binder引用。

Android Binder設(shè)計(jì)與實(shí)現(xiàn) - 設(shè)計(jì)篇中對Client尽纽、Server咐蚯、Binder驅(qū)動和ServiceManager有更詳細(xì)的介紹。

2.3 Binder的代理機(jī)制

通過上面的分析弄贿,我們已經(jīng)知道了Binder的基本通信過程:Client向SerivceManger獲取到Server的Binder引用春锋,Client通過Binder引用向Server發(fā)起具體請求。Client通過這個Binder引用具體是如何調(diào)用Server方法的呢差凹?

binder_proxy.png

比如一個Server提供add方法期奔,Client實(shí)際請求add的流程是這樣的:Client先通過Binder驅(qū)動向ServiceManager獲取Server的Binder引用侧馅,這個引用就是一個Java Object,這個Object有一個add方法呐萌;Cient拿到這個Object后就可以直接請求add方法了馁痴。

實(shí)際上Client拿到的Object并不是Server真正的Binder實(shí)體,Binder驅(qū)動做了一層對象轉(zhuǎn)換搁胆,將這個Object包裝成了一個代理對象ProxyObject弥搞,這個ProxyObject和真正的Binder實(shí)體有相同的方法簽名,Client通過這個ProxyObject請求add方法時渠旁,Binder驅(qū)動會自動將請求轉(zhuǎn)發(fā)到具體的Binder實(shí)體中執(zhí)行攀例,這就是Binder的代理機(jī)制。由于ProxyObject和真正的Binder實(shí)體有相同的方法簽名顾腊,其實(shí)Client并不需要關(guān)心是ProxyObject還是真實(shí)的Object粤铭。

為了方便描述,下面將Server真正的Binder實(shí)體稱為Binder本地對象杂靶;將Client中的Binder引用梆惯,即ProxyObject,稱之為Binder代理對象吗垮。

2.4 對Binder概念的重新理解

經(jīng)過上面的分析垛吗,我們已經(jīng)大體清楚了Binder機(jī)制的基本通信原理,現(xiàn)在回過頭來重新梳理下對Binder機(jī)制的認(rèn)識:

總體來說烁登,Binder是基于C/S結(jié)構(gòu)的一種面向?qū)ο蟮腎PC機(jī)制怯屉。包含:Client、Server饵沧、Binder驅(qū)動和ServiceManager四大組成部分锨络。各組成部分中的Binder含義都有所有不同:

  • 對于Client

    Binder是Server本地對象的一個引用,這個引用實(shí)際上是一個代理對象狼牺,Client通過這個代理對象來間接訪問Server的本地對象羡儿;

  • 對于Server

    Binder是提供具體實(shí)現(xiàn)的本地對象,需向ServiceManager注冊是钥;

  • 對于Binder驅(qū)動

    它是連接Client來Server的橋梁掠归,負(fù)責(zé)將代理對象轉(zhuǎn)化為本地對象,并將Server的執(zhí)行結(jié)果返回給Client悄泥。

  • 對于ServiceManager

    它保存了Server Binder字符名稱和Binder引用的映射拂到,Client通過它來找到Server的Binder引用。

Binder驅(qū)動中保留了Binder代理對象和Binder本地對象的具體結(jié)構(gòu)码泞,由于我們只關(guān)心Binder的基本通信機(jī)制兄旬,底層實(shí)現(xiàn)不做過多介紹,想具體了解的同學(xué)可以參考Android Binder設(shè)計(jì)與實(shí)現(xiàn) - 設(shè)計(jì)篇

3. 通過代碼來理解Binder

上面的介紹比較抽象领铐,現(xiàn)在我們通過具體實(shí)例來理解Binder悯森。

  1. 通過AIDL實(shí)例來了解Binder的用法
  2. 通過手動編碼實(shí)現(xiàn)ActivityManagerService

3.1 通過AIDL實(shí)例來了解Binder的用法

實(shí)現(xiàn)Binder通信的最常用方法就是通過aidl,aidl接口定義了Client和Server進(jìn)行通信的接口绪撵,對aidl不了解的同學(xué)請參考官方文檔Android 接口定義語言 (AIDL)瓢姻。

3.1.1 與Binder相關(guān)的幾個類的職責(zé)

在具體分析之前,我們需要先了解與Binder相關(guān)的幾個類的職責(zé):

  • IBinder

    跨進(jìn)程通信的Base接口音诈,它聲明了跨進(jìn)程通信需要實(shí)現(xiàn)的一系列抽象方法幻碱,實(shí)現(xiàn)了這個接口就說明可以進(jìn)行跨進(jìn)程通信,Client和Server都要實(shí)現(xiàn)此接口细溅。

  • IInterface

    這也是一個Base接口褥傍,用來表示Server提供了哪些能力,是Client和Server通信的協(xié)議喇聊。

  • Binder

    提供Binder服務(wù)的本地對象的基類恍风,它實(shí)現(xiàn)了IBinder接口,所有本地對象都要繼承這個類誓篱。

  • BinderProxy

    在Binder.java這個文件中還定義了一個BinderProxy類朋贬,這個類表示Binder代理對象它同樣實(shí)現(xiàn)了IBinder接口,不過它的很多實(shí)現(xiàn)都交由native層處理窜骄。Client中拿到的實(shí)際上是這個代理對象锦募。

  • Stub

    這個類在編譯aidl文件后自動生成,它繼承自Binder邻遏,表示它是一個Binder本地對象糠亩;它是一個抽象類,實(shí)現(xiàn)了IInterface接口党远,表明它的子類需要實(shí)現(xiàn)Server將要提供的具體能力(即aidl文件中聲明的方法)。

  • Proxy

    它實(shí)現(xiàn)了IInterface接口富弦,說明它是Binder通信過程的一部分沟娱;它實(shí)現(xiàn)了aidl中聲明的方法,但最終還是交由其中的mRemote成員來處理腕柜,說明它是一個代理對象济似,mRemote成員實(shí)際上就是BinderProxy。

3.1.2 AIDL實(shí)例

首先定義一個aidl文件盏缤,這個接口中聲明了一個getPid方法:

// IRemoteService.aidl
package com.rush.demo.aidltest;

interface IRemoteService {
    int getPid();
}

下面是編譯IRemoteService.aild后生成的java類:

// IRemoteService.java
package com.rush.demo.aidltest;

public interface IRemoteService extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {
        //Binder描述符
        private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService";

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

        public static com.rush.demo.aidltest.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.rush.demo.aidltest.IRemoteService))) {
                return ((com.rush.demo.aidltest.IRemoteService) iin);
            }
            return new com.rush.demo.aidltest.IRemoteService.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_getPid: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _result = this.getPid(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.rush.demo.aidltest.IRemoteService {
            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 getPid(java.lang.String name) 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(name);
                    mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            
        static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int getPid(java.lang.String name) throws android.os.RemoteException;
}

這個文件中有3個類:

  1. IRemoteService

    繼承至IInterface接口砰蠢,聲明了IRemoteService.aidl中聲明的getPid方法,它是Client和Service通信的接口唉铜。

  2. IRemoteService.Stub

    IRemoteService的靜態(tài)抽象內(nèi)部類台舱,繼承自Binder,其子類需要實(shí)現(xiàn)IRemoteService接口,表明它是Server的Binder本地對象竞惋,需要實(shí)現(xiàn)getPid接口柜去。

  3. IRemoteService.Stub.Proxy

    IRemoteService.Stub的靜態(tài)內(nèi)部類,它并沒有繼承自Binder拆宛,而是包含了一個IBinder對象嗓奢,這個對象其實(shí)是BinderProxy,說明它是Server在Client中的本地代理對象浑厚。Proxy實(shí)現(xiàn)了getPid接口股耽,將參數(shù)序列化后交由mRemote(BinderProxy)處理,實(shí)際上就是交給Binder驅(qū)動來完成與遠(yuǎn)程Stub的通信钳幅。

先來看Stub中的asInterface方法物蝙,這個方法通常是Client在bindService成功后,由Client來調(diào)用的贡这,作用是將綁定成功后返回的IBinder對象轉(zhuǎn)換為具體的IInterface接口茬末,Client拿到這個IInterface接口后就可以和自由的調(diào)用Server提供的方法了。

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

asInterface方法中既可能返回Stub本身的IRemoteService對象盖矫,也可能創(chuàng)建一個Proxy對象丽惭,這是為什么呢?因?yàn)锽inder雖然是跨進(jìn)程通信機(jī)制辈双,但也可以為本進(jìn)程服務(wù)责掏,也就是說Client和Server可能在同一個進(jìn)程,在同一個進(jìn)程就沒必要通過Binder驅(qū)動來中轉(zhuǎn)了湃望,直接訪問就可以了换衬;如果Client和Server在不同的進(jìn)程,就需要通過Binder代理對象來中轉(zhuǎn)证芭。也就是說:

  1. Client和Server在同一個進(jìn)程瞳浦,obj是Binder本地對象(Stub的子類),asInterface方法返回的就是Binder本地對象废士;
  2. Client和Server在不同的進(jìn)程叫潦,obj實(shí)際上是Binder代理對象,asInterface返回一個Proxy對象官硝。
//Binder.java
/**
 * obj.queryLocalInterface是怎樣去查找是否有本地的IInterface呢矗蕊,從Binder的代碼中可以看到,只是簡單的比較Binder的描述符和要查找的描述符是否匹配氢架,匹配的話直接返回mOwner傻咖,這個mOwner就是Stub構(gòu)造方法中調(diào)用attachInterface方法傳入的this參數(shù)。
 */
public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

final class BinderProxy implements IBinder {
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
}

public static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {
    // Binder描述符岖研,值為接口類名全稱
    private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService";

    public Stub() {
        //向Binder中綁定owner和descriptor
        this.attachInterface(this, DESCRIPTOR);
    }
}

obj.queryLocalInterface是怎樣去查找是否有本地的IInterface呢卿操,從Binder的代碼中可以看到,只是簡單的比較Binder的描述符和要查找的描述符是否匹配,匹配的話直接返回mOwner硬纤,這個mOwner就是Stub構(gòu)造方法中調(diào)用attachInterface方法傳入的this參數(shù)解滓。而BinderProxy的queryLocalInterface方法直接返回null。

Client中通過Binder調(diào)用Server方法有兩種場景:

  1. Client和Server在同一個進(jìn)程

    Stub.asInterface方法返回的是Stub對象筝家,即Binder本地對象洼裤。也就是說和Binder跨進(jìn)程通信無關(guān),直接調(diào)用即可溪王,此時Client調(diào)用方和Server響應(yīng)方在同一個線程中腮鞍。

  2. Client和Server在不同的進(jìn)程

    Stub.asInterface方法返回的是Binder代理對象,需要通過Binder驅(qū)動完成跨進(jìn)程通信莹菱。這種場景下移国,Client調(diào)用方線程會被掛起(Binder也提供了異步的方式,這里不討論)道伟,等待Server響應(yīng)后返回?cái)?shù)據(jù)迹缀。這里要注意的是,Server的響應(yīng)是在Server進(jìn)程的Binder線程池中處理的蜜徽,并不是主線程祝懂。

接下來分析跨進(jìn)程場景下,Client調(diào)用getPid方法的具體流程:

  1. Client調(diào)用Binder代理對象拘鞋,Client線程掛起

    Client中拿到的IRemoteService引用實(shí)際上是Proxy砚蓬,調(diào)用getPid方法實(shí)際上是調(diào)用Proxy的getPid方法,這個方法只是將參數(shù)序列化后盆色,調(diào)用了mRemote成員的transact方法灰蛙。Stub類中為IRemoteService中的每個方法定義了方法編號,transact方法中傳入getPid方法的編號隔躲。此時Client調(diào)用方線程掛起摩梧,等待Server響應(yīng)數(shù)據(jù)。

// Stub.Proxy
public int getPid(java.lang.String name) throws android.os.RemoteException {
    ...
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.writeString(name);
    mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
    _reply.readException();
    _result = _reply.readInt();
    ...
    return _result;
}
  1. Binder代理對象將請求派發(fā)給Binder驅(qū)動

    Proxy中的mRemote成員實(shí)際上是BinderProxy宣旱,而BinderProxy中的transact方法最終調(diào)用于transactNative方法仅父,也就是說Client的請求派發(fā)給了Binder驅(qū)動來處理。

  2. Binder驅(qū)動將請求派發(fā)給Server

    Binder驅(qū)動經(jīng)過一系列的處理后响鹃,將請求派發(fā)給了Server驾霜,即調(diào)用Server本地Binder對象(Stub)的onTransact方法最終在此方法中完成getPid方法的具體調(diào)用案训。在onTransact方法中买置,根據(jù)Proxy中調(diào)用transact時傳入的方法編號來區(qū)別具體要處理的方法。

// Stub
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    switch (code) {
        ...
        case TRANSACTION_getPid: {
            data.enforceInterface(DESCRIPTOR);
            //獲取方法參數(shù)
            java.lang.String _arg0 = data.readString();
            //調(diào)用getPid方法强霎,這個方法在Stub的子類忿项,即Server中實(shí)現(xiàn)
            int _result = this.getPid(_arg0);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}
  1. 喚醒Client線程,返回結(jié)果

    onTransact處理結(jié)束后,將結(jié)果寫入reply并返回至Binder驅(qū)動轩触,驅(qū)動喚醒掛起的Client線程寞酿,并將結(jié)果返回。至此脱柱,一次跨進(jìn)程通信完成伐弹。

3.2 手動編碼來實(shí)現(xiàn)ActivityManagerService

通過前面的示例我們已經(jīng)知道,aidl文件只是用來定義C/S交互的接口榨为,Android在編譯時會自動生成相應(yīng)的Java類惨好,生成的類中包含了Stub和Proxy靜態(tài)內(nèi)部類,用來封裝數(shù)據(jù)轉(zhuǎn)換的過程随闺,實(shí)際使用時只關(guān)心具體的Java接口類即可日川。為什么Stub和Proxy是靜態(tài)內(nèi)部類呢?這其實(shí)只是為了將三個類放在一個文件中矩乐,提高代碼的聚合性龄句。通過上面的分析,我們其實(shí)完全可以不通過aidl散罕,手動編碼來實(shí)現(xiàn)Binder的通信分歇,下面我們通過編碼來實(shí)現(xiàn)ActivityManagerService。

首先定義IActivityManager接口:

public interface IActivityManager extends IInterface {
    //binder描述符
    String DESCRIPTOR = "android.app.IActivityManager";
    //方法編號
    int TRANSACTION_startActivity = IBinder.FIRST_CALL_TRANSACTION + 0;
    //聲明一個啟動activity的方法笨使,為了簡化卿樱,這里只傳入intent參數(shù)
    int startActivity(Intent intent) throws RemoteException;
}

其次,實(shí)現(xiàn)ActivityManagerService側(cè)的本地Binder對象基類:

// 名稱隨意硫椰,不一致叫Stub
public abstract class ActivityManagerNative extends Binder implements IActivityManager {

    public static IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in = (IActivityManager) obj.queryLocalInterface(IActivityManager.DESCRIPTOR);
        if (in != null) {
            return in;
        }
        //代理對象繁调,見下面的代碼
        return new ActivityManagerProxy(obj);
    }

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

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            // 獲取binder描述符
            case INTERFACE_TRANSACTION:
                reply.writeString(IActivityManager.DESCRIPTOR);
                return true;
            // 啟動activity,從data中反序列化出intent參數(shù)后靶草,直接調(diào)用子類startActivity方法啟動activity蹄胰。
            case IActivityManager.TRANSACTION_startActivity:
                data.enforceInterface(IActivityManager.DESCRIPTOR);
                Intent intent = Intent.CREATOR.createFromParcel(data);
                int result = this.startActivity(intent);
                reply.writeNoException();
                reply.writeInt(result);
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }
}

再次,實(shí)現(xiàn)Client側(cè)的代理對象:

public class ActivityManagerProxy implements IActivityManager {
    private IBinder mRemote;

    public ActivityManagerProxy(IBinder remote) {
        mRemote = remote;
    }

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

    @Override
    public int startActivity(Intent intent) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int result;
        try {
            // 將intent參數(shù)序列化奕翔,寫入data中
            intent.writeToParcel(data, 0);
            // 調(diào)用BinderProxy對象的transact方法裕寨,交由Binder驅(qū)動處理。
            mRemote.transact(IActivityManager.TRANSACTION_startActivity, data, reply, 0);
            reply.readException();
            // 等待server執(zhí)行結(jié)束后派继,讀取執(zhí)行結(jié)果
            result = reply.readInt();
        } finally {
            data.recycle();
            reply.recycle();
        }
        return result;
    }
}

最后宾袜,實(shí)現(xiàn)Binder本地對象(IActivityManager接口):

public class ActivityManagerService extends ActivityManagerNative {
    @Override
    public int startActivity(Intent intent) throws RemoteException {
        // 啟動activity
        return 0;
    }
}

簡化版的ActivityManagerService到這里就已經(jīng)實(shí)現(xiàn)了,剩下就是Client需要獲取到AMS的代理對象IActivityManager就可以通信了驾窟。實(shí)際開發(fā)過程中通過aidl文件能夠自動編譯出中間代碼庆猫,并不需要我們手動去實(shí)現(xiàn),不過手動編碼能夠加深對Binder機(jī)制的理解绅络。在開發(fā)過程中我們也并不會直接使用到AMS月培,但了解AMS實(shí)現(xiàn)原理對熟悉Framework來說必不可少嘁字,關(guān)于AMS具體實(shí)現(xiàn)原理,我會在后續(xù)的文章中分析杉畜。

至此纪蜒,Binder機(jī)制的基本通信過程就介紹完了,由于Binder機(jī)制太過復(fù)雜此叠,本人水平有限纯续,文中難免出現(xiàn)錯誤或不足之處,歡迎大家指正灭袁。


參考資料

寫這篇文章學(xué)習(xí)了很多資料杆烁,整篇文章的思維結(jié)構(gòu)和結(jié)構(gòu)圖在很大程度上都參考了下面的文章,真誠感謝各位作者简卧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市举娩,隨后出現(xiàn)的幾起案子析校,更是在濱河造成了極大的恐慌,老刑警劉巖铜涉,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件智玻,死亡現(xiàn)場離奇詭異,居然都是意外死亡芙代,警方通過查閱死者的電腦和手機(jī)吊奢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纹烹,“玉大人页滚,你說我怎么就攤上這事∑毯牵” “怎么了裹驰?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長片挂。 經(jīng)常有香客問我幻林,道長,這世上最難降的妖魔是什么音念? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任沪饺,我火速辦了婚禮,結(jié)果婚禮上闷愤,老公的妹妹穿的比我還像新娘整葡。我一直安慰自己,他們只是感情好肝谭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布掘宪。 她就那樣靜靜地躺著,像睡著了一般攘烛。 火紅的嫁衣襯著肌膚如雪魏滚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天坟漱,我揣著相機(jī)與錄音鼠次,去河邊找鬼。 笑死芋齿,一個胖子當(dāng)著我的面吹牛腥寇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播觅捆,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼赦役,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了栅炒?” 一聲冷哼從身側(cè)響起掂摔,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赢赊,沒想到半個月后乙漓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡释移,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年叭披,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玩讳。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡涩蜘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熏纯,到底是詐尸還是另有隱情皱坛,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布豆巨,位于F島的核電站剩辟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏往扔。R本人自食惡果不足惜贩猎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萍膛。 院中可真熱鬧吭服,春花似錦、人聲如沸蝗罗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沼琉,卻和暖如春北苟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背打瘪。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工友鼻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闺骚。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓彩扔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親僻爽。 傳聞我的和親對象是個殘疾皇子虫碉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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