1、為什么需要binder
Android 系統(tǒng)中毅厚,每個(gè)應(yīng)用程序是由 Android 的 Activity塞颁,Service,Broadcast吸耿, ContentProvider 這四劍客的中一個(gè)或多個(gè)組合而成祠锣,這四劍客所涉及的多進(jìn)程 間的通信底層都是依賴于 Binder IPC 機(jī)制。例如當(dāng)進(jìn)程 A 中的 Activity 要向進(jìn) 程 B 中的 Service 通信咽安,這便需要依賴于 Binder IPC伴网。不僅于此,整個(gè) Android 系統(tǒng)架構(gòu)中妆棒,大量采用了 Binder 機(jī)制作為 IPC(進(jìn)程間通信)方案是偷,當(dāng)然也存 在部分其他的 IPC 方式拳氢,比如 Zygote 通信便是采用 socket。
2蛋铆、IPC原理
從進(jìn)程角度來(lái)看 IPC 機(jī)制
每個(gè) Android 的進(jìn)程馋评,只能運(yùn)行在自己進(jìn)程所擁有的虛擬地址空間。對(duì)應(yīng)一個(gè) 4GB 的虛擬地址空間刺啦,其中 3GB 是用戶空間留特,1GB 是內(nèi)核空間,當(dāng)然內(nèi)核空間 的大小是可以通過(guò)參數(shù)配置調(diào)整的玛瘸。對(duì)于用戶空間蜕青,不同進(jìn)程之間彼此是不能共 享的,而內(nèi)核空間卻是可共享的糊渊。Client 進(jìn)程向 Server 進(jìn)程通信右核,恰恰是利用 進(jìn)程間可共享的內(nèi)核內(nèi)存空間來(lái)完成底層通信工作的,Client 端與 Server 端進(jìn) 程往往采用 ioctl 等方法跟內(nèi)核空間的驅(qū)動(dòng)進(jìn)行交互渺绒。
3贺喝、Binder原理
Binder 通信采用 C/S 架構(gòu),從組件視角來(lái)說(shuō)宗兼,包含 Client躏鱼、Server、 ServiceManager 以及 binder 驅(qū)動(dòng)殷绍,其中 ServiceManager 用于管理系統(tǒng)中的各 種服務(wù)染苛。架構(gòu)圖如下所示:
可以看出無(wú)論是注冊(cè)服務(wù)和獲取服務(wù)的過(guò)程都需要 ServiceManager,需要注意 的是此處的 Service Manager 是指 Native 層的 ServiceManager(C++)主到,并非 指 framework 層的 ServiceManager(Java)茶行。ServiceManager 是整個(gè) Binder 通 信機(jī)制的大管家,是 Android 進(jìn)程間通信機(jī)制 Binder 的守護(hù)進(jìn)程登钥,要掌握 Binder 機(jī)制拢军,首先需要了解系統(tǒng)是如何首次啟動(dòng) Service Manager。當(dāng) Service Manager 啟動(dòng)之后怔鳖,Client 端和 Server 端通信時(shí)都需要先獲取 Service Manager 接口, 才能開(kāi)始通信服務(wù)固蛾。 圖中 Client/Server/ServiceManage 之間的相互通信都是基于 Binder 機(jī)制结执。既然 基于 Binder 機(jī)制通信,那么同樣也是 C/S 架構(gòu)艾凯,則圖中的 3 大步驟都有相應(yīng)的 Client 端與 Server 端献幔。
- 注 冊(cè) 服 務(wù) (addService) : Server 進(jìn) 程 要 先 注 冊(cè) Service 到 ServiceManager。該過(guò)程:Server 是客戶端趾诗,ServiceManager 是服務(wù)端蜡感。
- 獲 取 服 務(wù) (getService) : Client 進(jìn) 程 使 用 某 個(gè) Service 前 蹬蚁, 須 先 向 ServiceManager 中獲取相應(yīng)的 Service。該過(guò)程:Client 是客戶端郑兴, ServiceManager 是服務(wù)端犀斋。
- 使用服務(wù):Client 根據(jù)得到的 Service 信息建立與 Service 所在的 Server 進(jìn)程通信的通路,然后就可以直接與 Service 交互情连。該過(guò)程:client 是客 戶端叽粹,server 是服務(wù)端。
圖中的 Client,Server,Service Manager 之間交互都是虛線表示却舀,是由于它們彼 此之間不是直接交互的虫几,而是都通過(guò)與 Binder 驅(qū)動(dòng)進(jìn)行交互的,從而實(shí)現(xiàn) IPC 通信方式挽拔。其中 Binder 驅(qū)動(dòng)位于內(nèi)核空間辆脸,Client,Server,Service Manager 位 于用戶空間。Binder 驅(qū)動(dòng)和 Service Manager 可以看做是 Android 平臺(tái)的基礎(chǔ) 架構(gòu)螃诅,而 Client 和 Server 是 Android 的應(yīng)用層啡氢,開(kāi)發(fā)人員只需自定義實(shí)現(xiàn) client、 Server 端州刽,借助 Android 的基本平臺(tái)架構(gòu)便可以直接進(jìn)行 IPC 通信空执。
3.1、C/S模式
BpBinder(客戶端)和 BBinder(服務(wù)端)都是 Android 中 Binder 通信相關(guān)的代表穗椅, 它們都從 IBinder 類中派生而來(lái)辨绊,關(guān)系圖如下:
四、Binder面試題全解析
Binder 是一種進(jìn)程間通信機(jī)制匹表,做 Android 開(kāi)發(fā)肯定離不開(kāi)跟 Binder 打交道在面試中 Binder 也是經(jīng)常被問(wèn)的一個(gè)點(diǎn)门坷,那么本篇文章就以問(wèn)答的方式,帶你 了解一下關(guān)于 Binder 的重要知識(shí)點(diǎn)袍镀。
1.Binder 有什么優(yōu)勢(shì)
1默蚌、性能方面
共享內(nèi)存 0 次數(shù)據(jù)拷貝
Binder 1 次數(shù)據(jù)拷貝
Socket/管道/消息隊(duì)列 2 次數(shù)據(jù)拷貝
2、穩(wěn)定性方面
Binder:基于 C/S 架構(gòu)苇羡,客戶端( Client)有什么需求就丟給服務(wù)端(Server)去完成
架構(gòu)清晰绸吸、職責(zé)明確又相互獨(dú)立,自然穩(wěn)定性更好
共享內(nèi)存:雖然無(wú)需拷貝设江,但是控制復(fù)雜锦茁,難以使用
從穩(wěn)定性的角度講,Binder 機(jī)制是優(yōu)于內(nèi)存共享的叉存。
3码俩、安全性方面
傳統(tǒng)的 IPC 沒(méi)有任何安全措施,安全依賴上層協(xié)議來(lái)確保歼捏。
傳統(tǒng)的 IPC 方法無(wú)法獲得對(duì)方可靠的進(jìn)程用戶 ID/進(jìn)程 UI(UID/PID)稿存,從而無(wú)法別對(duì)方身份笨篷。
傳統(tǒng)的 IPC 只能由用戶在數(shù)據(jù)包中填入 UID/PID,容易被惡意程序利用瓣履。
傳統(tǒng)的 IPC 訪問(wèn)接入點(diǎn)是開(kāi)放的率翅,無(wú)法阻止惡意程序通過(guò)猜測(cè)接收方地址獲得連續(xù)
Binder 既支持實(shí)名 Binder,又支持匿名 Binder拂苹,安全性高安聘。
2.Binder 是如何做到一次拷貝的
主要是因?yàn)?Linux 是使用的虛擬內(nèi)存尋址方式,它有如下特性:
- 用戶空間的虛擬內(nèi)存地址是映射到物理內(nèi)存中的
- 對(duì)虛擬內(nèi)存的讀寫(xiě)實(shí)際上是對(duì)物理內(nèi)存的讀寫(xiě)瓢棒,這個(gè)過(guò)程就是內(nèi)存映射
- 這個(gè)內(nèi)存映射過(guò)程是通過(guò)系統(tǒng)調(diào)用 mmap()來(lái)實(shí)現(xiàn)的
Binder 借助了內(nèi)存映射的方法浴韭,在內(nèi)核空間和接收方用戶空間的數(shù)據(jù)緩存區(qū)之間做 了一層內(nèi)存映射,就相 當(dāng)于直接拷貝到了接收方用戶空間的數(shù)據(jù)緩存區(qū)脯宿,從而減少 了一次數(shù)據(jù)拷貝
3.MMAP 的內(nèi)存映射原理了解嗎
MMAP 內(nèi)存映射的實(shí)現(xiàn)過(guò)程念颈,總的來(lái)說(shuō)可以分為三個(gè)階段:
(一)進(jìn)程啟動(dòng)映射過(guò)程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
- 進(jìn)程在用戶空間調(diào)用庫(kù)函數(shù) mmap连霉,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- 在當(dāng)前進(jìn)程的虛擬地址空間中榴芳,尋找一段空閑的滿足要求的連續(xù)的虛擬地址
- 為此虛擬區(qū)分配一個(gè) vm_area_struct 結(jié)構(gòu),接著對(duì)這個(gè)結(jié)構(gòu)的各個(gè)域進(jìn)行了初始化
- 將新建的虛擬區(qū)結(jié)構(gòu)(vm_area_struct)插入進(jìn)程的虛擬地址區(qū)域鏈表或樹(shù)中
(二)調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù) mmap(不同于用戶空間函數(shù))跺撼,實(shí)現(xiàn)文件 物理地址和進(jìn)程虛擬地址的一一映射關(guān)系
- 為映射分配了新的虛擬地址區(qū)域后窟感,通過(guò)待映射的文件指針,在文件描述符表中找 到對(duì)應(yīng)的文件描述符歉井,通過(guò)文件描述符柿祈,鏈接到內(nèi)核“已打開(kāi)文件集”中該文件的文 件結(jié)構(gòu)體(struct file),每個(gè)文件結(jié)構(gòu)體維護(hù)著和這個(gè)已打開(kāi)文件相關(guān)各項(xiàng)信息哩至。
- 通過(guò)該文件的文件結(jié)構(gòu)體躏嚎,鏈接到 file_operations 模塊,調(diào)用內(nèi)核函數(shù) mmap菩貌,其原 型為:int mmap(struct file *filp, struct vm_area_struct *vma)卢佣,不同于用戶空間庫(kù)函數(shù)。
- 內(nèi)核 mmap 函數(shù)通過(guò)虛擬文件系統(tǒng) inode 模塊定位到文件磁盤物理地址箭阶。
- 通過(guò) remap_pfn_range 函數(shù)建立頁(yè)表虚茶,即實(shí)現(xiàn)了文件地址和虛擬地址區(qū)域的映射關(guān) 系。此時(shí)仇参,這片虛擬地址并沒(méi)有任何數(shù)據(jù)關(guān)聯(lián)到主存中
(三)進(jìn)程發(fā)起對(duì)這片映射空間的訪問(wèn)嘹叫,引發(fā)缺頁(yè)異常,實(shí)現(xiàn)文件內(nèi)容到物理 內(nèi)存(主存)的拷貝
注:前兩個(gè)階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射冈敛,但是并沒(méi)有將任何文件數(shù) 據(jù)的拷貝至主存。真正的文件讀取是當(dāng)進(jìn)程發(fā)起讀或?qū)懖僮鲿r(shí)鸣皂。
進(jìn)程的讀或?qū)懖僮髟L問(wèn)虛擬地址空間這一段映射地址抓谴,通過(guò)查詢頁(yè)表暮蹂,發(fā)現(xiàn)這一 段地址并不在物理頁(yè)面上。因?yàn)槟壳爸唤⒘说刂酚成浒┭梗嬲挠脖P數(shù)據(jù)還沒(méi)有 拷貝到內(nèi)存中仰泻,因此引發(fā)缺頁(yè)異常。
- 缺頁(yè)異常進(jìn)行一系列判斷滩届,確定無(wú)非法操作后集侯,內(nèi)核發(fā)起請(qǐng)求調(diào)頁(yè)過(guò)程。
- 調(diào)頁(yè)過(guò)程先在交換緩存空間(swap cache)中尋找需要訪問(wèn)的內(nèi)存頁(yè)帜消,如果沒(méi)有則 調(diào)用 nopage 函數(shù)把所缺的頁(yè)從磁盤裝入到主存中棠枉。
- 之后進(jìn)程即可對(duì)這片主存進(jìn)行讀或者寫(xiě)的操作,如果寫(xiě)操作改變了其內(nèi)容泡挺,一定時(shí) 間后系統(tǒng)會(huì)自動(dòng)回寫(xiě)臟頁(yè)面到對(duì)應(yīng)磁盤地址辈讶,也即完成了寫(xiě)入到文件的過(guò)程。
注:修改過(guò)的臟頁(yè)面并不會(huì)立即更新回文件中娄猫,而是有一段時(shí)間的延遲贱除,可以調(diào) 用 msync()來(lái)強(qiáng)制同步, 這樣所寫(xiě)的內(nèi)容就能立即保存到文件里了。
4.Binder 機(jī)制是如何跨進(jìn)程的
1.Binder 驅(qū)動(dòng)
- 在內(nèi)核空間創(chuàng)建一塊接收緩存區(qū)媳溺,
- 實(shí)現(xiàn)地址映射:將內(nèi)核緩存區(qū)月幌、接收進(jìn)程用戶空間映射到同一接收緩存區(qū)
2.發(fā)送進(jìn)程通過(guò)系統(tǒng)調(diào)用(copy_from_user)將數(shù)據(jù)發(fā)送到內(nèi)核緩存區(qū)。由于內(nèi) 核緩存區(qū)和接收進(jìn)程用戶空間存在映射關(guān)系悬蔽,故相當(dāng)于也發(fā)送了接收進(jìn)程的用戶 空間扯躺,實(shí)現(xiàn)了跨進(jìn)程通信。
5.說(shuō)說(shuō)四大組件的通信機(jī)制
1.activity
(1)一個(gè) Activity 通常就是一個(gè)單獨(dú)的屏幕(窗口)屯阀。
(2)Activity 之間通過(guò) Intent 進(jìn)行通信缅帘。
(3)android 應(yīng)用中每一個(gè) Activity 都必須要在 AndroidManifest.xml 配置文件中 聲明,否則系統(tǒng)將不識(shí)別也不執(zhí)行該 Activity难衰。
2.service
(1)service 用于在后臺(tái)完成用戶指定的操作钦无。service 分為兩種:
- started(啟動(dòng)):當(dāng)應(yīng)用程序組件(如 activity)調(diào)用 startService()方法啟動(dòng)服務(wù)時(shí), 服務(wù)處于 started 狀態(tài)盖袭。
- bound(綁定):當(dāng)應(yīng)用程序組件調(diào)用 bindService()方法綁定到服務(wù)時(shí)失暂,服務(wù)處于 bound 狀態(tài)
(2)startService()與 bindService()區(qū)別:
- started service(啟動(dòng)服務(wù))是由其他組件調(diào)用 startService()方法啟動(dòng)的,這導(dǎo)致服務(wù) 的 onStartCommand()方法被調(diào)用鳄虱。當(dāng)服務(wù)是 started 狀態(tài)時(shí)弟塞,其生命周期與啟動(dòng)它的 組件無(wú)關(guān),并且可以在后臺(tái)無(wú)限期運(yùn)行拙已,即使啟動(dòng)服務(wù)的組件已經(jīng)被銷毀决记。因此, 服務(wù)需要在完成任務(wù)后調(diào)用 stopSelf()方法停止倍踪,或者由其他組件調(diào)用 stopService() 方法停止系宫。
- 使用 bindService()方法啟用服務(wù)索昂,調(diào)用者與服務(wù)綁定在了一起,調(diào)用者一旦退出扩借,服 務(wù)也就終止椒惨,大有“不求同時(shí)生,必須同時(shí)死”的特點(diǎn)潮罪。
(3)開(kāi)發(fā)人員需要在應(yīng)用程序配置文件中聲明全部的 service康谆,使用 <service></service>標(biāo)簽。
(4)Service 通常位于后臺(tái)運(yùn)行嫉到,它一般不需要與用戶交互沃暗,因此 Service 組件沒(méi)有 圖形用戶界面。Service 組件需要繼承 Service 基類屯碴。Service 組件通常用于為其他 組件提供后臺(tái)服務(wù)或監(jiān)控其他組件的運(yùn)行狀態(tài)描睦。
3.content provider
(1)android 平臺(tái)提供了 Content Provider 使一個(gè)應(yīng)用程序的指定數(shù)據(jù)集提供給 其他應(yīng)用程序。其他應(yīng)用可以通過(guò) ContentResolver 類從該內(nèi)容提供者中獲取或 存入數(shù)據(jù)导而。
(2)只有需要在多個(gè)應(yīng)用程序間共享數(shù)據(jù)是才需要內(nèi)容提供者忱叭。例如,通訊錄 數(shù)據(jù)被多個(gè)應(yīng)用程序使用今艺,且必須存儲(chǔ)在一個(gè)內(nèi)容提供者中韵丑。它的好處是統(tǒng)一數(shù) 據(jù)訪問(wèn)方式。
(3)ContentProvider 實(shí)現(xiàn)數(shù)據(jù)共享虚缎。ContentProvider 用于保存和獲取數(shù)據(jù)撵彻,并 使其對(duì)所有應(yīng)用程序可見(jiàn)。這是不同應(yīng)用程序間共享數(shù)據(jù)的唯一方式实牡,因?yàn)?android 沒(méi)有提供所有應(yīng)用共同訪問(wèn)的公共存儲(chǔ)區(qū)陌僵。
(4)開(kāi)發(fā)人員不會(huì)直接使用 ContentProvider 類的對(duì)象,大多數(shù)是通過(guò) ContentResolver 對(duì)象實(shí)現(xiàn)對(duì) ContentProvider 的操作创坞。
(5)ContentProvider 使用 URI 來(lái)唯一標(biāo)識(shí)其數(shù)據(jù)集碗短,這里的 URI 以 content:// 作為前綴,表示該數(shù)據(jù)由 ContentProvider 來(lái)管理题涨。
4.broadcast receiver
(1)你的應(yīng)用可以使用它對(duì)外部事件進(jìn)行過(guò)濾偎谁,只對(duì)感興趣的外部事件(如當(dāng)電 話呼入時(shí),或者數(shù)據(jù)網(wǎng)絡(luò)可用時(shí))進(jìn)行接收并做出響應(yīng)纲堵。廣播接收器沒(méi)有用戶界 面巡雨。然而,它們可以啟動(dòng)一個(gè) activity 或 serice 來(lái)響應(yīng)它們收到的信息席函,或者用 NotificationManager 來(lái)通知用戶铐望。通知可以用很多種方式來(lái)吸引用戶的注意力, 例如閃動(dòng)背燈、震動(dòng)正蛙、播放聲音等炕舵。一般來(lái)說(shuō)是在狀態(tài)欄上放一個(gè)持久的圖標(biāo), 用戶可以打開(kāi)它并獲取消息跟畅。 (2)廣播接收者的注冊(cè)有兩種方法,分別是程序動(dòng)態(tài)注冊(cè)和 AndroidManifest 文 件中進(jìn)行靜態(tài)注冊(cè)溶推。
(3)動(dòng)態(tài)注冊(cè)廣播接收器特點(diǎn)是當(dāng)用來(lái)注冊(cè)的 Activity 關(guān)掉后徊件,廣播也就失效了。 靜態(tài)注冊(cè)無(wú)需擔(dān)憂廣播接收器是否被關(guān)閉蒜危,只要設(shè)備是開(kāi)啟狀態(tài)虱痕,廣播接收器也 是打開(kāi)著的。也就是說(shuō)哪怕 app 本身未啟動(dòng)辐赞,該 app 訂閱的廣播在觸發(fā)時(shí)也會(huì)對(duì) 它起作用部翘。
7.為什么 Intent 不能傳遞大數(shù)據(jù)
Intent 攜帶信息的大小其實(shí)是受 Binder 限制。數(shù)據(jù)以 Parcel 對(duì)象的形式存放在 Binder 傳遞緩存中响委。如果數(shù)據(jù)或返回值比傳遞 buffer 大新思,則此次傳遞調(diào)用失敗并 拋出 TransactionTooLargeException 異常。 Binder 傳遞緩存有一個(gè)限定大小赘风,通常是 1Mb夹囚。但同一個(gè)進(jìn)程中所有的傳輸共享 緩存空間。多個(gè)地方在進(jìn)行傳輸時(shí)邀窃,即時(shí)它們各自傳輸?shù)臄?shù)據(jù)不超出大小限制荸哟, TransactionTooLargeException 異常也可能會(huì)被拋出。在使用 Intent 傳遞數(shù)據(jù)時(shí)瞬捕, 1Mb 并不是安全上限鞍历。因?yàn)?Binder 中可能正在處理其它的傳輸工作。不同的機(jī) 型和系統(tǒng)版本肪虎,這個(gè)上限值也可能會(huì)不同劣砍。