IPC -通過AIDL看Binder的進程間通信過程

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

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

本篇中不涉及Binder的底層原來,但是要理解一個知識點:客戶端進程持有BinderProxy類的對象番电,通過Binder驅(qū)動,向?qū)倪\行在服務端進程中的Binder對象發(fā)送消息(執(zhí)行遠程方法調(diào)用)辆琅∈欤可以類比java的Socket編程中的,Socket 向SocketServer發(fā)送消息的過程婉烟,不過Binder不僅僅是發(fā)送報文消息那么簡單娩井,他對遠程方法調(diào)用實現(xiàn)了封裝。

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

AIDL生成代碼解析

為了便于閱讀生成的代碼扬霜,我們來寫一個最最簡單的AIDL,讓服務端為我們實現(xiàn)兩個數(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中運行Build-->Rebuild Project或者點擊Gradle同步按鈕,這時候會在app-->build-->generated-->source-->aidl下面生成ICalculate.java這個類啼县。這些代碼是自動生成的材原,不可修改,應該說改了也不起作用季眷,下次編譯還會被覆蓋余蟹。

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

打開這個類文件,來看下生成的源碼(為了便于閱讀子刮,在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這么個東西,我在aidl中就寫了一行代碼兼搏,系統(tǒng)生成了這么多卵慰,要不是系統(tǒng)自動生成佛呻,每次用到Binder和AIDL進行進程間通信的時候,都要手擼這么寫重復代碼鲤嫡。

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

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

然后是這樣:

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

最后是這么個樣子:

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

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

ICalculate

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

ICalculate.Stub

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

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

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

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

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

在ICalculate.Stub中有幾個很重要的方法:

  • asInterface
  • onTransact

首先看看asInterface方法引有,這個方法是一個靜態(tài)方法倦逐,我們在bind一個Service之后,在onServiceConnecttion的回調(diào)里面导帝,就是通過這個方法拿到一個遠程的service(這個Service不是Android的四大組件的那個Service)的代理(客戶端和服務端不在同一個進程中的情況下)穿铆,binderService時候的代碼如下:

    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) {

        }
    };
//一般情況下荞雏,如果是跨進程的穿件來的參數(shù)都是BinderProxy類型的
public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
    //這種情況基本不存在,可以忽略悦陋,你傳了個null進來大家還玩啥筑辨?
    if ((obj == null)) {    
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    //這個if來判斷,客戶端和服務端是不是在一個進程中
    //也就是來判斷暮现,傳進來的參數(shù)obj是Binder對象還是BinderProxy對象
    //如果在同一個進程中傳入的是Binder對象楚昭,也即是Stub子類的對象抚太,以下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這個類电媳。

ICalculate.Stub.Proxy

Stub.Proxy 2017-09-13 15.35.57.png

從代碼中可以看出匆背,這個類的構(gòu)造方法接收一個IBinder的實現(xiàn)類身冀,其實這里主要是BinderProxy的對象括享。然后忽略其他铃辖,直接看我們的add方法猪叙。前面也提到了,客戶端通過Stub.asInterface 靜態(tài)方法犬第,持有Stub.Proxy 類的對象芒帕,然后和存在于服務端進程中Stub子類的對象進行通信。其實歸根到底是客戶端BinderProxy和服務端Binder的通信鉴分。
這個add方法可以解讀為带膀,Stub.Proxy 類的對象垛叨,持有BinderProxy的對象,通過BinderProxy對象舔株,像遠程的Binder對象發(fā)送消息还棱〔训龋看下發(fā)送消息的主要代碼辞做。可以先不用去管Parcel對象稚补,可以把它看做一個可以序列化的對象框喳,或者向遠程發(fā)送數(shù)據(jù)的載體厦坛。把要傳遞給遠程對象的參數(shù)放到Parcel中杜秸,然后調(diào)用BinderProxy的transact 方法润绎,發(fā)送消息到Stub子類對象中。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
這個方法由三個參數(shù)

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

通過以上過程,這樣一個遠程方法調(diào)用膨蛮,就會通過Binder機制季研,把消息(調(diào)用某個方法)發(fā)送到服務端進程中的相應對象中与涡。我們在此依然忽略BinderProxy和Binder之間跨進程通信的底層原理,只要知道氨肌,BinderProxy通過調(diào)用transact 方法酌畜,能通過Binder驅(qū)動桥胞,發(fā)消息到Binder進程就可以了考婴。繼續(xù)分析當BinderProxy通過transact 方法發(fā)送消息到服務端Binder子類對應的進程的時候井誉,Stub的子類是如何接收處理這個消息的颗圣,看Stub類的onTransact方法:

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

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

//注意這個Service要放到單獨的進程中運行
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;
    }
}

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

測試效果圖:

測試效果圖 2017-09-13 16.57.14.png

重要知識點

在使用AIDL的時候蹋盆,編譯工具會給我們生成一個Stub的靜態(tài)內(nèi)部類;這個類繼承了Binder, 說明它是一個Binder本地對象楞抡,它實現(xiàn)了IInterface接口召廷,表明它具有遠程Server承諾給Client的能力;Stub是一個抽象類先紫,具體的IInterface的相關(guān)實現(xiàn)需要我們手動完成筹煮,這里使用了策略模式。

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

UML類圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疮胖,一起剝皮案震驚了整個濱河市闷板,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌性昭,老刑警劉巖糜颠,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萧求,死亡現(xiàn)場離奇詭異夸政,居然都是意外死亡,警方通過查閱死者的電腦和手機匀归,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門穆端,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人攒巍,你說我怎么就攤上這事狡赐≌硖耄” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵西潘,是天一觀的道長哨颂。 經(jīng)常有香客問我威恼,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任懊亡,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己卸留,他們只是感情好椭豫,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匆赃,像睡著了一般算柳。 火紅的嫁衣襯著肌膚如雪姓言。 梳的紋絲不亂的頭發(fā)上何荚,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音妥衣,去河邊找鬼戒傻。 笑死需纳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的兵扬。 我是一名探鬼主播口蝠,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼亚皂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狞谱?” 一聲冷哼從身側(cè)響起禁漓,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤播歼,失蹤者是張志新(化名)和其女友劉穎掰读,沒想到半個月后蹈集,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雇初,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡靖诗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年刊橘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攒庵。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡叙甸,死狀恐怖位衩,靈堂內(nèi)的尸體忽然破棺而出糖驴,到底是詐尸還是另有隱情,我是刑警寧澤辙谜,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布感昼,位于F島的核電站定嗓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凌简。R本人自食惡果不足惜恃逻,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望裳食。 院中可真熱鬧芙沥,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躺孝。三九已至,卻和暖如春底桂,著一層夾襖步出監(jiān)牢的瞬間植袍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工籽懦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留于个,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓暮顺,卻偏偏與公主長得像厅篓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捶码,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

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