前言
我們看過很多關(guān)于Binder的文章椅您,但是看完大多數(shù)文章后,都會有不知所云的感覺生闲,是因?yàn)槟切┪恼虏粔蚝脝嵯蹦纾坎皇悄切┪恼轮v得不夠好,我們看的不明白主要是存在兩種情況碍讯,一種深入代碼細(xì)節(jié)不能自拔悬蔽,從FrameWork到Kernel層,長篇累牘捉兴,讓人很難理解Binder蝎困;另一種是只講framework層,Binder驅(qū)動并沒有具體提到倍啥,導(dǎo)致我們會用Binder禾乘,也大致能說的出一些原理,可并沒有一個完整的深刻認(rèn)知虽缕。所以我不打算深入討論Binder的底層細(xì)節(jié)始藕,也不會貼出一大堆代碼,帶著大家一起看代碼彼宠,因?yàn)橛泻芏鄷臀恼略谶@方面做的已經(jīng)很細(xì)致了鳄虱,這篇文章是從自己的角度,解讀一下Binder機(jī)制凭峡,給大家一個簡單清晰的解釋拙已。
為什么使用Binder
Android使用的Linux內(nèi)核擁有著非常多的跨進(jìn)程通信機(jī)制,比如管道摧冀,System V(消息隊(duì)列倍踪、信號量和共享內(nèi)存),Socket等索昂;為什么還需要單獨(dú)搞一個Binder出來呢建车?主要有兩點(diǎn):
- 性能
- 安全
在移動設(shè)備上,廣泛的使用跨進(jìn)程通信肯定對通信機(jī)制本身提出了嚴(yán)格的要求椒惨;Binder相對傳統(tǒng)的Socket方式缤至,更加高效;另外康谆,傳統(tǒng)的進(jìn)程通信方式對于通信雙方的身份并沒有做出嚴(yán)格的驗(yàn)證领斥,只有在上層協(xié)議上進(jìn)行架設(shè)嫉到;碧柔Socket通信ip地址是客戶端手動填寫的,都可以進(jìn)行偽造月洛;而Binder機(jī)制從協(xié)議本身就支持對通信雙方做身份校驗(yàn)何恶,因而大大提升了安全性。這也是Android權(quán)限模型的基礎(chǔ)嚼黔。
什么是Binder
《Android開發(fā)藝術(shù)探索》一書中對Binder做了詳細(xì)的解釋:
Binder是Android中的一個類细层,它實(shí)現(xiàn)了IBinder接口。從IPC(Inter-Process Communication)角度來說唬涧,Binder是Android中一種跨進(jìn)程通信方法疫赎,Binder還可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動是/dev/binder爵卒,該通信方式在Linux中沒有虚缎;從AndroidFramework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager钓株、WindowManager实牡,等等)和相應(yīng)ManagerService的橋梁;從Android應(yīng)用層來說轴合,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介创坞,當(dāng)bindService的時候,服務(wù)端會返回一個包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象受葛,通過這個Binder對象题涨,客戶端可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)总滩。
上面的介紹纲堵,很多人看過之后,如果不理解闰渔,可能看過之后就會忘掉席函,我們暫時可以把Binder理解為Android中跨進(jìn)程通信的一種方式。接下來先要從幾個問題入手冈涧,來帶領(lǐng)大家去理解Binder機(jī)制:
- 既然是跨進(jìn)程茂附,那么Client端是如何準(zhǔn)確找到相對應(yīng)的Server端所在的進(jìn)程?
- 有一定計(jì)算機(jī)基礎(chǔ)的人都應(yīng)該明白督弓,兩個進(jìn)程間的內(nèi)存是不共享的彩倚,那么Binder又是如何將對象從一個進(jìn)程傳遞到另個一進(jìn)程呢钓试?
- 當(dāng)Client持有了遠(yuǎn)端進(jìn)程“對象”時,調(diào)用該對象具體函數(shù)心傀,Server進(jìn)程的對應(yīng)函數(shù)會得到執(zhí)行猜扮,這個也是解釋不通的。
- 第一個問題:在AIDL轉(zhuǎn)換成的java文件中,有一個常量DESCRIPTOR,這個常量是Binder的唯一標(biāo)識虱痕,一般用當(dāng)前的Binder的類名表示,通過這個常量就可以找到對應(yīng)的進(jìn)程辐赞。
- 第二個問題:對象是通過序列化在不同進(jìn)程間進(jìn)行通信的,只要對象實(shí)現(xiàn)了Parcelable接口硝训,就可以進(jìn)行序列化响委,Parcelable具體使用不在本文討論范圍。
- 第三個問題:通過第二個問題我們已經(jīng)了解到窖梁,對象需要進(jìn)行序列化赘风,才能在兩個進(jìn)程間進(jìn)行通信,那也就是說纵刘,兩個進(jìn)程拿到的對象并不是一個對象邀窃,回到第三個問題本身,要實(shí)現(xiàn)跨進(jìn)程的調(diào)用假哎,那么這兩個對象之間肯定是有聯(lián)系的瞬捕,通過什么進(jìn)行聯(lián)系呢,肯定是要通過Binder來進(jìn)行跨進(jìn)程間的調(diào)用舵抹。
首先我們看看我們的程序跨進(jìn)程調(diào)用系統(tǒng)服務(wù)的簡單示例肪虎,實(shí)現(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);
系統(tǒng)服務(wù)都是運(yùn)行在systemServer進(jìn)程中,因此我們調(diào)用系統(tǒng)服務(wù)都是跨進(jìn)程的調(diào)用惧蛹。第2行代碼中扇救,得到的wm是WindowManager對象的引用,第6行調(diào)用WindowManager的addView函數(shù)香嗓,將觸發(fā)遠(yuǎn)程調(diào)用迅腔,調(diào)用的是運(yùn)行在systemServer進(jìn)程中的WindowManager的addView函數(shù)。
從上面代碼可以看出來靠娱,創(chuàng)建一個Window是很簡單的事沧烈,只需要通過WindowManager即可完成,WindowManager是外界訪問Window的入口饱岸,但是具體的實(shí)現(xiàn)掺出,還是要通過WindowManagerService來實(shí)現(xiàn),WindowManager和WindowManagerService是一個掛進(jìn)程的交互苫费,是通過Binder實(shí)現(xiàn)的汤锨。
Binder機(jī)制
先看一下Binder的工作機(jī)制
當(dāng)客戶端發(fā)起遠(yuǎn)程請求時,由于當(dāng)前線程會被掛起直至服務(wù)端進(jìn)程返回數(shù)據(jù)百框,所以如果一個遠(yuǎn)程方法很耗時闲礼,那么就不能在UI線程中發(fā)起此遠(yuǎn)程請求,以防ANR;其次柬泽,由于服務(wù)端的Binder方法運(yùn)行在Binder的線程池中慎菲,所以Binder方法不管是否耗時都應(yīng)該采用同步的方法實(shí)現(xiàn),因?yàn)樗呀?jīng)運(yùn)行在一個線程中了锨并。
客戶端Client調(diào)用Service的函數(shù)Func露该,執(zhí)行的流程如下:
通過上面的兩張圖,應(yīng)該能夠清晰的了解到整個Binder的執(zhí)行過程了
Binder的架構(gòu)
上面一節(jié)我們對遠(yuǎn)程進(jìn)程調(diào)用代碼執(zhí)行過程有個初步了解第煮,在Android開發(fā)中解幼,我們大量使用到了系統(tǒng)Service,比如媒體播放包警、各種傳感器以及WindowManagerService等等撵摆。那么Android是怎么管理這些服務(wù),并且讓用戶跨進(jìn)程調(diào)用這些服務(wù)呢害晦?首先我們看看調(diào)用系統(tǒng)服務(wù)的過程特铝。在Android開機(jī)啟動過程中,Android會初始化系統(tǒng)的各種Service壹瘟,并將這些Service向ServiceManager注冊(即讓ServiceManager管理)鲫剿。客戶端想要得到具體的Service直接向ServiceManager要即可俐筋∏K兀客戶端首先向ServiceManager查詢得到具體的Service引用,然后通過這個引用向具體的服務(wù)端發(fā)送請求澄者,服務(wù)端執(zhí)行完成后就返回笆呆。
先看一下Client、Service粱挡、ServiceManager三者之間的關(guān)系:
當(dāng)Service創(chuàng)建的時候赠幕,首先在ServiceManager中注冊一下地址,Client在ServiceManager中查找對應(yīng)Service的地址询筏,然后根據(jù)地址進(jìn)行請求榕堰,Service做出響應(yīng)。
上圖簡單介紹了以下三者時間的關(guān)系嫌套,當(dāng)然真正的關(guān)系不是這么簡單的逆屡,涉及到Framework層、JNI層踱讨、Native層和Kernel層
從上圖中可以看到魏蔗,真正實(shí)現(xiàn)跨進(jìn)程通信是在Kernel層。我們平時說Binder相對于其他的進(jìn)程間通信方式的優(yōu)點(diǎn)是痹筛,在多進(jìn)程間傳遞數(shù)據(jù)時莺治,只會進(jìn)行一次數(shù)據(jù)拷貝廓鞠,這樣的說法是是有歧義的。在Kernel層這樣說是對的谣旁,但是從Framework到Kernel床佳,Kernel到Framework都會進(jìn)行內(nèi)存拷貝。也就是上圖的ioctl方法榄审。
Binder驅(qū)動實(shí)現(xiàn)原理
上述的第三個問題砌们,也就是Binder驅(qū)動實(shí)現(xiàn)的原理,只要能知道上述答案瘟判,那下面就不需要看了怨绣。重新回顧一下這個問題:客戶端持有遠(yuǎn)程進(jìn)程的某個對象引用,然后調(diào)用引用類中的函數(shù)拷获,遠(yuǎn)程進(jìn)程的函數(shù)就執(zhí)行了。學(xué)過操作系統(tǒng)的同學(xué)都知道减细,不同的進(jìn)程之間是不共享資源的匆瓜。也就是說,客戶端持有的這個對象跟遠(yuǎn)程進(jìn)程中的實(shí)際對象完全是兩個不同的對象未蝌⊥灾ǎ客戶端調(diào)用引用的對象跟遠(yuǎn)程進(jìn)程中的對象不是同一個,那問題就來了萧吠,為什么調(diào)用客戶端的這個遠(yuǎn)程對象的引用左冬,遠(yuǎn)端進(jìn)程相應(yīng)的方法就會被調(diào)用?先看下面這幅圖:
服務(wù)端跨進(jìn)程的類都要繼承Binder類纸型。我們所持有的Binder引用(即服務(wù)端的類引用)并不是實(shí)際真實(shí)的遠(yuǎn)程Binder對象拇砰,我們的引用在Binder驅(qū)動里還要做一次映射。也就是說狰腌,設(shè)備驅(qū)動根據(jù)我們的引用對象找到對應(yīng)的遠(yuǎn)程進(jìn)程除破。客戶端要調(diào)用遠(yuǎn)程對象函數(shù)時琼腔,只需把數(shù)據(jù)寫入到Parcel瑰枫,在調(diào)用所持有的Binder引用的transact()函數(shù),transact函數(shù)執(zhí)行過程中會把參數(shù)丹莲、標(biāo)識符(標(biāo)記遠(yuǎn)程對象及其函數(shù))等數(shù)據(jù)放入到Client的共享內(nèi)存光坝,Binder驅(qū)動從Client的共享內(nèi)存中讀取數(shù)據(jù),根據(jù)這些數(shù)據(jù)找到對應(yīng)的遠(yuǎn)程進(jìn)程的共享內(nèi)存甥材,把數(shù)據(jù)拷貝到遠(yuǎn)程進(jìn)程的共享內(nèi)存中盯另,并通知遠(yuǎn)程進(jìn)程執(zhí)行onTransact()函數(shù),這個函數(shù)也是屬于Binder類擂达。遠(yuǎn)程進(jìn)程Binder對象執(zhí)行完成后土铺,將得到的寫入自己的共享內(nèi)存中胶滋,Binder驅(qū)動再將遠(yuǎn)程進(jìn)程的共享內(nèi)存數(shù)據(jù)拷貝到客戶端的共享內(nèi)存,并喚醒客戶端線程悲敷。整個過程就完美解釋了兩個進(jìn)程間為什么能夠進(jìn)行通信究恤,所有的實(shí)現(xiàn)都是在Kernel層去實(shí)現(xiàn),屏蔽了細(xì)節(jié)后德,我們只需要實(shí)現(xiàn)Binder接口就可以輕松的實(shí)現(xiàn)跨進(jìn)程通信了部宿。
Binder機(jī)制運(yùn)用
好了,現(xiàn)在對Binder機(jī)制已經(jīng)理解了瓢湃,我們再看看Android是怎么運(yùn)用Binder的理张。再現(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);
這段代碼前面已經(jīng)出現(xiàn)過。getSystemService(getApplication().WINDOW_SERVICE);函數(shù)內(nèi)部原理就是向ServiceManager查詢標(biāo)識符為getApplication().WINDOW_SERVICE的遠(yuǎn)程對象的引用绵患。即WindowManager對象的引用雾叭,這個引用的真正實(shí)現(xiàn)是WindowManager的某個代理。得到這個引用后落蝙,在調(diào)用addView時织狐,真正的實(shí)現(xiàn)是在代理里面,代理把參數(shù)打包到Parcel對象中筏勒,然后調(diào)用transact函數(shù)(該函數(shù)繼承自Binder)移迫,再觸發(fā)Binder驅(qū)動的一系列調(diào)用過程,在Binder驅(qū)動實(shí)現(xiàn)原理一節(jié)中有具體介紹管行,忘記了的同學(xué)可以返回繼續(xù)看厨埋。關(guān)于Binder的代理對象,可以參考AIDL工具生成的代碼捐顷,這里不再具體介紹荡陷。