AIDL學(xué)習(xí)

AIDL

AIDL的核心有兩點(diǎn)

  • AIDL是一種跨進(jìn)程通訊方式
    這種方式是基于Binder機(jī)制來(lái)進(jìn)行的投慈,Binder本質(zhì)上是基于C/S架構(gòu),Service提供服務(wù)(方法)妈倔,Client使用服務(wù)(方法調(diào)用)
  • AIDL本質(zhì)上是一種代碼生成的方式
    從這一點(diǎn)上來(lái)說(shuō)它和apt代碼生成方式?jīng)]有本質(zhì)區(qū)別博投,AIDL的特殊之處在于它生成的代碼是Binder進(jìn)程間通訊的最外層封裝。

AIDL特點(diǎn)

AIDL的語(yǔ)法和Java基本類似盯蝴,但是也有不同點(diǎn)毅哗,主要在以下幾點(diǎn)

  • 支持的類型
  • Parcelable和AIDL接口都需要導(dǎo)包
  • 沒有public等關(guān)鍵字
  • 所有非基本類型的都需要添加in、out捧挺、inout關(guān)鍵字虑绵,用于指明數(shù)據(jù)流向,默認(rèn)為in

支持的類型

默認(rèn)情況下闽烙,AIDL 支持下列數(shù)據(jù)類型:

  • Java 編程語(yǔ)言中的所有原語(yǔ)類型(如 int翅睛、long、char黑竞、boolean 等等)
  • String
  • CharSequence
  • Parcelable Parcelable必須顯示加入一個(gè) import 語(yǔ)句捕发,即使這些類型是在與接口相同的軟件包中定義。
  • List List 中的所有元素都必須是列表中支持的數(shù)據(jù)類型很魂、其他 AIDL 生成的接口或Parcelable類型扎酷。 可選擇將 List 用作“通用”類(例如,List<String>)遏匆。另一端實(shí)際接收的具體類始終是 ArrayList法挨,但生成的方法使用的是 List 接口。
  • Map
    Map 中的所有元素都必須是列表中支持的數(shù)據(jù)類型幅聘、其他 AIDL 生成的接口或Parcelable類型凡纳。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端實(shí)際接收的具體類始終是 HashMap喊暖,但生成的方法使用的是 Map 接口惫企。
  • AIDL對(duì)應(yīng)的接口

AIDL生成文件解析

AIDL最終會(huì)生成一個(gè)繼承自IInterface的接口,先簡(jiǎn)單看下這個(gè)類的結(jié)構(gòu)。這里我做了一些結(jié)構(gòu)優(yōu)化狞尔,看上去更方便一些

public interface IMusicControler extends IInterface{

    public static abstract class Stub extends Binder implements IMusicControler{
        private static final String DESCRIPTOR = "site.yihome.ipc.IMusicControler";

        public static IMusicControler asInterface(IBinder obj){
           ......
        }

        @Override
        protected boolean onTransact(int code, Parcel data,  Parcel reply, int flags) throws RemoteException {
            ......
            return super.onTransact(code, data, reply, flags);
        }

        public static class Proxy implements IMusicControler{
            private IBinder mRemote;
            Proxy(IBinder remote) {
                mRemote = remote;
            }

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

            @Override
            public void startMusic(Music music) throws RemoteException {
                ......
            }

            @Override
            public void stopMusic() throws RemoteException {
                ......
            }

            @Override
            public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException {
                ......
            }
        }

    }

    public void startMusic(Music music) throws RemoteException;

    public void stopMusic() throws RemoteException;

    public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException;
}

從上面的代碼中丛版,我們可以明顯的看到IMusicControler中以包名定義一個(gè)標(biāo)示DESCRIPTOR,這個(gè)標(biāo)示貫穿整個(gè)通訊過(guò)程偏序,同時(shí)定義一個(gè)三層結(jié)構(gòu)

  • 最外層就是我們AIDL中定義的最原始的接口類型IMusicControler页畦,和它定義的方法
  • 第二層是一個(gè)抽象類Stub,他是三層中唯一的Binder對(duì)象研儒,用于和Binder交互完成真正的進(jìn)程間通訊
  • 第三層是一個(gè)IMusicControler的實(shí)現(xiàn)類Proxy豫缨,和名字一樣是Service端的代理類,進(jìn)程間通訊時(shí)client首先調(diào)用的就是這個(gè)類的方法端朵,然后在通過(guò)Binder調(diào)用遠(yuǎn)程對(duì)象好芭。

如下圖所示,Stub和Proxy本質(zhì)是就是Binder兩端的數(shù)據(jù)封裝和解析層


image

Proxy主要完成Client調(diào)用時(shí)參數(shù)的轉(zhuǎn)化冲呢,它把方法的參數(shù)轉(zhuǎn)換成Parcel類型舍败,用于Binder通信時(shí)傳輸數(shù)據(jù),同時(shí)也把Binder傳回的Parcel類型的返回值轉(zhuǎn)換成對(duì)應(yīng)我們需要的類型敬拓。

Stub和Proxy類似邻薯,但是Stub主要完成的是Server端的數(shù)據(jù)轉(zhuǎn)化。具體的代碼細(xì)節(jié)我們借助下面AIDL調(diào)用的全過(guò)程來(lái)講解

AIDL調(diào)用全過(guò)程

Client調(diào)用過(guò)程

AIDL的調(diào)用過(guò)程乘凸,我們從ServiceConnectiononServiceConnected方法開始

override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
    musicControler = IMusicControler.Stub.asInterface(service)
    ......
}

一般來(lái)說(shuō)厕诡,bindService后在onServiceConnected的回掉中,我們都會(huì)通過(guò)Stub的asInterface方法獲取可以操作的IInterface對(duì)象营勤,asInterface具體細(xì)節(jié)如下

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

asInterface中灵嫌,首先通過(guò)DESCRIPTOR去查詢本地接口(即非跨進(jìn)程),如果是進(jìn)程間通訊則會(huì)用這個(gè)IBinder對(duì)象去new一個(gè)Proxy對(duì)象冀偶。也就是說(shuō)我們?cè)贑lient中拿到的IMusicControler事實(shí)上就是Proxy醒第。

Proxy(IBinder remote) {
    mRemote = remote;
}

如果這時(shí)調(diào)用startMusic(Music music)去調(diào)用遠(yuǎn)程服務(wù),事實(shí)上是調(diào)用的Proxy的方法

public void startMusic(Music music) throws RemoteException {
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();

    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((music!=null)) {
            _data.writeInt(1);
            music.writeToParcel(_data, 0);
        }
        else {
            _data.writeInt(0);
        }
        mRemote.transact(TRANSACTION_startMusic, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

可以看到Proxy事實(shí)上只是把參數(shù)封裝到了Parcel對(duì)象中进鸠,同時(shí)會(huì)去獲取reply中的數(shù)據(jù)。同時(shí)會(huì)遠(yuǎn)程服務(wù)IBinder的transact形病,這個(gè)IBinder事實(shí)上是一個(gè)BinderProxy對(duì)象客年。這里就會(huì)調(diào)用BinderProxy的transact方法

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
    ......
    try {
        return transactNative(code, data, reply, flags);
    } finally {
        ......
    }
}

可以看到這個(gè)transact方法最終會(huì)調(diào)用native方法通過(guò)Binder完成真正的跨進(jìn)程。Client的流程到此結(jié)束漠吻。

Server端調(diào)用過(guò)程

在IPC的Service中量瓜,我們需要在onBind方法中需要返回一個(gè)Stub的實(shí)現(xiàn)類

return new IMusicControler.Stub() {......};

這個(gè)對(duì)象就是Server端的入口類,Client中transactNative方法最終會(huì)調(diào)用Stub的onTransact這個(gè)由它自己實(shí)現(xiàn)的方法

 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_startMusic:
            data.enforceInterface(DESCRIPTOR);
            Music music;
            if ((0!=data.readInt())) {
                music = Music.CREATOR.createFromParcel(data);
            }else {
                music = null;
            }
            this.startMusic(music);
            reply.writeNoException();
            break;
        ......
    }
    return super.onTransact(code, data, reply, flags);
}

這個(gè)方法會(huì)對(duì)在transact傳入的code進(jìn)行不同的處理途乃,同時(shí)從Parcel中獲取實(shí)際參數(shù)绍傲,最后調(diào)用Service中的對(duì)應(yīng)方法。

AIDL異步調(diào)用

默認(rèn)情況下AIDL的調(diào)用是同步的,即必須等到Server端執(zhí)行完成后才會(huì)返回烫饼。如何實(shí)現(xiàn)異步調(diào)用呢猎塞,可以看到在前面的transact中有四個(gè)參數(shù),我們用了其中三個(gè)杠纵,而第四個(gè)參數(shù)flag正是用來(lái)標(biāo)示調(diào)用方式的荠耽,默認(rèn)0標(biāo)示同步調(diào)用,如果需要異步調(diào)用比藻,則需要把flag設(shè)置為FLAG_ONEWAY(這個(gè)過(guò)程想要用AIDL文件進(jìn)行的話只需要在對(duì)應(yīng)的方法前添加oneway關(guān)鍵字)

但是通過(guò)異步調(diào)用铝量,我們是獲取不到返回值的,因此需要接口回掉银亲,但是普通的接口回掉無(wú)法滿足跨進(jìn)程通訊的需求慢叨,這種情況怎么處理呢?

還是Binder务蝠,前面一般情況下Service是充當(dāng)Server端提供方法插爹,Activity作為Client調(diào)用方法,Binder完成Client對(duì)Server的調(diào)用请梢,當(dāng)需要接口回掉時(shí)赠尾,兩者身份就會(huì)反轉(zhuǎn),Activity作為Service提供IBinder對(duì)象毅弧,供Service調(diào)用气嫁,而這個(gè)IBinder對(duì)象則調(diào)用Service的方法通過(guò)參數(shù)傳遞(前面也說(shuō)過(guò),AIDL是支持這種IBinder對(duì)象傳遞的)

Binder的回掉方法是執(zhí)行在子線程中的够坐,這一點(diǎn)可能和Binder的機(jī)制有關(guān)寸宵,有待進(jìn)一步研究

RemoteCallbackList

上面這一種異步回掉只是一種簡(jiǎn)單的情況,當(dāng)一個(gè)Service需要對(duì)很多Client進(jìn)行回掉時(shí)元咙,就需要對(duì)注冊(cè)的回掉進(jìn)行一個(gè)系統(tǒng)的管理了梯影,RemoteCallbackList正是用來(lái)處理這種情況的,它內(nèi)部使用了一個(gè)ArrayMap來(lái)存儲(chǔ)所有的回掉庶香,同時(shí)會(huì)在需要的時(shí)候進(jìn)行調(diào)用甲棍,核心的代碼如下

public class RemoteCallbackList<E extends IInterface> {
    ArrayMap<IBinder, Callback> mCallbacks
        = new ArrayMap<IBinder, Callback>();
    public boolean register(E callback) {
        return register(callback, null);
    }
    
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            // Flag unusual case that could be caused by a leak. b/36778087
            logExcessiveCallbacks();
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }
    
     public void broadcast(Consumer<E> action) {
        int itemCount = beginBroadcast();
        try {
            for (int i = 0; i < itemCount; i++) {
                action.accept(getBroadcastItem(i));
            }
        } finally {
            finishBroadcast();
        }
    }
}

這個(gè)類并不復(fù)雜,它主要封裝了對(duì)callback常見的操作赶掖,同時(shí)幫助我們做了很多線程同步上的工作感猛。

下步目標(biāo)

了解Binder通訊原理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奢赂,隨后出現(xiàn)的幾起案子陪白,更是在濱河造成了極大的恐慌,老刑警劉巖膳灶,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咱士,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)序厉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門锐膜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人脂矫,你說(shuō)我怎么就攤上這事枣耀。” “怎么了庭再?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵捞奕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拄轻,道長(zhǎng)颅围,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任恨搓,我火速辦了婚禮院促,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斧抱。我一直安慰自己常拓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布辉浦。 她就那樣靜靜地躺著弄抬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宪郊。 梳的紋絲不亂的頭發(fā)上掂恕,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音弛槐,去河邊找鬼懊亡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乎串,可吹牛的內(nèi)容都是我干的店枣。 我是一名探鬼主播刹碾,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坚嗜!你這毒婦竟也來(lái)了矢沿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哮洽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蕉斜,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宅此。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片机错。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖父腕,靈堂內(nèi)的尸體忽然破棺而出弱匪,到底是詐尸還是另有隱情,我是刑警寧澤璧亮,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布萧诫,位于F島的核電站,受9級(jí)特大地震影響枝嘶,放射性物質(zhì)發(fā)生泄漏帘饶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一群扶、第九天 我趴在偏房一處隱蔽的房頂上張望及刻。 院中可真熱鬧,春花似錦竞阐、人聲如沸缴饭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颗搂。三九已至,卻和暖如春汪疮,著一層夾襖步出監(jiān)牢的瞬間峭火,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工智嚷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卖丸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓盏道,卻偏偏與公主長(zhǎng)得像稍浆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猜嘱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 原文鏈接: http://weishu.me/2016/01/12/binder-index-for-newer/...
    miniminiming閱讀 725評(píng)論 1 6
  • 毫不夸張地說(shuō)衅枫,Binder是Android系統(tǒng)中最重要的特性之一;正如其名“粘合劑”所喻朗伶,它是系統(tǒng)間各個(gè)組件的橋梁...
    weishu閱讀 17,868評(píng)論 29 246
  • Jianwei's blog 首頁(yè) 分類 關(guān)于 歸檔 標(biāo)簽 巧用Android多進(jìn)程弦撩,微信,微博等主流App都在用...
    justCode_閱讀 5,917評(píng)論 1 23
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理论皆,服務(wù)發(fā)現(xiàn)益楼,斷路器猾漫,智...
    卡卡羅2017閱讀 134,661評(píng)論 18 139
  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要點(diǎn)...
    指尖流逝的青春閱讀 2,609評(píng)論 0 13