3分鐘帶你看懂a(chǎn)ndroid中的Binder機制

一.引言

最近一段時間由于工作扇售,接觸到framework部分比較多一點,也難免要和Binder打一些交道挪蹭,也整理了一些相關(guān)知識,但準備寫這篇文章時休偶,還是有些慌梁厉。而且關(guān)于整個Binder機制的復雜程度不是三言兩語能描敘清楚的,也害怕自己的理解有些偏差踏兜,誤導一些朋友(ps:反正也沒人看....扎心)所以也參考了很多資料词顾。

本文主要站在Android開發(fā)的角度來大致解析下Binder在java層的一些知識原理,給大家腦子形成一個完整的概念碱妆,比如AIDL的實現(xiàn)原理肉盹,Binder是怎么通信的等等,文章文字較多疹尾,請耐心觀看

二.Binder概述

2.1 Android為什么選擇Binder

Android是基于Linux內(nèi)核的垮媒,所以Android要實現(xiàn)進程間的通信,其實大可使用linux原有的一些手段航棱,比如管道睡雇,共享內(nèi)存,socket等方式饮醇,但是Android還是采用了Binder作為主要機制它抱,說明Binder具有無可比擬的優(yōu)勢。

其實進程通信大概就兩個方面因素朴艰,一者性能方面观蓄,傳輸效率問題,傳統(tǒng)的管道隊列模式采用內(nèi)存緩沖區(qū)的方式祠墅,數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開辟的緩存區(qū)中侮穿,然后再從內(nèi)核緩存區(qū)拷貝到接收方緩存區(qū),至少有兩次拷貝過程毁嗦,而socket都知道傳輸效率低亲茅,開銷大,用于跨網(wǎng)絡(luò)進程交互比較多狗准,共享內(nèi)存雖然無需拷貝克锣。

二者這是安全問題,Android作為一個開放式腔长,擁有眾多開發(fā)者的的平臺袭祟,應用程序的來源廣泛,確保終端安全是非常重要的捞附,傳統(tǒng)的IPC通信方式?jīng)]有任何措施巾乳,基本依靠上層協(xié)議您没,其一無法確認對方可靠的身份,Android為每個安裝好的應用程序分配了自己的UID胆绊,故進程的UID是鑒別進程身份的重要標志紊婉,傳統(tǒng)的IPC要發(fā)送類似的UID也只能放在數(shù)據(jù)包里,但也容易被攔截辑舷,惡意進攻,socket則需要暴露自己的ip和端口槽片,知道這些惡意程序則可以進行任意接入何缓。

綜上所述,Android需要一種高效率还栓,安全性高的進程通信方式碌廓,也就是Binder,Binder只需要一次拷貝剩盒,性能僅次于共享內(nèi)存谷婆,而且采用的傳統(tǒng)的C/S結(jié)構(gòu),穩(wěn)定性也是沒得說辽聊,發(fā)送添加UID/PID纪挎,安全性高。

2.2 Binder實現(xiàn)機制

2.2.1進程隔離

我們知道進程之間是無法直接進行交互的跟匆,每個進程獨享自己的數(shù)據(jù)异袄,而且操作系統(tǒng)為了保證自身的安全穩(wěn)定性,將系統(tǒng)內(nèi)核空間和用戶空間分離開來玛臂,保證用戶程序進程崩潰時不會影響到整個系統(tǒng)烤蜕,簡單的說就是,內(nèi)核空間(Kernel)是系統(tǒng)內(nèi)核運行的空間迹冤,用戶空間(UserSpace)是用戶程序運行的空間讽营。為了保證安全性,它們之間是隔離的泡徙,所以用戶空間的進程要進行交互需要通過內(nèi)核空間來驅(qū)動整個過程橱鹏。

2.2.2 C/S結(jié)構(gòu)

Binder是基于C/S機制的,要實現(xiàn)這樣的機制堪藐,server必須需要有特定的節(jié)點來接受到client的請求蚀瘸,也就是入口地址,像輸入一個網(wǎng)址庶橱,通過DNS解析出對應的ip贮勃,然后進行訪問,這個就是server提供出來的節(jié)點地址苏章,而Binder而言的話寂嘉,與傳統(tǒng)的C/S不太一樣奏瞬,Binder本身來作為Server中提供的節(jié)點,client拿到Binder實體對象對應的地址去訪問Server泉孩,對于client而言硼端,怎么拿到這個地址并建立起整個通道是整個交互的關(guān)鍵所在,而且Binder作為一個Server中的實體寓搬,對象提供一系列的方法來實現(xiàn)服務(wù)端和客戶端之間的請求珍昨,只要client拿到這個引用就可以或者一個有著該方法代理對象的引用,就可以進行通信了句喷。

引用Android Bander設(shè)計與實現(xiàn) - 設(shè)計篇一段話來總結(jié):

面向?qū)ο笏枷氲囊雽⑦M程間通信轉(zhuǎn)化為通過對某個Binder對象的引用調(diào)用該對象的方法镣典,而其獨特之處在于Binder對象是一個可以跨進程引用的對象,它的實體位于一個進程中唾琼,而它的引用卻遍布于系統(tǒng)的各個進程之中兄春。最誘人的是,這個引用和java里引用一樣既可以是強類型锡溯,也可以是弱類型赶舆,而且可以從一個進程傳給其它進程,讓大家都能訪問同一Server祭饭,就象將一個對象或引用賦值給另一個引用一樣芜茵。Binder模糊了進程邊界,淡化了進程間通信過程倡蝙,整個系統(tǒng)仿佛運行于同一個面向?qū)ο蟮某绦蛑邢οP涡紊腂inder對象以及星羅棋布的引用仿佛粘接各個應用程序的膠水,這也是Binder在英文里的原意悠咱。

2.2.3 Binder通信模型

Binder基于C/S的結(jié)構(gòu)下蒸辆,定義了4個角色:Server、Client析既、ServerManager躬贡、Binder驅(qū)動,其中前三者是在用戶空間的眼坏,也就是彼此之間無法直接進行交互拂玻,Binder驅(qū)動是屬于內(nèi)核空間的,屬于整個通信的核心宰译,雖然叫驅(qū)動檐蚜,但是實際上和硬件沒有太大關(guān)系,只是實現(xiàn)的方式和驅(qū)動差不多沿侈,驅(qū)動負責進程之間Binder通信的建立闯第,Binder在進程之間的傳遞,Binder引用計數(shù)管理缀拭,數(shù)據(jù)包在進程之間的傳遞和交互等一系列底層支持咳短。

ServerManager的作用填帽?

我們知道ServerManager也是屬于用戶空間的一個進程,主要作用就是作為Server和client的橋梁咙好,client可以ServerManager拿到Server中Binder實體的引用篡腌,這么說可能有點模糊,舉個簡單的例子勾效,我們訪問嘹悼,www.baidu.com,百度首頁頁面就顯示出來了层宫,首先我們知道杨伙,這個頁面肯定是發(fā)布在百度某個服務(wù)器上的,DNS通過你這個地址卒密,解析出對應的ip地址,再去訪問對應的頁面棠赛,然后再把數(shù)據(jù)返回給客戶端哮奇,完成交互。這個和Binder的C/S非常類似睛约,這里的DNS就是對應的ServerManager鼎俘,首先,Server中的Binder實體對象辩涝,將自己的引用(也就是ip地址)注冊到ServerManager贸伐,client通過特定的key(也就是百度這個網(wǎng)址)和這個引用進行綁定,ServerManager內(nèi)部自己維護一個類似MAP的表來一一對應怔揩,通過這個key就可以向ServerManager拿到Server中Binder的引用捉邢,對應到Android開發(fā)中,我們知道很多系統(tǒng)服務(wù)都是通過Binder去和AMS進行交互的商膊,比如獲取音量服務(wù):

AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

細心的朋友應該發(fā)現(xiàn)ServerManager和Server也是兩個不同的進程呀伏伐,Server要向ServerManager去注冊不是也要涉及到進程間的通信嗎,當前實現(xiàn)進程間通信又要用到進程間的通信晕拆,你這不是扯犢子嗎....莫急莫急藐翎,Binder的巧妙之處在于,當ServerManager作為Serve端的時候实幕,它提供的Binder比較特殊吝镣,它沒有名字也不需要注冊,當一個進程使用BINDER_SET_CONTEXT_MGR命令將自己注冊成SMgr時Binder驅(qū)動會自動為它創(chuàng)建Binder實體昆庇,這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得末贾。也就是說,一個Server若要向ServerManager注冊自己Binder就必需通過0這個引用號和ServerManager的Binder通信,有朋友又要問了整吆,server和client屬于兩個不同的進程未舟,client怎么能拿到server中對象圈暗,不妨先看看下面的交互圖

image

從上圖很清晰的可以看出來整個的交互過程,原本從SM中拿到binder的引用裕膀,通過Binder驅(qū)動層的處理之后员串,返回給了client一個代理對象,實際上如果client和server處于同一個進程昼扛,返回的就是當前binder對象寸齐,如果client和server不處于同一個進程,返回給client的就是一個代理對象抄谐,這一點渺鹦,有興趣可以看下源碼。

2.2.4 Binder角色的定位

Binder本質(zhì)上只是提供了一種通信的方式蛹含,和我們具體要實現(xiàn)的內(nèi)容沒有關(guān)系毅厚,為了實現(xiàn)這個服務(wù),我們需要定義一些接口浦箱,讓client能夠遠程調(diào)用服務(wù)吸耿,因為是跨進程,這時候就要設(shè)計到代理模式酷窥,以接口函數(shù)位基準咽安,client和server去實現(xiàn)接口函數(shù),Server是服務(wù)真正的實現(xiàn)蓬推,client作為一個遠程的調(diào)用妆棒。

  • 從Server進程來看,Binder是存在的實體對象沸伏,client通過transact()函數(shù)糕珊,經(jīng)過Binder驅(qū)動,最終回調(diào)到Binder實體的onTransact()函數(shù)中毅糟。
  • 從 Client進程的角度看放接,Binder 指的是對 Binder 代理對象,是 Binder 實體對象的一個遠程代理留特,通過Binder驅(qū)動進行交互

3.手寫進程通信

上面說了那么多纠脾,大家伙也看累了,下面通過代碼的形式讓大家對Binder加深點理解蜕青,日常開發(fā)中苟蹈,涉及到進程間通信的話,我們首先想到的可能就是AIDL右核,但不知道有沒有和我感覺一樣的朋友慧脱。。第一次寫AIDL是蒙蔽的贺喝,通過.aidl文件菱鸥,編譯器自動生成代碼宗兼,生成一個java文件,里面又有Stub類氮采,里面還有Proxy類殷绍,完全不理解里面的機制,確實不便于我們理解學習鹊漠,為了加深理解主到,我們拋棄aidl,手寫一個通信代碼躯概。

首先我們要定義一個接口服務(wù)登钥,也就是上述的服務(wù)端要具備的能力來提供給客戶端,定義一個接口繼承IInterface,代表了服務(wù)端的能力

public interface PersonManger extends IInterface {
    void addPerson(Person mPerson);
    List<Person> getPersonList();
}

接下來我們就要定義一個Server中的Binder實體對象了娶靡,首先肯定要繼承Binder牧牢,其次需要實現(xiàn)上面定義好的服務(wù)接口

public abstract class BinderObj extends Binder implements PersonManger {
    public static final String DESCRIPTOR = "com.example.taolin.hellobinder";
    public static final int TRANSAVTION_getPerson = IBinder.FIRST_CALL_TRANSACTION;
    public static final int TRANSAVTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1;
    public static PersonManger asInterface(IBinder mIBinder){
        IInterface iInterface = mIBinder.queryLocalInterface(DESCRIPTOR);
        if (null!=iInterface&&iInterface instanceof PersonManger){
            return (PersonManger)iInterface;
        }
        return new Proxy(mIBinder);
    }
    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        switch (code){
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;

            case TRANSAVTION_getPerson:
                data.enforceInterface(DESCRIPTOR);
                List<Person> result = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;

            case TRANSAVTION_addPerson:
                data.enforceInterface(DESCRIPTOR);
                Person arg0 = null;
                if (data.readInt() != 0) {
                    arg0 = Person.CREATOR.createFromParcel(data);
                }
                this.addPerson(arg0);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);

    }

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

首先我們看asInterface方法,Binder驅(qū)動傳來的IBinder對象姿锭,通過queryLocalInterface方法塔鳍,查找本地Binder對象,如果返回的就是PersonManger艾凯,說明client和server處于同一個進程献幔,直接返回懂傀,如果不是趾诗,返回給一個代理對象。

當然作為代理對象蹬蚁,也是需要實現(xiàn)服務(wù)接口

public class Proxy implements PersonManger {
    private IBinder mIBinder;
    public Proxy(IBinder mIBinder) {
        this.mIBinder =mIBinder;
    }

    @Override
    public void addPerson(Person mPerson) {
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (mPerson != null) {
                data.writeInt(1);
                mPerson.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            mIBinder.transact(BinderObj.TRANSAVTION_addPerson, data, replay, 0);
            replay.readException();
        } catch (RemoteException e){
            e.printStackTrace();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    @Override
    public List<Person> getPersonList() {
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        List<Person> result = null;
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            mIBinder.transact(BinderObj.TRANSAVTION_getPerson, data, replay, 0);
            replay.readException();
            result = replay.createTypedArrayList(Person.CREATOR);
        }catch (RemoteException e){
            e.printStackTrace();
        } finally{
            replay.recycle();
            data.recycle();
        }
        return result;
    }

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

這里的代理對象實質(zhì)就是client最終拿到的代理服務(wù)恃泪,通過這個就可以和Server進行通信了,首先通過Parcel將數(shù)據(jù)序列化犀斋,然后調(diào)用 remote.transact()將方法code贝乎,和data傳輸過去,對應的會回調(diào)在在Server中的onTransact()中

然后是我們的Server進程,onBind方法返回mStub對象叽粹,也就是Server中的Binder實體對象

public class ServerSevice extends Service {
    private static final String TAG = "ServerSevice";
    private List<Person> mPeople = new ArrayList<>();


    @Override
    public void onCreate() {
        mPeople.add(new Person());
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }
    private BinderObj mStub = new BinderObj() {
        @Override
        public void addPerson(Person mPerson) {
            if (mPerson==null){
                mPerson = new Person();
                Log.e(TAG,"null obj");
            }
            mPeople.add(mPerson);
            Log.e(TAG,mPeople.size()+"");
        }

        @Override
        public List<Person> getPersonList() {
            return mPeople;
        }
    };
}

最終我們在客戶端進程览效,bindService傳入一個ServiceConnection對象,在與服務(wù)端建立連接時虫几,通過我們定義好的BinderObj的asInterface方法返回一個代理對象锤灿,再調(diào)用方法進行交互

public class MainActivity extends AppCompatActivity {
    private boolean isConnect = false;
    private static final String TAG = "MainActivity";
    private PersonManger personManger;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
        findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (personManger==null){
                    Log.e(TAG,"connect error");
                    return;
                }
                personManger.addPerson(new Person());
                Log.e(TAG,personManger.getPersonList().size()+"");
            }
        });
    }

    private void start() {
        Intent intent = new Intent(this, ServerSevice.class);
        bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
    }
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG,"connect success");
            isConnect = true;
            personManger = BinderObj.asInterface(service);
            List<Person> personList = personManger.getPersonList();
            Log.e(TAG,personList.size()+"");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG,"connect failed");
            isConnect = false;
        }
    };
}

這樣的話,一次完成的進程間的交互就完成了~是不是感覺沒有想象中那么難辆脸,最后建議大家在不借助 AIDL 的情況下手寫實現(xiàn) Client 和 Server 進程的通信但校,加深對 Binder 通信過程的理解。

本文在寫作過程中參考了蠻多的文章和源碼啡氢,感謝大佬們的無私奉獻状囱,溜了溜了~

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末术裸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亭枷,更是在濱河造成了極大的恐慌袭艺,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奶栖,死亡現(xiàn)場離奇詭異匹表,居然都是意外死亡,警方通過查閱死者的電腦和手機宣鄙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門袍镀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冻晤,你說我怎么就攤上這事苇羡。” “怎么了鼻弧?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵设江,是天一觀的道長。 經(jīng)常有香客問我攘轩,道長叉存,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任度帮,我火速辦了婚禮歼捏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘笨篷。我一直安慰自己瞳秽,他們只是感情好,可當我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布率翅。 她就那樣靜靜地躺著练俐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冕臭。 梳的紋絲不亂的頭發(fā)上腺晾,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天,我揣著相機與錄音辜贵,去河邊找鬼悯蝉。 笑死,一個胖子當著我的面吹牛念颈,可吹牛的內(nèi)容都是我干的泉粉。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗡靡!你這毒婦竟也來了跺撼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤讨彼,失蹤者是張志新(化名)和其女友劉穎歉井,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哈误,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡哩至,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜜自。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菩貌。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖重荠,靈堂內(nèi)的尸體忽然破棺而出箭阶,到底是詐尸還是另有隱情,我是刑警寧澤戈鲁,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布仇参,位于F島的核電站,受9級特大地震影響婆殿,放射性物質(zhì)發(fā)生泄漏诈乒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一婆芦、第九天 我趴在偏房一處隱蔽的房頂上張望怕磨。 院中可真熱鬧,春花似錦寞缝、人聲如沸癌压。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至集侯,卻和暖如春被啼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棠枉。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工浓体, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辈讶。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓命浴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子生闲,可洞房花燭夜當晚...
    茶點故事閱讀 43,587評論 2 350

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