最近在網(wǎng)上看到一篇對于Binder講解絕佳的文章顾患,讀完感覺對于自己幫助很大设预,遂原封不動的轉(zhuǎn)載于此鳖枕,順便膜拜一下大神酿秸。原文的Github鏈接:
寫給 Android 應(yīng)用工程師的 Binder 原理剖析
一. 前言
這篇文章我醞釀了很久辣苏,參考了很多資料,讀了很多源碼退客,卻依舊不敢下筆。生怕自己理解上還有偏差粥脚,對大家造成誤解刷允,貽笑大方。又怕自己理解不夠透徹天通,無法用清晰直白的文字準確的表達出 Binder 的設(shè)計精髓。直到今天提筆寫作時還依舊戰(zhàn)戰(zhàn)兢兢诺祸。
Binder 之復雜遠遠不是一篇文章就能說清楚的筷笨,本文想站在一個更高的維度來俯瞰 Binder 的設(shè)計轴或,最終幫助大家形成一個完整的概念。對于應(yīng)用層開發(fā)的同學來說悼瘾,理解到本文這個程度也就差不多了卸勺。希望更加深入理解 Binder 實現(xiàn)機制的,可以閱讀文末的參考資料以及相關(guān)源碼悟狱。
二. Binder 概述
簡單介紹下什么是 Binder。Binder 是一種進程間通信機制浴麻,基于開源的 OpenBinder 實現(xiàn)焚挠;OpenBinder 起初由 Be Inc. 開發(fā)榛泛,后由 Plam Inc. 接手叉信。從字面上來解釋 Binder 有膠水硅急、粘合劑的意思覆享,顧名思義就是粘和不同的進程,使之實現(xiàn)通信营袜。對于 Binder 更全面的定義撒顿,等我們介紹完 Binder 通信原理后再做詳細說明。
2.1 為什么必須理解 Binder 荚板?
作為 Android 工程師的你凤壁,是不是常常會有這樣的疑問:
- 為什么 Activity 間傳遞對象需要序列化?
- Activity 的啟動流程是什么樣的拧抖?
- 四大組件底層的通信機制是怎樣的淌哟?
- AIDL 內(nèi)部的實現(xiàn)原理是什么?
- 插件化編程技術(shù)應(yīng)該從何學起缴啡?等等...
這些問題的背后都與 Binder 有莫大的關(guān)系携取,要弄懂上面這些問題理解 Bidner 通信機制是必須的晤斩。
我們知道 Android 應(yīng)用程序是由 Activity腊敲、Service、Broadcast Receiver 和 Content Provide 四大組件中的一個或者多個組成的。有時這些組件運行在同一進程乱凿,有時運行在不同的進程全蝶。這些進程間的通信就依賴于 Binder IPC 機制筐喳。不僅如此,Android 系統(tǒng)對應(yīng)用層提供的各種服務(wù)如:ActivityManagerService牌废、PackageManagerService 等都是基于 Binder IPC 機制來實現(xiàn)的番甩。Binder 機制在 Android 中的位置非常重要漱抓,毫不夸張的說理解 Binder 是邁向 Android 高級工程的第一步追迟。
2.2 為什么是 Binder ?
Android 系統(tǒng)是基于 Linux 內(nèi)核的,Linux 已經(jīng)提供了管道靶病、消息隊列、共享內(nèi)存和 Socket 等 IPC 機制。那為什么 Android 還要提供 Binder 來實現(xiàn) IPC 呢然痊?主要是基于性能冯吓、穩(wěn)定性和安全性幾方面的原因。
性能
首先說說性能上的優(yōu)勢。Socket 作為一款通用接口恶迈,其傳輸效率低佑颇,開銷大蚂维,主要用在跨網(wǎng)絡(luò)的進程間通信和本機上進程間的低速通信。消息隊列和管道采用存儲-轉(zhuǎn)發(fā)方式杯聚,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開辟的緩存區(qū)中,然后再從內(nèi)核緩存區(qū)拷貝到接收方緩存區(qū)腮恩,至少有兩次拷貝過程均澳。共享內(nèi)存雖然無需拷貝,但控制復雜各薇,難以使用。Binder 只需要一次數(shù)據(jù)拷貝,性能上僅次于共享內(nèi)存。
注:各種IPC方式數(shù)據(jù)拷貝次數(shù),此表來源于Android Binder 設(shè)計與實現(xiàn) - 設(shè)計篇
IPC方式 | 數(shù)據(jù)拷貝次數(shù) |
---|---|
共享內(nèi)存 | 0 |
Binder | 1 |
Socket/管道/消息隊列 | 2 |
穩(wěn)定性
再說說穩(wěn)定性,Binder 基于 C/S 架構(gòu)亭引,客戶端(Client)有什么需求就丟給服務(wù)端(Server)去完成萌京,架構(gòu)清晰、職責明確又相互獨立宏浩,自然穩(wěn)定性更好知残。共享內(nèi)存雖然無需拷貝,但是控制負責比庄,難以使用求妹。從穩(wěn)定性的角度講,Binder 機制是優(yōu)于內(nèi)存共享的佳窑。
安全性
另一方面就是安全性制恍。Android 作為一個開放性的平臺,市場上有各類海量的應(yīng)用供用戶選擇安裝神凑,因此安全性對于 Android 平臺而言極其重要净神。作為用戶當然不希望我們下載的 APP 偷偷讀取我的通信錄,上傳我的隱私數(shù)據(jù)溉委,后臺偷跑流量鹃唯、消耗手機電量。傳統(tǒng)的 IPC 沒有任何安全措施瓣喊,完全依賴上層協(xié)議來確保坡慌。首先傳統(tǒng)的 IPC 接收方無法獲得對方可靠的進程用戶ID/進程ID(UID/PID),從而無法鑒別對方身份型宝。Android 為每個安裝好的 APP 分配了自己的 UID八匠,故而進程的 UID 是鑒別進程身份的重要標志絮爷。傳統(tǒng)的 IPC 只能由用戶在數(shù)據(jù)包中填入 UID/PID趴酣,但這樣不可靠,容易被惡意程序利用坑夯♂可靠的身份標識只有由 IPC 機制在內(nèi)核中添加。其次傳統(tǒng)的 IPC 訪問接入點是開放的柜蜈,只要知道這些接入點的程序都可以和對端建立連接仗谆,不管怎樣都無法阻止惡意程序通過猜測接收方地址獲得連接。同時 Binder 既支持實名 Binder淑履,又支持匿名 Binder隶垮,安全性高。
基于上述原因秘噪,Android 需要建立一套新的 IPC 機制來滿足系統(tǒng)對穩(wěn)定性狸吞、傳輸性能和安全性方面的要求,這就是 Binder。
最后用一張表格來總結(jié)下 Binder 的優(yōu)勢:
優(yōu)勢 | 描述 |
---|---|
性能 | 只需要一次數(shù)據(jù)拷貝蹋偏,性能上僅次于共享內(nèi)存 |
穩(wěn)定性 | 基于 C/S 架構(gòu)便斥,職責明確、架構(gòu)清晰威始,因此穩(wěn)定性好 |
安全性 | 為每個 APP 分配 UID枢纠,進程的 UID 是鑒別進程身份的重要標志 |
三. Linux 下傳統(tǒng)的進程間通信原理
了解 Linux IPC 相關(guān)的概念和原理有助于我們理解 Binder 通信原理。因此黎棠,在介紹 Binder 跨進程通信原理之前晋渺,我們先聊聊 Linux 系統(tǒng)下傳統(tǒng)的進程間通信是如何實現(xiàn)。
3.1 基本概念介紹
這里我們先從 Linux 中進程間通信涉及的一些基本概念開始介紹脓斩,然后逐步展開些举,向大家說明傳統(tǒng)的進程間通信的原理。
上圖展示了 Liunx 中跨進程通信涉及到的一些基本概念:
- 進程隔離
- 進程空間劃分:用戶空間(User Space)/內(nèi)核空間(Kernel Space)
- 系統(tǒng)調(diào)用:用戶態(tài)/內(nèi)核態(tài)
進程隔離
簡單的說就是操作系統(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 級供用戶程序使用必搞。
當一個任務(wù)(進程)執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時,稱進程處于內(nèi)核運行態(tài)(內(nèi)核態(tài))囊咏。此時處理器處于特權(quán)級最高的(0級)內(nèi)核代碼中執(zhí)行恕洲。當進程處于內(nèi)核態(tài)時,執(zhí)行的內(nèi)核代碼會使用當前進程的內(nèi)核棧梅割。每個進程都有自己的內(nèi)核棧霜第。
當進程在執(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)核空間拷貝到用戶空間
3.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)用 copy_from_user() 函數(shù)將數(shù)據(jù)從用戶空間的內(nèi)存緩存區(qū)拷貝到內(nèi)核空間的內(nèi)核緩存區(qū)中殊校。同樣的晴玖,接收方進程在接收數(shù)據(jù)時在自己的用戶空間開辟一塊內(nèi)存緩存區(qū)读存,然后內(nèi)核程序調(diào)用 copy_to_user() 函數(shù)將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收進程的內(nèi)存緩存區(qū)为流。這樣數(shù)據(jù)發(fā)送方進程和數(shù)據(jù)接收方進程就完成了一次數(shù)據(jù)傳輸,我們稱完成了一次進程間通信让簿。如下圖:
這種傳統(tǒng)的 IPC 通信方式有兩個問題:
- 性能低下敬察,一次數(shù)據(jù)傳遞需要經(jīng)歷:內(nèi)存緩存區(qū) --> 內(nèi)核緩存區(qū) --> 內(nèi)存緩存區(qū),需要 2 次數(shù)據(jù)拷貝尔当;
- 接收數(shù)據(jù)的緩存區(qū)由數(shù)據(jù)接收進程提供莲祸,但是接收進程并不知道需要多大的空間來存放將要傳遞過來的數(shù)據(jù)蹂安,因此只能開辟盡可能大的內(nèi)存空間或者先調(diào)用 API 接收消息頭來獲取消息體的大小,這兩種做法不是浪費空間就是浪費時間锐帜。
四. Binder 跨進程通信原理
理解了 Linux IPC 相關(guān)概念和通信原理田盈,接下來我們正式介紹下 Binder IPC 的原理。
4.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)核空間,負責各個用戶進程通過 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)存映射能夠提供對進程間通信的支持帕胆。
4.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 通信過程通常是這樣:
- 首先 Binder 驅(qū)動在內(nèi)核空間創(chuàng)建一個數(shù)據(jù)接收緩存區(qū)铝穷;
- 接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū)钠怯,建立內(nèi)核緩存區(qū)和內(nèi)核中數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系,以及內(nèi)核中數(shù)據(jù)接收緩存區(qū)和接收進程用戶空間地址的映射關(guān)系晦炊;
- 發(fā)送方進程通過系統(tǒng)調(diào)用 copy_from_user() 將數(shù)據(jù) copy 到內(nèi)核中的內(nèi)核緩存區(qū)断国,由于內(nèi)核緩存區(qū)和接收進程的用戶空間存在內(nèi)存映射稳衬,因此也就相當于把數(shù)據(jù)發(fā)送到了接收進程的用戶空間,這樣便完成了一次進程間的通信街夭。
如下圖:
五. Binder 通信模型
介紹完 Binder IPC 的底層通信原理莱坎,接下來我們看看實現(xiàn)層面是如何設(shè)計的檐什。
一次完整的進程間通信必然至少包含兩個進程乃正,通常我們稱通信的雙方分別為客戶端進程(Client)和服務(wù)端進程(Server),由于進程隔離機制的存在名党,通信雙方必然需要借助 Binder 來實現(xiàn)传睹。
5.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)跨進程通信。
Client让腹、Server、ServiceManager腹纳、Binder 驅(qū)動這幾個組件在通信過程中扮演的角色就如同互聯(lián)網(wǎng)中服務(wù)器(Server)足画、客戶端(Client)锌云、DNS域名服務(wù)器(ServiceManager)以及路由器(Binder 驅(qū)動)之前的關(guān)系彬向。
通常我們訪問一個網(wǎng)頁的步驟是這樣的:首先在瀏覽器輸入一個地址娃胆,如 www.google.com 然后按下回車鍵凿蒜。但是并沒有辦法通過域名地址直接找到我們要訪問的服務(wù)器废封,因此需要首先訪問 DNS 域名服務(wù)器漂洋,域名服務(wù)器中保存了 www.google.com 對應(yīng)的 ip 地址 10.249.23.13,然后通過這個 ip 地址才能放到到 www.google.com 對應(yīng)的服務(wù)器贝咙。
Android Binder 設(shè)計與實現(xiàn)一文中對 Client没讲、Server徙缴、ServiceManager疏叨、Binder 驅(qū)動有很詳細的描述,以下是部分摘錄:
Binder 驅(qū)動
Binder 驅(qū)動就如同路由器一樣秀又,是整個通信的核心;驅(qū)動負責進程之間 Binder 通信的建立昏苏,Binder 在進程之間的傳遞,Binder 引用計數(shù)管理,數(shù)據(jù)包在進程之間的傳遞和交互等一系列底層支持浦译。
ServiceManager 與實名 Binder
ServiceManager 和 DNS 類似精盅,作用是將字符形式的 Binder 名字轉(zhuǎn)化成 Client 中對該 Binder 的引用叹俏,使得 Client 能夠通過 Binder 的名字獲得對 Binder 實體的引用。注冊了名字的 Binder 叫實名 Binder僻族,就像網(wǎng)站一樣除了除了有 IP 地址意外還有自己的網(wǎng)址粘驰。Server 創(chuàng)建了 Binder,并為它起一個字符形式述么,可讀易記得名字蝌数,將這個 Binder 實體連同名字一起以數(shù)據(jù)包的形式通過 Binder 驅(qū)動發(fā)送給 ServiceManager ,通知 ServiceManager 注冊一個名為“張三”的 Binder度秘,它位于某個 Server 中顶伞。驅(qū)動為這個穿越進程邊界的 Binder 創(chuàng)建位于內(nèi)核中的實體節(jié)點以及 ServiceManager 對實體的引用语卤,將名字以及新建的引用打包傳給 ServiceManager齐婴。ServiceManger 收到數(shù)據(jù)后從中取出名字和引用填入查找表毡证。
細心的讀者可能會發(fā)現(xiàn)屎勘,ServierManager 是一個進程,Server 是另一個進程进副,Server 向 ServiceManager 中注冊 Binder 必然涉及到進程間通信姐帚。當前實現(xiàn)進程間通信又要用到進程間通信,這就好像蛋可以孵出雞的前提卻是要先找只雞下蛋!Binder 的實現(xiàn)比較巧妙匙姜,就是預先創(chuàng)造一只雞來下蛋。ServiceManager 和其他進程同樣采用 Bidner 通信庐杨,ServiceManager 是 Server 端鸟辅,有自己的 Binder 實體堡纬,其他進程都是 Client悴灵,需要通過這個 Binder 的引用來實現(xiàn) Binder 的注冊被芳,查詢和獲取趣兄。ServiceManager 提供的 Binder 比較特殊总棵,它沒有名字也不需要注冊。當一個進程使用 BINDER_SET_CONTEXT_MGR 命令將自己注冊成 ServiceManager 時 Binder 驅(qū)動會自動為它創(chuàng)建 Binder 實體(這就是那只預先造好的那只雞)候味。其次這個 Binder 實體的引用在所有 Client 中都固定為 0 而無需通過其它手段獲得。也就是說允青,一個 Server 想要向 ServiceManager 注冊自己的 Binder 就必須通過這個 0 號引用和 ServiceManager 的 Binder 通信戈毒。類比互聯(lián)網(wǎng)碳蛋,0 號引用就好比是域名服務(wù)器的地址胚泌,你必須預先動態(tài)或者手工配置好。要注意的是肃弟,這里說的 Client 是相對于 ServiceManager 而言的玷室,一個進程或者應(yīng)用程序可能是提供服務(wù)的 Server,但對于 ServiceManager 來說它仍然是個 Client笤受。
Client 獲得實名 Binder 的引用
Server 向 ServiceManager 中注冊了 Binder 以后穷缤, Client 就能通過名字獲得 Binder 的引用了。Client 也利用保留的 0 號引用向 ServiceManager 請求訪問某個 Binder: 我申請訪問名字叫張三的 Binder 引用箩兽。ServiceManager 收到這個請求后從請求數(shù)據(jù)包中取出 Binder 名稱津肛,在查找表里找到對應(yīng)的條目,取出對應(yīng)的 Binder 引用作為回復發(fā)送給發(fā)起請求的 Client汗贫。從面向?qū)ο蟮慕嵌瓤瓷碜琒erver 中的 Binder 實體現(xiàn)在有兩個引用:一個位于 ServiceManager 中,一個位于發(fā)起請求的 Client 中落包。如果接下來有更多的 Client 請求該 Binder部蛇,系統(tǒng)中就會有更多的引用指向該 Binder ,就像 Java 中一個對象有多個引用一樣咐蝇。
5.2 Binder 通信過程
至此涯鲁,我們大致能總結(jié)出 Binder 通信過程:
- 首先,一個進程使用 BINDER_SET_CONTEXT_MGR 命令通過 Binder 驅(qū)動將自己注冊成為 ServiceManager嘹害;
- Server 通過驅(qū)動向 ServiceManager 中注冊 Binder(Server 中的 Binder 實體)撮竿,表明可以對外提供服務(wù)。驅(qū)動為這個 Binder 創(chuàng)建位于內(nèi)核中的實體節(jié)點以及 ServiceManager 對實體的引用笔呀,將名字以及新建的引用打包傳給 ServiceManager,ServiceManger 將其填入查找表髓需。
- Client 通過名字许师,在 Binder 驅(qū)動的幫助下從 ServiceManager 中獲取到對 Binder 實體的引用,通過這個引用就能實現(xiàn)和 Server 進程的通信僚匆。
我們看到整個通信過程都需要 Binder 驅(qū)動的接入微渠。下圖能更加直觀的展現(xiàn)整個通信過程(為了進一步抽象通信過程以及呈現(xiàn)上的方便,下圖我們忽略了 Binder 實體及其引用的概念):
5.3 Binder 通信中的代理模式
我們已經(jīng)解釋清楚 Client咧擂、Server 借助 Binder 驅(qū)動完成跨進程通信的實現(xiàn)機制了逞盆,但是還有個問題會讓我們困惑。A 進程想要 B 進程中某個對象(object)是如何實現(xiàn)的呢松申?畢竟它們分屬不同的進程云芦,A 進程 沒法直接使用 B 進程中的 object俯逾。
前面我們介紹過跨進程通信的過程都有 Binder 驅(qū)動的參與,因此在數(shù)據(jù)流經(jīng) Binder 驅(qū)動的時候驅(qū)動會對數(shù)據(jù)做一層轉(zhuǎn)換舅逸。當 A 進程想要獲取 B 進程中的 object 時桌肴,驅(qū)動并不會真的把 object 返回給 A,而是返回了一個跟 object 看起來一模一樣的代理對象 objectProxy琉历,這個 objectProxy 具有和 object 一摸一樣的方法坠七,但是這些方法并沒有 B 進程中 object 對象那些方法的能力,這些方法只需要把把請求參數(shù)交給驅(qū)動即可旗笔。對于 A 進程來說和直接調(diào)用 object 中的方法是一樣的彪置。
當 Binder 驅(qū)動接收到 A 進程的消息后,發(fā)現(xiàn)這是個 objectProxy 就去查詢自己維護的表單蝇恶,一查發(fā)現(xiàn)這是 B 進程 object 的代理對象拳魁。于是就會去通知 B 進程調(diào)用 object 的方法,并要求 B 進程把返回結(jié)果發(fā)給自己艘包。當驅(qū)動拿到 B 進程的返回結(jié)果后就會轉(zhuǎn)發(fā)給 A 進程的猛,一次通信就完成了。
5.4 Binder 的完整定義
現(xiàn)在我們可以對 Binder 做個更加全面的定義了:
- 從進程間通信的角度看想虎,Binder 是一種進程間通信的機制卦尊;
- 從 Server 進程的角度看,Binder 指的是 Server 中的 Binder 實體對象舌厨;
- 從 Client 進程的角度看岂却,Binder 指的是對 Binder 代理對象,是 Binder 實體對象的一個遠程代理
- 從傳輸過程的角度看裙椭,Binder 是一個可以跨進程傳輸?shù)膶ο篚锪ǎ籅inder 驅(qū)動會對這個跨越進程邊界的對象對一點點特殊處理,自動完成代理對象和本地對象之間的轉(zhuǎn)換揉燃。
六. 手動編碼實現(xiàn)跨進程調(diào)用
通常我們在做開發(fā)時扫尺,實現(xiàn)進程間通信用的最多的就是 AIDL。當我們定義好 AIDL 文件炊汤,在編譯時編譯器會幫我們生成代碼實現(xiàn) IPC 通信正驻。借助 AIDL 編譯以后的代碼能幫助我們進一步理解 Binder IPC 的通信原理。
但是無論是從可讀性還是可理解性上來看抢腐,編譯器生成的代碼對開發(fā)者并不友好姑曙。比如一個 BookManager.aidl 文件對應(yīng)會生成一個 BookManager.java 文件,這個 java 文件包含了一個 BookManager 接口迈倍、一個 Stub 靜態(tài)的抽象類和一個 Proxy 靜態(tài)類伤靠。Proxy 是 Stub 的靜態(tài)內(nèi)部類,Stub 又是 BookManager 的靜態(tài)內(nèi)部類啼染,這就造成了可讀性和可理解性的問題宴合。
Android 之所以這樣設(shè)計其實是有道理的焕梅,因為當有多個 AIDL 文件的時候把 BookManager、Stub形纺、Proxy 放在同一個文件里能有效避免 Stub 和 Proxy 重名的問題丘侠。
因此便于大家理解,下面我們來手動編寫代碼來實現(xiàn)跨進程調(diào)用逐样。
6.1 各 Java 類職責描述
在正式編碼實現(xiàn)跨進程調(diào)用之前蜗字,先介紹下實現(xiàn)過程中用到的一些類。了解了這些類的職責脂新,有助于我們更好的理解和實現(xiàn)跨進程通信挪捕。
IBinder : IBinder 是一個接口,代表了一種跨進程通信的能力争便。只要實現(xiàn)了這個借口级零,這個對象就能跨進程傳輸。
IInterface : IInterface 代表的就是 Server 進程對象具備什么樣的能力(能提供哪些方法滞乙,其實對應(yīng)的就是 AIDL 文件中定義的接口)
Binder : Java 層的 Binder 類奏纪,代表的其實就是 Binder 本地對象。BinderProxy 類是 Binder 類的一個內(nèi)部類斩启,它代表遠程進程的 Binder 對象的本地代理序调;這兩個類都繼承自 IBinder, 因而都具有跨進程傳輸?shù)哪芰Γ粚嶋H上兔簇,在跨越進程的時候发绢,Binder 驅(qū)動會自動完成這兩個對象的轉(zhuǎn)換。
Stub : AIDL 的時候垄琐,編譯工具會給我們生成一個名為 Stub 的靜態(tài)內(nèi)部類边酒;這個類繼承了 Binder, 說明它是一個 Binder 本地對象,它實現(xiàn)了 IInterface 接口狸窘,表明它具有 Server 承諾給 Client 的能力墩朦;Stub 是一個抽象類,具體的 IInterface 的相關(guān)實現(xiàn)需要開發(fā)者自己實現(xiàn)翻擒。
6.2 實現(xiàn)過程講解
一次跨進程通信必然會涉及到兩個進程介杆,在這個例子中 RemoteService 作為服務(wù)端進程,提供服務(wù)韭寸;ClientActivity 作為客戶端進程,使用 RemoteService 提供的服務(wù)荆隘。如下圖:
那么服務(wù)端進程具備什么樣的能力恩伺?能為客戶端提供什么樣的服務(wù)呢?還記得我們前面介紹過的 IInterface 嗎椰拒,它代表的就是服務(wù)端進程具體什么樣的能力晶渠。因此我們需要定義一個 BookManager 接口凰荚,BookManager 繼承自 IIterface,表明服務(wù)端具備什么樣的能力褒脯。
/**
* 這個類用來定義服務(wù)端 RemoteService 具備什么樣的能力
*/
public interface BookManager extends IInterface {
void addBook(Book book) throws RemoteException;
}
只定義服務(wù)端具備什么樣的能力是不夠的便瑟,既然是跨進程調(diào)用,那么接下來我們得實現(xiàn)一個跨進程調(diào)用對象 Stub番川。Stub 繼承 Binder, 說明它是一個 Binder 本地對象到涂;實現(xiàn) IInterface 接口,表明具有 Server 承諾給 Client 的能力颁督;Stub 是一個抽象類践啄,具體的 IInterface 的相關(guān)實現(xiàn)需要調(diào)用方自己實現(xiàn)。
public abstract class Stub extends Binder implements BookManager {
...
public static BookManager asInterface(IBinder binder) {
if (binder == null)
return null;
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof BookManager)
return (BookManager) iin;
return new Proxy(binder);
}
...
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSAVTION_addBook:
data.enforceInterface(DESCRIPTOR);
Book arg0 = null;
if (data.readInt() != 0) {
arg0 = Book.CREATOR.createFromParcel(data);
}
this.addBook(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
...
}
Stub 類中我們重點介紹下 asInterface
和 onTransact
沉御。
先說說 asInterface
屿讽,當 Client 端在創(chuàng)建和服務(wù)端的連接,調(diào)用 bindService 時需要創(chuàng)建一個 ServiceConnection 對象作為入?yún)⒎婉伞T?ServiceConnection 的回調(diào)方法 onServiceConnected 中 會通過這個 asInterface(IBinder binder) 拿到 BookManager 對象伐谈,這個 IBinder 類型的入?yún)?binder 是驅(qū)動傳給我們的,正如你在代碼中看到的一樣试疙,方法中會去調(diào)用 binder.queryLocalInterface() 去查找 Binder 本地對象诵棵,如果找到了就說明 Client 和 Server 在同一進程,那么這個 binder 本身就是 Binder 本地對象效斑,可以直接使用非春。否則說明是 binder 是個遠程對象,也就是 BinderProxy缓屠。因此需要我們創(chuàng)建一個代理對象 Proxy奇昙,通過這個代理對象來是實現(xiàn)遠程訪問。
接下來我們就要實現(xiàn)這個代理類 Proxy 了敌完,既然是代理類自然需要實現(xiàn) BookManager 接口储耐。
public class Proxy implements BookManager {
...
public Proxy(IBinder remote) {
this.remote = remote;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
replay.readException();
} finally {
replay.recycle();
data.recycle();
}
}
...
}
我們看看 addBook() 的實現(xiàn);在 Stub 類中滨溉,addBook(Book book) 是一個抽象方法什湘,Server 端需要去實現(xiàn)它。
- 如果 Client 和 Server 在同一個進程晦攒,那么直接就是調(diào)用這個方法闽撤。
- 如果是遠程調(diào)用,Client 想要調(diào)用 Server 的方法就需要通過 Binder 代理來完成脯颜,也就是上面的 Proxy哟旗。
在 Proxy 中的 addBook() 方法中首先通過 Parcel 將數(shù)據(jù)序列化,然后調(diào)用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中創(chuàng)建闸餐,能走到創(chuàng)建 Proxy 這一步就說明 Proxy 構(gòu)造函數(shù)的入?yún)⑹?BinderProxy饱亮,即這里的 remote 是個 BinderProxy 對象。最終通過一系列的函數(shù)調(diào)用舍沙,Client 進程通過系統(tǒng)調(diào)用陷入內(nèi)核態(tài)近上,Client 進程中執(zhí)行 addBook() 的線程掛起等待返回;驅(qū)動完成一系列的操作之后喚醒 Server 進程拂铡,調(diào)用 Server 進程本地對象的 onTransact()壹无。最終又走到了 Stub 中的 onTransact() 中,onTransact() 根據(jù)函數(shù)編號調(diào)用相關(guān)函數(shù)(在 Stub 類中為 BookManager 接口中的每個函數(shù)中定義了一個編號和媳,只不過上面的源碼中我們簡化掉了格遭;在跨進程調(diào)用的時候,不會傳遞函數(shù)而是傳遞編號來指明要調(diào)用哪個函數(shù))留瞳;我們這個例子里面拒迅,調(diào)用了 Binder 本地對象的 addBook() 并將結(jié)果返回給驅(qū)動,驅(qū)動喚醒 Client 進程里剛剛掛起的線程并將結(jié)果返回她倘。
這樣一次跨進程調(diào)用就完成了璧微。
完整的代碼我放到 GitHub 上了,有興趣的小伙伴可以去看看硬梁。源碼地址:https://github.com/BaronZ88/HelloBinder
最后建議大家在不借助 AIDL 的情況下手寫實現(xiàn) Client 和 Server 進程的通信前硫,加深對 Binder 通信過程的理解。
受個人能力水平限制荧止,文章中難免會有錯誤屹电。如果大家發(fā)現(xiàn)文章不足之處,歡迎與我溝通交流跃巡。
本文在寫作過程中參考了很多文章危号、書籍和源碼,其中有很多描述和圖片都借鑒了下面的文章素邪,在這里感謝大佬們的無私分享外莲!
參考資料如下:
- Android Binder 設(shè)計與實現(xiàn) - 設(shè)計篇
- Android 進程間通信(IPC)機制 Binder 簡要介紹和學習計劃、《Android 系統(tǒng)源代碼情景分析》
- Binder 學習指南
- Binder 系列文章
- Android 圖文詳解 Binder 跨進程通信原理
- Android 深入淺出之 Binder 機制
- 用戶空間與內(nèi)核空間
- 認真分析 mmap :是什么 為什么 怎么用
如果你喜歡我的文章兔朦,就關(guān)注下我的公眾號 BaronTalk 偷线、 知乎專欄 或者在 GitHub 上添個 Star 吧!
- 微信公眾號:BaronTalk
- 知乎專欄:https://zhuanlan.zhihu.com/baron
- GitHub:https://github.com/BaronZ88
- 個人博客:http://baronzhang.com