Binder學(xué)習(xí)概要

很早就想寫一篇Binder的文章了火的,但是遲遲沒寫出來州袒,因?yàn)锽inder機(jī)制牽涉到的知識點(diǎn)太多了蹬音,有Java層的Binder坦报,也有底層的binder驅(qū)動库说。通常我們在Java層面做應(yīng)用開發(fā)牽涉到Binder的,有AIDL片择、Messenger等潜的。如何寫AIDL、Messenger相關(guān)的代碼并不難字管,難點(diǎn)在于理解Binder的原理啰挪。如果想要系統(tǒng)地學(xué)習(xí)Binder,推薦看《Android開發(fā)藝術(shù)探索》的相關(guān)章節(jié)以及老羅《Android進(jìn)程間通信(IPC)機(jī)制Binder簡要介紹和學(xué)習(xí)計(jì)劃》系列嘲叔,分別對應(yīng)的是Java層的Binder機(jī)制和底層的Binder機(jī)制亡呵。還有一些其它的優(yōu)秀的文章,文后會有推薦硫戈。

本文不打算深入地介紹Binder 機(jī)制的原理锰什,畢竟這不是一兩篇文章能介紹的清楚的,而且現(xiàn)在網(wǎng)上也有很多優(yōu)秀的文章丁逝。本文想介紹的是我個人通過對Binder地學(xué)習(xí)汁胆,對Binder機(jī)制和Android系統(tǒng)的一些理解。

限于水平果港,難免有誤沦泌,還請指正。

Binder基本介紹

Binder是Android系統(tǒng)運(yùn)行的一個重要基石辛掠,做Android開發(fā)的應(yīng)該沒有沒聽說過Binder的吧谢谦,那么Binder到底是個什么東西呢?

Binder萝衩,最簡單來說就是一個Java類回挽,全路徑名是 android.os.Binder ,一般我們看到包名是 android.os 的猩谊,就應(yīng)該想到它是系統(tǒng)運(yùn)行相關(guān)的類千劈。這個Binder類實(shí)現(xiàn)了IBinder接口,代表這個類的對象具有跨進(jìn)程通訊的能力牌捷。從IPC角度上講墙牌,Binder是Android中一種跨進(jìn)程通訊方式。Binder還可以理解為一個虛擬的物理設(shè)備暗甥,這是因?yàn)樗怯序?qū)動的喜滨,但是又不像一般的硬件那樣有物理實(shí)體,它的設(shè)備驅(qū)動是/dev/binder撤防,這個驅(qū)動是Android特有的虽风,Linux中沒有。從Framework層講,是ServiceManager與各種manager辜膝,比如ActivityManagerService无牵、WindowManagerService和各種ManagerService進(jìn)行通訊的橋梁;從應(yīng)用層來說厂抖,Binder是客戶端和服務(wù)端進(jìn)行通訊的媒介茎毁。

我覺得不管從什么角度來看Binder,Binder的作用就是用來進(jìn)行跨進(jìn)程通訊 (IPC) 的验游。這里充岛,我們需要簡單介紹下IPC保檐,以便于更好的理解Binder的作用耕蝉。

IPC方式

任何一個系統(tǒng)都需要有相應(yīng)的IPC機(jī)制,Linux上面可以通過管道夜只、System V IPC垒在,即消息隊(duì)列/共享內(nèi)存/信號量,或者Socket的方式來進(jìn)行跨進(jìn)程通訊扔亥,Android是基于Linux的场躯,也就是說Android也可以使用這些IPC方式,那么Android系統(tǒng)為什么還要再引入Binder這種IPC方式呢旅挤?這個問題踢关,我覺得可以從三個方面來回答:使用方便、傳輸性能粘茄、安全签舞。

  • 使用方便: Binder是基于C/S架構(gòu)的,利用了面向?qū)ο蟮乃枷肫獍辏瑢τ陂_發(fā)者使用來說儒搭,很方便。從這個角度來說芙贫,共享內(nèi)存這種方式就不適合搂鲫,因?yàn)樗褂锰珡?fù)雜。
  • 傳輸性能:Android畢竟是移動設(shè)備磺平,移動設(shè)備就要考慮內(nèi)存占用和耗電量的問題魂仍,Binder只需要拷貝內(nèi)存1次,而管道拣挪、消息隊(duì)列擦酌、Socket都需要對數(shù)據(jù)拷貝2次。
  • 安全:傳統(tǒng)IPC沒有任何安全措施媒吗,完全依賴上層協(xié)議來確保仑氛。傳統(tǒng)IPC訪問接入點(diǎn)是開放的,無法建立私有通道。比如命名管道的名稱锯岖,system V的鍵值介袜,socket的ip地址或文件名都是開放的,只要知道這些接入點(diǎn)的程序都可以和對端建立連接出吹,不管怎樣都無法阻止惡意程序通過猜測接收方地址獲得連接遇伞。
IPC 數(shù)據(jù)拷貝次數(shù)
共享內(nèi)存 0
Binder 1
Socket/管道/消息隊(duì)列 2

基于以上這些原因,Android引入了Binder這種IPC方式捶牢,基于C/S架構(gòu)鸠珠,傳輸過程只需要1次拷貝,為發(fā)送方添加UID/PID身份驗(yàn)證秋麸,既支持實(shí)名Binder也支持匿名Binder渐排,安全性高。

Binder中的4種角色

  • Client:客戶端灸蟆,使用服務(wù)的一端
  • Server:服務(wù)端驯耻,提供服務(wù)的一端
  • ServerManager:ServiceManager的作用是將字符形式的Binder名字轉(zhuǎn)化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實(shí)體的引用炒考。其實(shí)ServiceManager端也是一個Server端可缚,也有自己的Binder實(shí)體,對于ServerManager端來說斋枢,其它端都是client端帘靡。它是一個守護(hù)進(jìn)程,用來管理Server瓤帚,并向Client提供查詢Server接口的能力描姚。
  • Binder驅(qū)動:驅(qū)動負(fù)責(zé)進(jìn)程之間Binder通信的建立,Binder在進(jìn)程之間的傳遞缘滥,Binder引用計(jì)數(shù)管理轰胁,數(shù)據(jù)包在進(jìn)程之間的傳遞和交互等一系列底層支持。
圖片摘自老羅的Binder簡要介紹和學(xué)習(xí)計(jì)劃.png

Client朝扼、Server和Service Manager實(shí)現(xiàn)在用戶空間中赃阀,Binder驅(qū)動程序?qū)崿F(xiàn)在內(nèi)核空間中。 Client和Server之間的進(jìn)程間通信通過Binder驅(qū)動程序間接實(shí)現(xiàn)擎颖。Binder驅(qū)動程序和Service Manager在Android平臺中已經(jīng)實(shí)現(xiàn)榛斯,對于我們應(yīng)用開發(fā)者來說,只需要實(shí)現(xiàn)自己的Client和Server就行搂捧,常見的就是使用AIDL的方式來實(shí)現(xiàn)IPC驮俗。

應(yīng)用開發(fā)中的Binder使用

Binder的那些概念介紹是挺枯燥乏味的,在Android開發(fā)中我們無時不刻不在使用Binder允跑,那么我們就結(jié)合平時的開發(fā)來了解一些使用的Binder機(jī)制的地方王凑。

1 Activity啟動

Activity的啟動需要用到ActivityManagerService搪柑,但是我們的App進(jìn)程和ActivityManagerService所在的進(jìn)程不是同一個進(jìn)程,所以就需要用到進(jìn)程間通訊了索烹。在App進(jìn)程中我們拿到的是ActivityManagerService的一個分身工碾,也就是ActivityManagerProxy,這個ActivityManagerProxy與ActivityManagerService都實(shí)現(xiàn)了IActivityManager接口百姓,因此它們具有相同的功能渊额,但是ActivityManagerProxy只是做了一個中轉(zhuǎn),創(chuàng)建兩個Parcel對象垒拢,一個用于攜帶請求的參數(shù)旬迹,一個用于拿到請求結(jié)果,然后調(diào)用transact方法求类,通過Binder驅(qū)動奔垦,ActivityManagerService的onTransact方法會被調(diào)用,然后根據(jù)相應(yīng)的code仑嗅,調(diào)用相應(yīng)的方法宴倍,并把處理結(jié)果返回张症。

在這個過程中仓技,我們的App進(jìn)程就是Client,ActivityManagerService所在的進(jìn)程是Service俗他。

但是Activity的啟動過程還沒有完脖捻,ActivityManagerService還會調(diào)用我們App所在進(jìn)程的ApplicationThread來最終完成Activity的啟動,其實(shí)ActivityManagerService拿到的也是ApplicationThread的一個分身ApplicationThreadProxy兆衅,通過這個分身地沮,ApplicationThread相應(yīng)的方法會被調(diào)用。

在這個過程中羡亩,我們的App端是Server摩疑,ActivityManagerService所在的進(jìn)程是Client。

還有一個問題我們要注意畏铆,ActivityThread有一個內(nèi)部類H(一個Hander)雷袋,ApplicationThread方法內(nèi)部都會通過這個Handler來發(fā)送消息,最終調(diào)用到ActivityThread的方法辞居。為什么要這么做呢楷怒?

在分析源碼的過程中,很長一段時間瓦灶,這個問題都困擾著我鸠删,直到有一天對Binder的理解加深了,我才明白:Binder服務(wù)端的方法都是運(yùn)行在Binder線程池的一個線程中的贼陶,所以要通過Hander刃泡,把方法的調(diào)用切換到主線程中來巧娱。

2 Intent攜帶數(shù)據(jù)

我們都知道Intent可以傳遞的數(shù)據(jù)包含:基本類型、String烘贴、實(shí)現(xiàn)了Serializable接口或者Parcelable接口的類以及對應(yīng)的數(shù)組或者集合類家卖。其實(shí)Intent中的數(shù)據(jù)都是通過Bundle來攜帶的,那么我們就要有個疑問了庙楚,為什么限定只能是這些類型的數(shù)據(jù)上荡,而不是任意的數(shù)據(jù)類型呢?

歸根結(jié)底馒闷,限制這些類型的是Parcel這個類酪捡。如果我們查看源碼的話就會看到,Bundle其實(shí)也是用到了Parcel這個類纳账。

Parcel 逛薇,“包裹的意思”,它的作用就是為了在IPC過程中存放數(shù)據(jù)疏虫。我們要知道一點(diǎn)永罚,進(jìn)程間傳遞數(shù)據(jù),實(shí)際上就是二進(jìn)制數(shù)據(jù)卧秘,所以對于非基本類型呢袱,必然存在著序列化和反序列過程,這也是為什么要求Intent傳遞的非基本類型數(shù)據(jù)必須實(shí)現(xiàn)Serializable或者Parcelable接口的原因翅敌。

至于Parcel在IPC過程中使用到的地方羞福,我們可以看一段代碼,這個是我仿造著AIDL生成的文件蚯涮,自己手寫的一個Binder服務(wù)端治专。看一下Proxy類的add方法遭顶,實(shí)際上就是先創(chuàng)建兩個Parcel對象张峰,一個通過調(diào)用 writexxx 方法用于存放請求數(shù)據(jù),一個是通過調(diào)用 readxxx 方法獲取結(jié)果棒旗。Proxy真正干的就是這些喘批,真正計(jì)算的還是服務(wù)端Stub的實(shí)現(xiàn)類。當(dāng)Proxy調(diào)用 mRemote.transact(TRANSACTION_add, _data, _reply, 0); 方法后嗦哆,Stub的onTransact方法會被調(diào)用谤祖,進(jìn)而調(diào)用真正的add方法。

在這里老速,我們就可以看到Parcel的一系列 writexxx粥喜、readxxx方法的作用。

public interface ICalculateInterface extends IInterface {

    public abstract class Stub extends Binder implements ICalculateInterface {
        private static final String DESCRIPTOR = "com.sososeen09.knowledge.ipc.handipc.ICalculateInterface";

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

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

        public static ICalculateInterface asInterface(IBinder obj) {
            if (obj == null) {
                return null;
            }

            IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);

            if (iInterface != null && iInterface instanceof ICalculateInterface) {
                return (ICalculateInterface) iInterface;
            } else {
                return new Proxy(obj);
            }
        }

        @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 TRANSACTION_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0, _arg1, _result;
                    _arg0 = data.readInt();
                    _arg1 = data.readInt();
                    _result = this.add(_arg0, _arg1);

                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements ICalculateInterface {

            private IBinder mRemote;

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

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                int result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);

                    mRemote.transact(TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();

                    result = _reply.readInt();
                } finally {
                    _data.recycle();
                    _reply.recycle();
                }
                return result;
            }

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

    static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0);
    int add(int a, int b) throws RemoteException;
}

關(guān)于Binder的介紹和Java層的使用橘券,先介紹到這里额湘,這些內(nèi)容會持續(xù)更新卿吐。

Binder使用的一些注意事項(xiàng)

  • Binder方法是在Binder線程池中被調(diào)用的,所以不需要再次new一個線程了锋华,Client調(diào)用Server端方法嗡官,當(dāng)前線程會被調(diào)起,太耗時的話記得用一個線程來調(diào)用毯焕。
  • Intent攜帶的數(shù)據(jù)大小是限制了衍腥,不要超過1M,否則就會報(bào)一個TransactionTooLargeException的異常纳猫。這是因?yàn)锽inder數(shù)據(jù)的緩存大小就是1M婆咸。有的時候,即使一次攜帶的數(shù)據(jù)不到1M芜辕,還是可能會報(bào)異常尚骄,因?yàn)榇嬖诓l(fā)的情況。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侵续,一起剝皮案震驚了整個濱河市倔丈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌状蜗,老刑警劉巖需五,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诗舰,居然都是意外死亡警儒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門眶根,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人边琉,你說我怎么就攤上這事属百。” “怎么了变姨?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵族扰,是天一觀的道長。 經(jīng)常有香客問我定欧,道長渔呵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任砍鸠,我火速辦了婚禮扩氢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘爷辱。我一直安慰自己录豺,他們只是感情好朦肘,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著双饥,像睡著了一般媒抠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咏花,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天趴生,我揣著相機(jī)與錄音,去河邊找鬼昏翰。 笑死冲秽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矩父。 我是一名探鬼主播锉桑,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窍株!你這毒婦竟也來了民轴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤球订,失蹤者是張志新(化名)和其女友劉穎后裸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冒滩,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡微驶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了开睡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片因苹。...
    茶點(diǎn)故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖篇恒,靈堂內(nèi)的尸體忽然破棺而出扶檐,到底是詐尸還是另有隱情,我是刑警寧澤胁艰,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布款筑,位于F島的核電站,受9級特大地震影響腾么,放射性物質(zhì)發(fā)生泄漏奈梳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一解虱、第九天 我趴在偏房一處隱蔽的房頂上張望攘须。 院中可真熱鬧,春花似錦饭寺、人聲如沸阻课。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽限煞。三九已至抹恳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間署驻,已是汗流浹背奋献。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旺上,地道東北人瓶蚂。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像宣吱,于是被迫代替她去往敵國和親窃这。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評論 2 359

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