探索AIDL定向tag in out inout原理

上一篇文章《從一個(gè)例子開始分析AIDL原理》分析了通過AIDL來完成跨進(jìn)程通信的過程,文章最后拋出了一個(gè)問題: aidl語(yǔ)法中參數(shù)列表中的定向tag: in叫搁、out 和 inout 是什么含義?(閱讀這篇文章需要有一定aidl基礎(chǔ)政供,如果你看到云里霧里旬薯,可以先看看上一篇文章

現(xiàn)在假設(shè)我不知道(盡管或多或少有一些猜想)般又,通常的做法是誰(shuí)寫的這個(gè)東西就找誰(shuí),于是去到Android官網(wǎng)搜索aidl關(guān)鍵字丐黄,在長(zhǎng)長(zhǎng)的文章中躺著下面的一段話斋配。

All non-primitive parameters require a directional tag indicating which way the data goes. Either in, out, or inout. Primitives are in by default, and cannot be otherwise.
Caution: You should limit the direction to what is truly needed, because marshalling parameters is expensive.

大致的意思是說所有非基本數(shù)據(jù)類型的參數(shù)在傳遞的時(shí)候都需要指定一個(gè)方向tag來指明數(shù)據(jù)的流向,可以是in灌闺、out或者inout艰争。基本數(shù)據(jù)類型默認(rèn)是in桂对,還不能修改為其他的甩卓。還附帶了一個(gè)警告,說呀你應(yīng)該根據(jù)實(shí)際需要來限制方向蕉斜,因?yàn)閰?shù)的編排開銷是很大的(畢竟實(shí)現(xiàn)需求追求性能是我們的目標(biāo))逾柿。

官網(wǎng)上也就這么幾句話關(guān)于定向tag的說明,貌似并不能解釋清楚蛛勉。很迷茫鹿寻,怎么辦?如果你是狄仁杰還能來這么一句:元芳诽凌,你怎么看毡熏?這時(shí)元芳一臉胸有成竹地說:當(dāng)一切都毫無頭緒的時(shí)候,就要回歸基本侣诵,看看有沒有忽略細(xì)微的線索痢法。好吧狱窘,回憶一下使用aidl的過程。

創(chuàng)建aidl文件->Build->生成對(duì)應(yīng)的java文件->實(shí)現(xiàn)其內(nèi)部類Stub->onBind返回一個(gè)Stub實(shí)現(xiàn)類實(shí)例......

注意到一點(diǎn)aidl文件的作用是用來生成java文件财搁,最終參與編譯的是這個(gè)java文件蘸炸,這貌似是一個(gè)突破口:只要在aidl文件中分別使用in、out和intout分別定義幾個(gè)方法尖奔,看看生成的java文件的區(qū)別搭儒,從這個(gè)這角度似乎能反推出它們的含義?非常激動(dòng)提茁,根本停不下來淹禾,還是使用上一篇文章的例子:

// IDownloadCenter.aidl
package com.jdqm.downloadcenter.aidl;
import com.jdqm.downloadcenter.aidl.DownloadTask;

interface IDownloadCenter {
    //添加下載任務(wù)
    void addDownloadTaskIn(in DownloadTask task);
    void addDownloadTaskOut(out DownloadTask task);
    void addDownloadTaskInout(inout DownloadTask task);
}

在aidl文件中定義了三個(gè)方法,參數(shù)分別使用了in茴扁、out和inout這三個(gè)定向tag修飾铃岔,Build一下看看生成的java文件。

1峭火、in

@Override
public void addDownloadTaskIn(com.jdqm.downloadcenter.aidl.DownloadTask task) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((task != null)) {
        
            //注意這里參數(shù)不是null寫入的是1
            _data.writeInt(1);
            
            //將傳遞的實(shí)參序列化到 _data
            task.writeToParcel(_data, 0);
        } else {
        
            //參數(shù)為null寫入0
            _data.writeInt(0);
        }
        mRemote.transact(Stub.TRANSACTION_addDownloadTaskIn, _data, _reply, 0);
        _reply.readException();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

可以看到當(dāng)使用in定向tag時(shí)毁习,我們傳過來的參數(shù)被序列化到_data中,所以服務(wù)端的onTransact能收到我們?cè)诎l(fā)起遠(yuǎn)程調(diào)用時(shí)傳的參數(shù)卖丸。

case TRANSACTION_addDownloadTaskIn: {
    data.enforceInterface(DESCRIPTOR);
    com.jdqm.downloadcenter.aidl.DownloadTask _arg0;
    
    //通過讀取tansact中寫入的int值來判讀是否有參數(shù)
    if ((0 != data.readInt())) {
    
        //將參數(shù)反序列化
        _arg0 = com.jdqm.downloadcenter.aidl.DownloadTask.CREATOR.createFromParcel(data);
    } else {
        _arg0 = null;
    }
    
    //調(diào)用目標(biāo)方法
    this.addDownloadTaskIn(_arg0);
    reply.writeNoException();
    return true;
}

到這里我們得出了一個(gè)結(jié)論:使用in定向tag來修飾參數(shù)纺且,在服務(wù)端目標(biāo)方法可以得到一個(gè)與實(shí)參值相同的對(duì)象(注意不是同一個(gè)對(duì)象,它們?cè)诓煌摂M機(jī)實(shí)例中)坯苹。另外要注意一點(diǎn)就是在服務(wù)端反序列化參數(shù)的時(shí)候使用的是:

com.jdqm.downloadcenter.aidl.DownloadTask.CREATOR.createFromParcel(data);

這點(diǎn)提醒我們?cè)趯?shí)現(xiàn)Parcelable接口時(shí)應(yīng)該正確實(shí)現(xiàn)這個(gè)CREATOR

2隆檀、out

@Override
public void addDownloadTaskOut(com.jdqm.downloadcenter.aidl.DownloadTask task) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        
        //這里根本沒有將我們傳過來的task寫入 _data
        mRemote.transact(Stub.TRANSACTION_addDownloadTaskOut, _data, _reply, 0);
        _reply.readException();
        
        //_reply.readInt()這個(gè)值由服務(wù)端寫入
        if ((0 != _reply.readInt())) {
        
            //從 _reply 中反序列化出服務(wù)端 onTransact 寫入 reply 的值,所以我們傳的實(shí)參被修改了
            task.readFromParcel(_reply);
        }
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

可以發(fā)現(xiàn),使用out定向tag粹湃,我們傳的實(shí)參不會(huì)傳遞到服務(wù)端恐仑,并且,如果 _reply.readInt()這個(gè)值不為0为鳄,我們傳的參數(shù)還會(huì)被反序列化修改掉裳仆。出現(xiàn)疑問,既然參數(shù)不傳到服務(wù)端孤钦,那服務(wù)端調(diào)用目標(biāo)方法時(shí)的參數(shù)哪里來的歧斟?看看服務(wù)端onTransat:

case TRANSACTION_addDownloadTaskOut: {
    data.enforceInterface(DESCRIPTOR);
    com.jdqm.downloadcenter.aidl.DownloadTask _arg0;
    
    //沒有從 data 中讀取參數(shù)的過程,而是直接new了一個(gè)新的對(duì)象
    _arg0 = new com.jdqm.downloadcenter.aidl.DownloadTask();
    
    //將new出來的對(duì)象直接傳給目標(biāo)方法
    this.addDownloadTaskOut(_arg0);
    reply.writeNoException();
    if ((_arg0 != null)) {
    
        //有參數(shù)回寫偏形,標(biāo)記1
        reply.writeInt(1);
        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
    } else {
    
        //沒有參數(shù)回寫静袖,標(biāo)記0
        reply.writeInt(0);
    }
    return true;
}

到這里剛才的疑問得到了解答,原來呀在服務(wù)端直接通過new關(guān)鍵字新建了一個(gè)對(duì)象俊扭,然后將這個(gè)新建的對(duì)象傳給了目標(biāo)方法队橙,目標(biāo)方法返回后將參數(shù)序列化到reply中返回。

又得出了一些結(jié)論:使用定向 tag out 的修飾參數(shù),在發(fā)起遠(yuǎn)程調(diào)用時(shí)傳入的實(shí)參不會(huì)傳到到服務(wù)端捐康,而是在服務(wù)端新建一個(gè)對(duì)象傳給目標(biāo)方法仇矾,待目標(biāo)方法返回后將這個(gè)對(duì)象傳回客戶端。另外還發(fā)現(xiàn)一點(diǎn)解总,服務(wù)端是調(diào)用無參的構(gòu)造方法來新建對(duì)象贮匕,這意味著你的參數(shù)所對(duì)應(yīng)的類必須有有無參的構(gòu)造方法。

3花枫、inout

可能看完in刻盐、out這兩個(gè)定向tag后,你已經(jīng)開始猜想inout可能是它們的并集劳翰,到底是不是呢隙疚,接著往下看。

@Override
public void addDownloadTaskInout(com.jdqm.downloadcenter.aidl.DownloadTask task) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((task != null)) {
            _data.writeInt(1);
            
            //將參數(shù)寫入_data磕道,這點(diǎn)與定向tag in一致
            task.writeToParcel(_data, 0);
        } else {
            _data.writeInt(0);
        }
        mRemote.transact(Stub.TRANSACTION_addDownloadTaskInout, _data, _reply, 0);
        _reply.readException();
        if ((0 != _reply.readInt())) {
            //將服務(wù)端寫入reply的值反序列化,修改可客戶端對(duì)象
            task.readFromParcel(_reply);
        }
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

可以看到行冰,客戶端確實(shí)是跟我們猜想的一樣溺蕉,將參數(shù)寫進(jìn)了_data(這點(diǎn)與in相同),調(diào)用task.readFromParcel(_reply)修改了我們傳的參數(shù)(這點(diǎn)與out相同)悼做,再看看服務(wù)端疯特。

case TRANSACTION_addDownloadTaskInout: {
    data.enforceInterface(DESCRIPTOR);
    com.jdqm.downloadcenter.aidl.DownloadTask _arg0;
    if ((0 != data.readInt())) {
    
        //從data中反序列化了客戶端傳過來的參數(shù)
        _arg0 = com.jdqm.downloadcenter.aidl.DownloadTask.CREATOR.createFromParcel(data);
    } else {
        _arg0 = null;
    }
    this.addDownloadTaskInout(_arg0);
    reply.writeNoException();
    if ((_arg0 != null)) {
        reply.writeInt(1);
        
        //將_arg0序列化到repley,客戶端將反序列化出來
        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
    } else {
        reply.writeInt(0);
    }
    return true;
}

服務(wù)端既讀取了客戶端傳過來的數(shù)據(jù)(沒有new對(duì)象肛走,不具備out這點(diǎn)特性)漓雅,并將參數(shù)回寫給客戶端。到這基本上你已經(jīng)有了自己對(duì)這三個(gè)定向tag的結(jié)論朽色,剩下就是該總結(jié)一下:

  1. 定向tag in修飾的的參數(shù)邻吞,經(jīng)序列化后傳遞服務(wù)端,服務(wù)端反序列化得到一個(gè)與之值相同的新的對(duì)象葫男;
  2. 定向tag out修飾的參數(shù)抱冷,客戶端不會(huì)序列化該參數(shù),而是服務(wù)端調(diào)用無參構(gòu)造方法新建了一個(gè)對(duì)象梢褐,待目標(biāo)方法返回后旺遮,將參數(shù)寫入reply返回給客戶端;
  3. 定向tag inout基本上算是in盈咳、out的并集耿眉,為什么說基本上,因?yàn)閛ut會(huì)在服務(wù)端通過new關(guān)鍵字來新建一個(gè)對(duì)象鱼响,而inout已經(jīng)通過反序列化客戶端傳過來的數(shù)據(jù)得到一個(gè)新的對(duì)象鸣剪,就沒有必要再new一個(gè)了。

下面試圖通過一些例子來驗(yàn)證上面的結(jié)論:

//aidl接口方法
void addDownloadTaskIn(in DownloadTask task);

//服務(wù)端實(shí)現(xiàn)
@Override
public void addDownloadTaskIn(DownloadTask task) throws RemoteException {
    //打印結(jié)果:{id=1, url='url of directional tag in'}
    //可以看到目標(biāo)方法的參數(shù)確實(shí)與傳的參數(shù)值是一樣的
    Log.d(TAG, "addDownloadTaskIn: " + task);
    
    //修改參數(shù)的id字段
    task.setId(110);
}

//客戶端調(diào)用
DownloadTask taskIn = new DownloadTask(1, "url of directional tag in");
downloadCenter.addDownloadTaskIn(taskIn);

//打印結(jié)果:{id=1, url='url of directional tag in'}
//服務(wù)端修改了id為110, 但客戶端對(duì)象并沒有被修改
Log.d(TAG, taskIn.toString());
//aidl接口方法
void addDownloadTaskOut(out DownloadTask task);

//服務(wù)端實(shí)現(xiàn)
@Override
public void addDownloadTaskOut(DownloadTask task) throws RemoteException {
    //打印結(jié)果:{id=0, url='null'}
    //可以看到,并不是調(diào)用方法是傳的值西傀,事實(shí)上是調(diào)用無參構(gòu)造方法得到的新對(duì)象
    Log.d(TAG, "addDownloadTaskOut: " + task);

    //將task的兩個(gè)字段
    task.setId(119);
    task.setUrl("change by service");
}

//客戶端調(diào)用
DownloadTask taskOut = new DownloadTask(2, "url of directional tag out");
downloadCenter.addDownloadTaskOut(taskOut);

//打印結(jié)果:{id=119, url='change by service'}
//服務(wù)端修改了參數(shù)斤寇,客戶端參數(shù)也被修改了
Log.d(TAG, taskOut.toString());

//aidl接口方法
void addDownloadTaskInout(inout DownloadTask task);

//服務(wù)端實(shí)現(xiàn)
@Override
public void addDownloadTaskInout(DownloadTask task) throws RemoteException {
    //打印結(jié)果:{id=3, url='url of directional tag inout'}
    //可以看到目標(biāo)方法的參數(shù)確實(shí)與傳的參數(shù)值是一樣的
    Log.d(TAG, "addDownloadTaskInout: " + task);

    //將task的兩個(gè)字段
    task.setId(120);
    task.setUrl("change by service");
}

//客戶端調(diào)用
DownloadTask taskInout = new DownloadTask(3, "url of directional tag inout");
downloadCenter.addDownloadTaskInout(taskInout);
//打印結(jié)果:{id=120, url='change by service'}
//服務(wù)端修改了參數(shù),客戶端參數(shù)也被修改了
Log.d(TAG,  taskInout.toString());

實(shí)驗(yàn)結(jié)果證明拥褂,前面的分析是正確的娘锁。了解了這三個(gè)定向tag后,就可以根據(jù)自己的業(yè)務(wù)需求選擇合適的tag了饺鹃,其實(shí)大部分情況都是用的in莫秆。但是我還是有一個(gè)疑問,上一篇文章中提到悔详,如果在方法前面上關(guān)鍵字 oneway镊屎,當(dāng)客戶端調(diào)用transact后,線程不會(huì)被掛起等待服務(wù)端返回茄螃,這似乎和定向tag out缝驳、inout矛盾了。舉一個(gè)栗子:

oneway void addDownloadTaskOut(out DownloadTask task);

Build后直接報(bào)錯(cuò)了归苍,inout也是一樣不能再使用oneway用狱。這樣看來oneway指的是in這個(gè)way了。等等拼弃,如果方法有返回值呢夏伊?

oneway int addDownloadTaskIn(in DownloadTask task);

Build后依然報(bào)錯(cuò)。雖然你是in吻氧,但是有返回值也不行溺忧,所以這個(gè)oneway還得附加一個(gè)條件:返回值類型要是void。

注意:以上這些結(jié)論也好例子也罷盯孙,都是建立在參數(shù)正確實(shí)現(xiàn)Parcelable接口的基礎(chǔ)上的鲁森。

AIDl有哪些語(yǔ)法

  1. 有哪幾種aidl文件?
    一種是非默認(rèn)支持的數(shù)據(jù)類型對(duì)應(yīng)的aidl文件振惰,如DownlaodTask.aidl刀森,另外一種是定義接口的aidl。例如:
// DownloadTask.aidl
package com.jdqm.downloadcenter.aidl;

parcelable DownloadTask;
// IDownloadCenter.aidl
package com.jdqm.downloadcenter.aidl;

interface IDownloadCenter {
    //添加下載任務(wù)
    void addDownloadTask(in DowloadTask task);
    
    //查詢所有的添加的下載任務(wù)
    List<DownloadTask> getDownloadTask();
}

如果你使用的是默認(rèn)支持的數(shù)據(jù)類型报账,那么第一類aild文件就不需要研底,所以你知道為啥叫“Interface Language”了吧。

  1. 默認(rèn)支持的數(shù)據(jù)類型有哪些透罢?

(1)Java的所有基本數(shù)據(jù)類型榜晦,只支持定向tag in;
(2)String羽圃,只支持定向tag in乾胶;
(3)CharSequence,只支持定向tag in;
(4)List识窿,其中元素必須是aidl支持的數(shù)據(jù)類型斩郎,包括(1)(2)(3)和其他aidl文件定義的parcelable;另外還有一點(diǎn)喻频,接收方得到的總是ArrayList缩宜;
(5)Map,其中元素必須是aidl支持的數(shù)據(jù)類型甥温,包括(1)(2)(3)和其他aidl文件定義的parcelable锻煌;另外還有一點(diǎn),接收方得到的總是HashMap姻蚓。

  1. 必須使用Java語(yǔ)言來編寫宋梧,同時(shí)也提供了一些關(guān)鍵字,如parcelable狰挡、onway捂龄、in、out加叁、inout跺讯,這些關(guān)鍵字的含義以及用法在前面已經(jīng)提到過。

  2. 在使用自定義Parcelable類型時(shí)殉农,必須使用import語(yǔ)句進(jìn)行導(dǎo)入,即使是在同一個(gè)包中局荚。

  3. aidl接口中只能包含方法的定義超凳,不能通過它來暴露一些靜態(tài)字段。

關(guān)注微信公眾號(hào)耀态,第一時(shí)間接收推送!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轮傍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子首装,更是在濱河造成了極大的恐慌创夜,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仙逻,死亡現(xiàn)場(chǎng)離奇詭異驰吓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)系奉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門檬贰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缺亮,你說我怎么就攤上這事翁涤。” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵葵礼,是天一觀的道長(zhǎng)号阿。 經(jīng)常有香客問我,道長(zhǎng)鸳粉,這世上最難降的妖魔是什么扔涧? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮赁严,結(jié)果婚禮上扰柠,老公的妹妹穿的比我還像新娘。我一直安慰自己疼约,他們只是感情好卤档,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著程剥,像睡著了一般劝枣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上织鲸,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天舔腾,我揣著相機(jī)與錄音,去河邊找鬼搂擦。 笑死稳诚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瀑踢。 我是一名探鬼主播扳还,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼橱夭!你這毒婦竟也來了氨距?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤棘劣,失蹤者是張志新(化名)和其女友劉穎俏让,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茬暇,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡首昔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糙俗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沙廉。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臼节,靈堂內(nèi)的尸體忽然破棺而出撬陵,到底是詐尸還是另有隱情珊皿,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布巨税,位于F島的核電站蟋定,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏草添。R本人自食惡果不足惜驶兜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望远寸。 院中可真熱鬧抄淑,春花似錦、人聲如沸驰后。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灶芝。三九已至郑原,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夜涕,已是汗流浹背犯犁。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留女器,地道東北人酸役。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驾胆,于是被迫代替她去往敵國(guó)和親涣澡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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