IPC -通過AIDL看Binder的進(jìn)程間通信過程

AIDL是 Android Interface definition language的縮寫,即:Android接口定義語言。

進(jìn)程隔離:不同進(jìn)程間不可以相互訪問內(nèi)存空間,要想相互調(diào)用,必須要進(jìn)行進(jìn)程間通信。

本篇中不涉及Binder的底層原來,但是要理解一個(gè)知識(shí)點(diǎn):客戶端進(jìn)程持有BinderProxy類的對(duì)象,通過Binder驅(qū)動(dòng),向?qū)?yīng)的運(yùn)行在服務(wù)端進(jìn)程中的Binder對(duì)象發(fā)送消息(執(zhí)行遠(yuǎn)程方法調(diào)用)。可以類比java的Socket編程中的,Socket 向SocketServer發(fā)送消息的過程,不過Binder不僅僅是發(fā)送報(bào)文消息那么簡單,他對(duì)遠(yuǎn)程方法調(diào)用實(shí)現(xiàn)了封裝。

注:這不是一篇介紹如何使用AIDL的文章(如果想學(xué)習(xí)aidl如何使用移步官網(wǎng)案例任玉剛博客),這篇文章主要解讀編譯系統(tǒng)根據(jù)aidl文件生成的java代碼,目的是為了將來讀懂ActivityManagerService的源碼。

AIDL生成代碼解析

為了便于閱讀生成的代碼,我們來寫一個(gè)最最簡單的AIDL,讓服務(wù)端為我們實(shí)現(xiàn)兩個(gè)數(shù)相加的功能。客戶端界面如下:

AIDL客戶端界面 2017-09-12 18.01.31.png

首先定義AIDL接口:

// ICalculate.aidl
package me.febsky.aidl;

interface ICalculate {
    int add(int a, int b);
}

然后在AndroidStudio中運(yùn)行Build-->Rebuild Project或者點(diǎn)擊Gradle同步按鈕,這時(shí)候會(huì)在app-->build-->generated-->source-->aidl下面生成ICalculate.java這個(gè)類。這些代碼是自動(dòng)生成的,不可修改,應(yīng)該說改了也不起作用,下次編譯還會(huì)被覆蓋。

AIDL代碼生成位置 2017-09-12 18.11.05.png

打開這個(gè)類文件,來看下生成的源碼(為了便于閱讀,在Mac上可以按command+alt + L來格式化代碼),現(xiàn)在摘錄代碼如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/liuqiang/Desktop/AIDL/app/src/main/aidl/me/febsky/aidl/ICalculate.aidl
 */
package me.febsky.aidl;

public interface ICalculate extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements me.febsky.aidl.ICalculate {
        private static final java.lang.String DESCRIPTOR = "me.febsky.aidl.ICalculate";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an me.febsky.aidl.ICalculate interface,
         * generating a proxy if needed.
         */
        public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
                return ((me.febsky.aidl.ICalculate) iin);
            }
            return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
        }

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

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

        private static class Proxy implements me.febsky.aidl.ICalculate {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

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

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int add(int a, int b) throws android.os.RemoteException;
}

好吧,怪不得Android要搞出AIDL這么個(gè)東西,我在aidl中就寫了一行代碼,系統(tǒng)生成了這么多,要不是系統(tǒng)自動(dòng)生成,每次用到Binder和AIDL進(jìn)行進(jìn)程間通信的時(shí)候,都要手?jǐn)]這么寫重復(fù)代碼。

這些代碼從何看起呢,為了便于從宏觀上觀察生成的代碼,我們折疊一下暫時(shí)不關(guān)心的代碼:

代碼總覽1 2017-09-12 18.17.35.png

然后是這樣:

代碼總覽2 2017-09-12 18.19.25.png

最后是這么個(gè)樣子:

代碼總3 2017-09-12 18.23.30.png

從以上代碼來看,在生成的java文件中主要有三個(gè)類:ICalculateICalculate.StubICalculate.Proxy。其中Stub是接口ICalculate的靜態(tài)內(nèi)部類,Proxy是Stub的私有靜態(tài)內(nèi)部類(個(gè)人認(rèn)為其實(shí)StubProxy沒有必要一定要做為ICalculate的靜態(tài)內(nèi)部類,這樣放置只是為了便于管理和查看他們之間的關(guān)聯(lián)關(guān)系)

ICalculate

這個(gè)接口其實(shí)很簡單繼承與IInterface,先不用管這個(gè)IInterface的作用,只看ICalculate的話就是個(gè)普通的接口,這里面有我們定義的add方法,就是定了了我們要在AIDL中實(shí)現(xiàn)的業(yè)務(wù)邏輯。這個(gè)接口其實(shí)為了進(jìn)程間通信,所有定義的是客戶端需要服務(wù)端提供的功能。

ICalculate.Stub

這個(gè)類很重要,它繼承了Binder類,實(shí)現(xiàn)了ICalculate接口。從繼承關(guān)系來看他是一個(gè)具有ICalculate功能的Binder。好,既然是一個(gè)Binder就具有了進(jìn)程間通信的功能。注意這個(gè)類是個(gè)抽象類,它只是定義了Binder的業(yè)務(wù)層通信功能,但是具體的通信內(nèi)容(也就是我們的業(yè)務(wù)方法add方法)并沒有具體實(shí)現(xiàn),需要子類來實(shí)現(xiàn)。一般Stub的子類在服務(wù)端實(shí)現(xiàn)。

說到這里必須說下Binder,Binder是Android上比較復(fù)雜的一個(gè)東西了。但這里我們不分析Binder的通信原理。只需要知道,Binder和BinderProxy是成對(duì)出現(xiàn)的,客戶端進(jìn)程持有BinderProxy對(duì)象,然后BinderProxy可以和binder驅(qū)動(dòng)交互,binder驅(qū)動(dòng)再去發(fā)消息給Binder對(duì)象從而實(shí)現(xiàn)IPC。個(gè)人認(rèn)為為了便于理解,完全可以把BinderProxy和Binder類比成javaTCP 中的Socket和SocketServer

看下Binder的源碼結(jié)構(gòu):

Binder 和BinderProxy 源碼2017-09-13 14.30.06.png

Binder和BinderProxy只是實(shí)現(xiàn)了進(jìn)程間通信功能,具體通信內(nèi)容是啥他不關(guān)心。通信內(nèi)容交給ICalculate.Stub.Proxy 和 ICalculate.Stub的子類來實(shí)現(xiàn)。

在ICalculate.Stub中有幾個(gè)很重要的方法:

  • asInterface
  • onTransact

首先看看asInterface方法,這個(gè)方法是一個(gè)靜態(tài)方法,我們?cè)赽ind一個(gè)Service之后,在onServiceConnecttion的回調(diào)里面,就是通過這個(gè)方法拿到一個(gè)遠(yuǎn)程的service(這個(gè)Service不是Android的四大組件的那個(gè)Service)的代理(客戶端和服務(wù)端不在同一個(gè)進(jìn)程中的情況下),binderService時(shí)候的代碼如下:

    ICalculate calculate;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            calculate = ICalculate.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
//一般情況下,如果是跨進(jìn)程的穿件來的參數(shù)都是BinderProxy類型的
public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
    //這種情況基本不存在,可以忽略,你傳了個(gè)null進(jìn)來大家還玩啥?
    if ((obj == null)) {    
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    //這個(gè)if來判斷,客戶端和服務(wù)端是不是在一個(gè)進(jìn)程中
    //也就是來判斷,傳進(jìn)來的參數(shù)obj是Binder對(duì)象還是BinderProxy對(duì)象
    //如果在同一個(gè)進(jìn)程中傳入的是Binder對(duì)象,也即是Stub子類的對(duì)象,以下if語句成立
    if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
          return ((me.febsky.aidl.ICalculate) iin);
    }
    return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
}

onTransact后面我們和Proxy 中的transact方法一塊分析。接下來先看Stub.Proxy這個(gè)類。

ICalculate.Stub.Proxy

Stub.Proxy 2017-09-13 15.35.57.png

從代碼中可以看出,這個(gè)類的構(gòu)造方法接收一個(gè)IBinder的實(shí)現(xiàn)類,其實(shí)這里主要是BinderProxy的對(duì)象。然后忽略其他,直接看我們的add方法。前面也提到了,客戶端通過Stub.asInterface 靜態(tài)方法,持有Stub.Proxy 類的對(duì)象,然后和存在于服務(wù)端進(jìn)程中Stub子類的對(duì)象進(jìn)行通信。其實(shí)歸根到底是客戶端BinderProxy和服務(wù)端Binder的通信。
這個(gè)add方法可以解讀為,Stub.Proxy 類的對(duì)象,持有BinderProxy的對(duì)象,通過BinderProxy對(duì)象,像遠(yuǎn)程的Binder對(duì)象發(fā)送消息。看下發(fā)送消息的主要代碼。可以先不用去管Parcel對(duì)象,可以把它看做一個(gè)可以序列化的對(duì)象,或者向遠(yuǎn)程發(fā)送數(shù)據(jù)的載體。把要傳遞給遠(yuǎn)程對(duì)象的參數(shù)放到Parcel中,然后調(diào)用BinderProxy的transact 方法,發(fā)送消息到Stub子類對(duì)象中。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
這個(gè)方法由三個(gè)參數(shù)

  • Stub.TRANSACTION_add 方法名的唯一標(biāo)示,告訴遠(yuǎn)程對(duì)象,我要執(zhí)行你的哪個(gè)方法,目前我們的接口中只定義了一個(gè)add方法,
  • _data封裝了要調(diào)用的遠(yuǎn)程方法的所有的需要的參數(shù)
  • _reply 遠(yuǎn)程方法返回值的載體
  • flag 最后一個(gè)參數(shù)是個(gè)flag,默認(rèn)0就可以了,好像是用來指定是不是單向的IPC的

通過以上過程,這樣一個(gè)遠(yuǎn)程方法調(diào)用,就會(huì)通過Binder機(jī)制,把消息(調(diào)用某個(gè)方法)發(fā)送到服務(wù)端進(jìn)程中的相應(yīng)對(duì)象中。我們?cè)诖艘廊缓雎訠inderProxy和Binder之間跨進(jìn)程通信的底層原理,只要知道,BinderProxy通過調(diào)用transact 方法,能通過Binder驅(qū)動(dòng),發(fā)消息到Binder進(jìn)程就可以了。繼續(xù)分析當(dāng)BinderProxy通過transact 方法發(fā)送消息到服務(wù)端Binder子類對(duì)應(yīng)的進(jìn)程的時(shí)候,Stub的子類是如何接收處理這個(gè)消息的,看Stub類的onTransact方法:

Stub中的onTransact 方法 2017-09-13 16.08.30.png

可以看到在這個(gè)方法中有個(gè)switch語句,這個(gè)code就是剛剛在transact中的第一參數(shù),用了標(biāo)志該調(diào)用哪個(gè)方法。其余方法也和transact 中的一一對(duì)應(yīng),不再解釋。其實(shí)上面的代碼也很好理解,主要看第二個(gè)case里面語句嗎,先把方法需要參數(shù)從data這個(gè)載體中讀出來,對(duì)應(yīng) BinderProxy transact方法的寫入操作,然后調(diào)用真正的業(yè)務(wù)方法addint _result = this.add(_arg0, _arg1);并把返回值寫入到reply 這個(gè)返回值載體中從而能把方法返回值傳遞個(gè)客戶端。從上面可以看到Stub是個(gè)抽象類,并沒有實(shí)現(xiàn)業(yè)務(wù)方法add,這個(gè)要在他的子類中實(shí)現(xiàn),具體代碼如下:

//注意這個(gè)Service要放到單獨(dú)的進(jìn)程中運(yùn)行
public class CalculateService extends Service {

    private ICalculate.Stub calculate = new ICalculate.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return calculate;
    }
}

【[測(cè)試代碼下載]
(http://download.csdn.net/download/niyingxunzong/9977048)】

測(cè)試效果圖:

測(cè)試效果圖 2017-09-13 16.57.14.png

重要知識(shí)點(diǎn)

在使用AIDL的時(shí)候,編譯工具會(huì)給我們生成一個(gè)Stub的靜態(tài)內(nèi)部類;這個(gè)類繼承了Binder, 說明它是一個(gè)Binder本地對(duì)象,它實(shí)現(xiàn)了IInterface接口,表明它具有遠(yuǎn)程Server承諾給Client的能力;Stub是一個(gè)抽象類,具體的IInterface的相關(guān)實(shí)現(xiàn)需要我們手動(dòng)完成,這里使用了策略模式。

其中基本的UML類圖如下,類圖中并沒有標(biāo)注出所有的方法,只是標(biāo)注了我們關(guān)心的幾個(gè):

UML類圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

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