理解 Android 的 Binder 機(jī)制

可以說(shuō) Binder 是 Android 底層系統(tǒng)的一個(gè)特色了钓辆,它很好地解決了進(jìn)程間通訊的問(wèn)題跷究。其實(shí)網(wǎng)上有很多介紹 Binder 的文章搜锰,那么本文還是想將 Binder 這部分內(nèi)容細(xì)化一下维贺,更適合于初學(xué)者閱讀告唆。

Binder 產(chǎn)生的背景

首先我們說(shuō)說(shuō)為什么會(huì)出現(xiàn) Binder 這個(gè)東西域携。作為 iOS 開(kāi)發(fā)者簇秒,我還是情不自禁地想去談?wù)?iOS app,事實(shí)上秀鞭,iOS 的每一個(gè) app 都是一個(gè)獨(dú)立的進(jìn)程宰睡,它沒(méi)有 Android 那種比較開(kāi)放的多進(jìn)程通訊能力,甚至 App 與 Extension (如通知中心插件)之間都不能有一種非常直接的通訊方式气筋,當(dāng)然不是說(shuō) iOS 沒(méi)有 IPC 技術(shù)拆内,其實(shí) mach 內(nèi)核也是有著不錯(cuò)的 IPC 技術(shù)的,但這不是本文的重點(diǎn)麸恍。Android 則不太一樣,Android apps 基本上都需要各式各樣的 IPC 需求搀矫,甚至啟動(dòng)一個(gè) Activity 也需要用到 IPC抹沪,有一些 IPC 調(diào)用也許你并不知曉,可能對(duì)開(kāi)發(fā)者最可見(jiàn)的就是用 AIDL 去寫(xiě)一個(gè) Remote Service 接口了瓤球。

Android 很多核心功能都是由一系列 Services 支持的融欧,比如 ActivityService、WindowService 等等等等卦羡,應(yīng)用需要頻繁地與這些 Services 發(fā)生交互噪馏,正是基于這種場(chǎng)景,就亟需一種好的 IPC 解決方案绿饵。

你可能會(huì)想欠肾,為什么不是 Local Socket?或者 Shared Memory拟赊,那是因?yàn)榘踩詿o(wú)法得到保障刺桃。Android 的權(quán)限系統(tǒng)需要一種可靠的方式來(lái)保證各種 Services 的訪(fǎng)問(wèn)是在權(quán)限系統(tǒng)的監(jiān)控下進(jìn)行的,上述提到的解決方案就做不到了吸祟,因?yàn)椴还苁翘捉幼诌€是共享內(nèi)存瑟慈,現(xiàn)有的 Linux 內(nèi)核都不存在一種檢驗(yàn)雙方身份的方法存在桃移,任何通過(guò)套接字或者共享內(nèi)存走的數(shù)據(jù)都可以偽造,而在這個(gè)基礎(chǔ)上做任何驗(yàn)證葛碧,代價(jià)都是相當(dāng)高的借杰。Android 的選擇是基于內(nèi)核,重新開(kāi)發(fā)一套 IPC 機(jī)制吹埠,讓它固有這些特性第步,也就是讓系統(tǒng)可以在 Ring0 級(jí)保障交互雙方身份的正確性,并且這種基于內(nèi)核的方案效率還很高缘琅。

既然要基于內(nèi)核粘都,就一定要對(duì)內(nèi)核動(dòng)手腳,Android 采用驅(qū)動(dòng)的方式實(shí)現(xiàn)這個(gè)技術(shù)刷袍,而不是直接修改 Linux 內(nèi)核翩隧。這樣你就可以假設(shè),手機(jī)中有一個(gè)“設(shè)備”呻纹,應(yīng)用之間通過(guò)這個(gè)設(shè)備來(lái)交互堆生,而這個(gè)設(shè)備自身有一套身份校驗(yàn)機(jī)制,這樣就比基于用戶(hù)態(tài)的 IPC 方案來(lái)的安全得多雷酪,也快得多了淑仆。

Binder 是怎么工作的

我們暫且不需要深入理解 Binder 驅(qū)動(dòng)底層的實(shí)現(xiàn),也不需要知道 Binder 驅(qū)動(dòng)提供了什么接口哥力,我們就來(lái)看看一個(gè) app 是如何通過(guò) Binder 這個(gè)機(jī)制來(lái)實(shí)現(xiàn)跨進(jìn)程通信的蔗怠。

到這里,你可以把 Binder 驅(qū)動(dòng)看作一個(gè)機(jī)器吩跋,它連接著所有 Services(假設(shè) app 只調(diào)用 Services 提供的接口)寞射。

一個(gè) app(客戶(hù)端)想要找一個(gè) Service 辦點(diǎn)事,它就要去操作這個(gè)機(jī)器锌钮,而這個(gè)機(jī)器會(huì)檢測(cè) app 的身份桥温,如果身份合法,則可以繼續(xù)操作梁丘。假設(shè) app 要調(diào)用 A 服務(wù)的 foo 函數(shù)侵浸,那么它可以告訴這個(gè)機(jī)器,這個(gè)機(jī)器隨后就會(huì)檢查連在它身上的所有 Services兰吟,找到 app 需要的那一個(gè)通惫,讓它執(zhí)行 foo 函數(shù),并且再將返回值告訴 app混蔼,這樣 app 就成功隔著進(jìn)程操作到 A 服務(wù)了。

這當(dāng)然是很抽象的說(shuō)法珊燎,系統(tǒng)在 Framework 層做了很好地封裝惭嚣,讓我們可以友好地使用 Binder 驅(qū)動(dòng)來(lái)進(jìn)行 IPC 操作遵湖,下面我們就直接看應(yīng)用層所提供的接口是如何工作的。

探究與 Binder 相關(guān)的 Java 類(lèi)

要說(shuō)這些類(lèi)晚吞,我們首先要用到它們延旧,最簡(jiǎn)單的方式就是去創(chuàng)建一個(gè) Service,讓它運(yùn)行在單獨(dú)的進(jìn)程中槽地,然后編寫(xiě) AIDL迁沫,實(shí)現(xiàn)一個(gè)接口,再到 Activity 中去使用這個(gè)接口捌蚊。(注意:本文不介紹 Remote Service 與 AIDL 的相關(guān)知識(shí)集畅,如果讀者對(duì)這部分內(nèi)容還不夠了解,請(qǐng)先將它們弄懂再回來(lái)看本文)

AIDL 代碼生成器為我們創(chuàng)建了一個(gè) java 文件缅糟,這里面涉及到幾個(gè)類(lèi):


這些就是在應(yīng)用層使用 Binder 所需的所有類(lèi)了挺智,看類(lèi)圖有些錯(cuò)綜,但實(shí)際還是很簡(jiǎn)單的窗宦。

AIDL 生成的就是 IMyService 這個(gè)接口赦颇,而 StubProxy 則是這個(gè)接口的兩個(gè)內(nèi)部類(lèi),分別是 Binder 類(lèi)和 BinderProxy 類(lèi)的子類(lèi)(Proxy 類(lèi)雖然是用組合方式來(lái)持有 BinderProxy 的赴涵,但實(shí)際就是在直接用這個(gè)類(lèi)媒怯,只不過(guò)做了一層封裝,讓其更易使用而已)髓窜,StubProxy 都實(shí)現(xiàn)了 IMyService扇苞。

所以 IInterface 到底是什么,它就是一個(gè)用于表達(dá) Service 提供的功能的一個(gè)契約纱烘,也就是說(shuō) IInterface 里有的方法杨拐,Service 都能提供,調(diào)用者你別管用的是 BinderProxy 還是什么擂啥,只要拿到 IInterface哄陶,你就可以直接調(diào)用里面的方法,它就是一個(gè)接口哺壶。

同時(shí) Stub 雖然實(shí)現(xiàn)了 IMyService屋吨,但是并沒(méi)有實(shí)現(xiàn)厘米的任何方法,它是一個(gè)抽象類(lèi)山宾,開(kāi)發(fā)者需要自己子類(lèi)化 Stub 去實(shí)現(xiàn)具體的功能至扰。
Proxy 實(shí)現(xiàn)了 IMyService,并且實(shí)現(xiàn)了里面的方法资锰,這些方法的實(shí)現(xiàn)我們下面再說(shuō)敢课。

為什么 IMyService 要分 StubProxy 呢?這是為了要適用于本地調(diào)用和遠(yuǎn)程調(diào)用兩種情況。如果 Service 運(yùn)行在同一個(gè)進(jìn)程直秆,那就直接用 Stub濒募,因?yàn)樗苯訉?shí)現(xiàn)了 Service 提供的功能,不需要任何 IPC 過(guò)程圾结。如果 Service 運(yùn)行在其他進(jìn)程瑰剃,那客戶(hù)端使用的就是 Proxy,這里這個(gè) Proxy 的功能大家應(yīng)該能想到了吧筝野,就是把參數(shù)封裝后發(fā)送給 Binder 驅(qū)動(dòng)晌姚,然后執(zhí)行一系列 IPC 操作最后再取出結(jié)果返回。

好歇竟,到這里請(qǐng)求用 Proxy 發(fā)出去了挥唠,Service 怎么接受請(qǐng)求并作出響應(yīng)呢,這就需要 Stub 了途蒋,還記得我們的 Stub 是繼承自 Binder 的嗎猛遍?Binder 有一個(gè) onTransact 方法,而 Stub 重寫(xiě)了這個(gè)函數(shù)号坡,這個(gè)函數(shù)三個(gè)重要參數(shù):int code懊烤、Parcel dataParcel reply宽堆,分別對(duì)應(yīng)了被調(diào)函數(shù)編號(hào)腌紧、參數(shù)包、響應(yīng)包畜隶。當(dāng) Proxy 發(fā)起了一個(gè)請(qǐng)求壁肋,服務(wù)端中相應(yīng)的響應(yīng)線(xiàn)程就會(huì)通過(guò) JNI 調(diào)用到 Stub 類(lèi),然后執(zhí)行里面的 execTransact 方法籽慢,進(jìn)而轉(zhuǎn)到 onTransact 方法浸遗。(這一系列調(diào)用鏈大家可以從源碼中分析出來(lái),我這里作為拋磚引玉箱亿,就不帶大家分析 native 層的代碼了)

我們來(lái)看一眼這個(gè) onTransact

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code)    {
        case INTERFACE_TRANSACTION:
        {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_increaseCounter:
        {
            data.enforceInterface(DESCRIPTOR);
            int _arg0;
            _arg0 = data.readInt();
            int _result = this.increaseCounter(_arg0);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

是不是非常清晰易懂跛锌,就是根據(jù)傳過(guò)來(lái)的數(shù)據(jù)包來(lái)做相應(yīng)的操作,然后把結(jié)果寫(xiě)回?cái)?shù)據(jù)包届惋,Binder 驅(qū)動(dòng)會(huì)來(lái)幫我們做好這些數(shù)據(jù)包的分發(fā)工作髓帽。而這段代碼是運(yùn)行在 Service 本地進(jìn)程中的,它可以直接調(diào)用實(shí)現(xiàn)好的 Stub 類(lèi)中的相關(guān)方法(本例子中是 increaseCounter 方法)脑豹。

下面我們趁熱打鐵再來(lái)看一眼 Proxy 類(lèi)中的 increaseCounter 是怎么實(shí)現(xiàn)的:

@Override
public int increaseCounter(int increment) throws RemoteException{
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeInt(increment);
        mRemote.transact(Stub.TRANSACTION_increaseCounter, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readInt();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

正好與 Stub 中的處理能夠?qū)?yīng)起來(lái)郑藏,其實(shí)這兩段代碼就是整個(gè) IPC 的核心了,Binder 驅(qū)動(dòng)和 Binder 類(lèi)在底層幫我們做好了其他一切事情瘩欺。


休息一下必盖。

下面我們來(lái)思考另一件事情拌牲,如何判斷 Service 運(yùn)行在同一進(jìn)程還是不同進(jìn)程?

我們知道筑悴,Service 有一個(gè) onBind 方法们拙,這里面就返回了我們實(shí)現(xiàn)好的 Stub 類(lèi)稍途,而客戶(hù)端 bind service 時(shí)拿到的又是一個(gè) IBinder 對(duì)象阁吝,我們每次只需要調(diào)用 StubasInterface 靜態(tài)方法,把這個(gè) IBinder 對(duì)象傳進(jìn)去就能拿到 Stub 類(lèi)或者 Proxy 類(lèi)了械拍,看起來(lái)十分智能突勇!那么這個(gè) asInterface 又蘊(yùn)藏什么玄機(jī)呢?我們來(lái)看一眼實(shí)現(xiàn)代碼:

public static IBackgroundService asInterface(IBinder obj){
    if ((obj==null)) {
        return null;
    }
    IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof IBackgroundService))) {
        return ((IBackgroundService) iin);
    }
    return new IBackgroundService.Stub.Proxy(obj);
}

是不是有點(diǎn)莫名其妙坷虑,queryLocalInterface 是什么鬼甲馋?
我們?cè)賮?lái)看看 queryLocalInterface 的實(shí)現(xiàn):

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

它判斷了一下 descriptor 參數(shù)是否與自身 ownerdescriptor 一致,如果一致就直接返回 owner迄损,那么 ownerdescriptor 是在哪被設(shè)置的呢定躏,就是在 Stub 的構(gòu)造函數(shù)中被設(shè)置的。

于是乎芹敌,如果 Service 運(yùn)行在同一進(jìn)程痊远,那么客戶(hù)端拿到的 IBinder 就是 Stub 類(lèi),而 StubqueryLocalInterface 又會(huì)返回自己氏捞;而 Service 運(yùn)行在單獨(dú)進(jìn)程中時(shí)碧聪,客戶(hù)端拿到的 IBinder 就是系統(tǒng)提供好的 BinderProxyBinderProxy 中的 queryLocalInterface 默認(rèn)直接返回 null液茎,根據(jù)代碼逞姿,asInterface 就會(huì)構(gòu)造一個(gè) Proxy 返回給客戶(hù)端,那么接下來(lái)的故事就是上面我們講過(guò)的了捆等。

自己利用 Binder 來(lái)進(jìn)行 IPC

有了上面的基礎(chǔ)滞造,其實(shí)我們完全不需要 AIDL 了有木有,自己用 Binder 類(lèi)和 BinderProxy 類(lèi)就完全可以實(shí)現(xiàn) Service 與客戶(hù)端的通訊栋烤,下面我就速速寫(xiě)一個(gè)簡(jiǎn)單的例子谒养。

Service 中的 onBinder 方法我這樣實(shí)現(xiàn):


客戶(hù)端就這樣:


我們甚至不必按照 AIDL 的通信規(guī)范,自己處理 data 和 reply 也是完全可以的班缎,但這只是為了演示 Binder 的原理蝴光,日常開(kāi)發(fā)中還是不要這么做了吧。

小結(jié)

Binder 的設(shè)計(jì)非常優(yōu)秀达址,分析 Binder 的實(shí)現(xiàn)也是一件十分有趣的事情蔑祟,本文作為對(duì) Binder 深入研究的「引入」,站在了一個(gè)很高的角度俯視整套系統(tǒng)沉唠,并沒(méi)有對(duì)其中的細(xì)節(jié)做深入探討疆虚,如果大家對(duì)內(nèi)核開(kāi)發(fā)或者操作系統(tǒng)底層有興趣的話(huà),也可以去看看 Binder 驅(qū)動(dòng)的實(shí)現(xiàn),相信也會(huì)有不少收獲径簿,本文到這里就先結(jié)束了 ;-)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末罢屈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子篇亭,更是在濱河造成了極大的恐慌缠捌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件译蒂,死亡現(xiàn)場(chǎng)離奇詭異曼月,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)柔昼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)哑芹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人捕透,你說(shuō)我怎么就攤上這事聪姿。” “怎么了乙嘀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵末购,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我乒躺,道長(zhǎng)招盲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任嘉冒,我火速辦了婚禮曹货,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讳推。我一直安慰自己顶籽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布银觅。 她就那樣靜靜地躺著礼饱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪究驴。 梳的紋絲不亂的頭發(fā)上镊绪,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音洒忧,去河邊找鬼蝴韭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛熙侍,可吹牛的內(nèi)容都是我干的榄鉴。 我是一名探鬼主播履磨,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼庆尘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼驶忌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晤柄,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后售担,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體哭尝,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桶唐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滥玷。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖如贷,靈堂內(nèi)的尸體忽然破棺而出陷虎,到底是詐尸還是另有隱情,我是刑警寧澤杠袱,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布尚猿,位于F島的核電站,受9級(jí)特大地震影響楣富,放射性物質(zhì)發(fā)生泄漏凿掂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一纹蝴、第九天 我趴在偏房一處隱蔽的房頂上張望庄萎。 院中可真熱鬧,春花似錦骗灶、人聲如沸惨恭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脱羡。三九已至,卻和暖如春免都,著一層夾襖步出監(jiān)牢的瞬間锉罐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工绕娘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脓规,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓险领,卻偏偏與公主長(zhǎng)得像侨舆,于是被迫代替她去往敵國(guó)和親秒紧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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

  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要點(diǎn)...
    指尖流逝的青春閱讀 2,608評(píng)論 0 13
  • 毫不夸張地說(shuō)挨下,Binder是Android系統(tǒng)中最重要的特性之一熔恢;正如其名“粘合劑”所喻,它是系統(tǒng)間各個(gè)組件的橋梁...
    weishu閱讀 17,866評(píng)論 29 246
  • Android跨進(jìn)程通信IPC整體內(nèi)容如下 1臭笆、Android跨進(jìn)程通信IPC之1——Linux基礎(chǔ)2叙淌、Andro...
    隔壁老李頭閱讀 11,880評(píng)論 11 56
  • 親愛(ài)的爸爸: 您好嗎?又是一年2月1日愁铺,今天是您的祭日鹰霍,原諒我不能回家看您,如果世界上真的有天使茵乱,我想再一...
    憨包豬閱讀 431評(píng)論 1 0
  • 斜風(fēng)細(xì)雨黃花濃茂洒, 枯柳枝上月朦朧 飲酒高歌乘風(fēng)浪, ...
    丐少閱讀 210評(píng)論 0 1