-
何為AIDL
Android Interface Definition Language即Android接口定義語言
我們知道亿蒸,因?yàn)橛脩粜枰袈瑁珹ndroid系統(tǒng)中可以同時(shí)運(yùn)行著多個(gè)進(jìn)程,為了進(jìn)程間能夠正常的運(yùn)行贸桶,進(jìn)程之間必須保持獨(dú)立互不干擾刺桃,這就是進(jìn)程隔離。但是很多時(shí)候我們又不得不和另一個(gè)進(jìn)程通信监透,那么這種進(jìn)程間的通信就叫做IPC(Inter-Process Communication)。實(shí)現(xiàn)IPC的方式有很多種航唆,像管道胀蛮、信號(hào)、消息隊(duì)列糯钙、共享內(nèi)存醇滥、Socket等,RPC(Reomote Procedure Call超营,譯稱遠(yuǎn)程過程調(diào)用)也是IPC的一種方式,AIDL使用的就是這種方式阅虫。
-
AIDL原理簡(jiǎn)述
圖片來源:Binder學(xué)習(xí)指南 (ps:這篇文章對(duì)Binder機(jī)制寫的不錯(cuò)演闭,值得一看)
簡(jiǎn)單來說,就是方法提供方稱為server颓帝,方法調(diào)用方稱為client米碰,client和server之間的交互就是IPC。首先server注冊(cè)到一個(gè)稱為ServiceManager的地方购城,client調(diào)用時(shí)會(huì)去查詢?cè)搒erver是否已經(jīng)注冊(cè)吕座,若已注冊(cè)則取到server的代理對(duì)象并執(zhí)行要調(diào)用的方法,對(duì)于調(diào)用方來說就和調(diào)用自己進(jìn)程中的方法一樣瘪板,但是背后的工作確是由Binder驅(qū)動(dòng)轉(zhuǎn)交給真正擁有該方法的server去執(zhí)行并把結(jié)果返回給調(diào)用方client吴趴。
-
AIDL是如何做的
我們還是從使用開始,一步步揭開AIDL的神秘面紗
-
創(chuàng)建AIDL文件
在src/main下新建文件夾aidl侮攀,在其下新建一個(gè).aidl文件锣枝,如果你是使用studio的創(chuàng)建AIDL文件功能選項(xiàng)創(chuàng)建的aidl文件,則會(huì)看到自動(dòng)生成了一個(gè)basicTypes方法:
這個(gè)方法里面的參數(shù)就是aidl支持的基本類型(你可以把這個(gè)方法刪掉也不會(huì)有什么問題)兰英,除此之外撇叁,最好都需要import語句導(dǎo)入(List也可以不用導(dǎo)包,但是是建立在存在jdk的基礎(chǔ)上)畦贸,所以最規(guī)范的做法就是除基本類型之外的類型都要導(dǎo)入陨闹,和java語法一樣,導(dǎo)入為止如下圖:
當(dāng)需要自定義的數(shù)據(jù)類型時(shí),首先要求這個(gè)自定義類型是parcelable類型趋厉,并實(shí)現(xiàn)Parcelable需要的所有東西寨闹,這里值得注意的是,因?yàn)锳IDL規(guī)定除了基本數(shù)據(jù)類型之外觅廓,其他所有類型都需要定向tag顯示聲明鼻忠,有in、out杈绸、inout三種帖蔓,基本數(shù)據(jù)類型默認(rèn)都是in(只能是in),后面會(huì)講到其作用瞳脓,這里我們只要記得如果要支持out或者inout定向的話要手動(dòng)添加readFromParcel()方法塑娇,因?yàn)檫@個(gè)方法Parcelable接口中沒有,需要自行添加:
其次需要定義此類的aidl文件劫侧,這個(gè)aidl文件因?yàn)椴皇墙涌谒圆粫?huì)被自動(dòng)生成(generated):
特別注意: Demo02.aidl的包名要和Demo02.java的包名完全一致B癯辍!
然后在要使用這個(gè)類的aidl文件中引入:
可以看到pringtList和addDemo02方法中的參數(shù)都不是基本類型烧栋,所以前面都必須有一個(gè)定向tag修飾(這里是in)写妥,那么in、out审姓、inout的意義是什么呢珍特?簡(jiǎn)單來說,根據(jù)前面我們提到的進(jìn)程間通信的機(jī)制魔吐,通信的雙方是client和server扎筒,當(dāng)我們從client傳遞一個(gè)引用類型的數(shù)據(jù)給server端的時(shí)候,如果server端修改了數(shù)據(jù)酬姆,若定向是in嗜桌,則client的數(shù)據(jù)不會(huì)被改變,若是out或者是inout則client端的數(shù)據(jù)也會(huì)跟著改變(比如說你調(diào)用test(a)辞色,如果服務(wù)端處理時(shí)a的某些屬性值變了骨宠,那客戶端的a對(duì)象的對(duì)應(yīng)屬性值也會(huì)跟著改變);同理如果定向修飾為out相满,則server端不會(huì)收到client發(fā)送的引用數(shù)據(jù)類型(比如說你調(diào)用test(a)诱篷,服務(wù)端是不會(huì)使用這個(gè)a的,它只會(huì)重新創(chuàng)建一個(gè)同樣類型的新對(duì)象)雳灵。這里可以發(fā)現(xiàn)棕所,只有引用變量才可能被改變,因?yàn)橐脭?shù)據(jù)類型有地址指向悯辙,所以這也就是為什么基本數(shù)據(jù)類型默認(rèn)是in定向修飾符琳省,而且只能是in迎吵,這里其實(shí)有一個(gè)readFromParcel的操作,后面會(huì)看到针贬。關(guān)于定向tag击费,這篇文章講的很詳細(xì):你真的理解AIDL中的in、out桦他、inout么蔫巩?
好了到現(xiàn)在為止我們已經(jīng)做好了所有準(zhǔn)備工作,現(xiàn)在只需要點(diǎn)擊一下studio的rebuild project就會(huì)看到在對(duì)應(yīng)文件夾中自動(dòng)生成的java文件快压,我猜測(cè)studio版本和gradle版本不同圆仔,可能生成的目標(biāo)目錄也會(huì)不一樣,所以不要死記這個(gè)路徑蔫劣,但肯定會(huì)在build/generated目錄下:
下面我們?nèi)タ纯唇o我們自動(dòng)生成的java接口類:
可以看到我們的接口繼承了一個(gè)android.os.IInterface接口:
沒什么特別含義坪郭,就是為了轉(zhuǎn)化成IBinder類型的。
再來看內(nèi)部脉幢,除了簡(jiǎn)單的聲明了我們aidl中定義的接口方法之外歪沃,靜態(tài)內(nèi)部抽象類Stub是自動(dòng)幫我們創(chuàng)建的內(nèi)容,它繼承了android.os.Binder并實(shí)現(xiàn)了外部的AIDL接口嫌松,它是AIDL的核心沪曙。
-
使用AIDL
我們使用常見的Activity和Service的通信來梳理AIDL的工作,首先創(chuàng)建Service:
注意這里的Service可以被多個(gè)主題綁定萎羔,所以這里的方法要考慮線程安全珊蟀,其次,因?yàn)閎inder是匿名類實(shí)現(xiàn)外驱,同時(shí),它又是私有變量只通過onBind方法暴露腻窒,所以這里自定義的方法不能夠被外部調(diào)用昵宇。
再來看一下client端關(guān)鍵代碼:
mAidlService是一個(gè)AidlTest02類型的變量,在service綁定成功之后儿子,通過AidlTest02.Stub.asInterface(IBinder)方法賦值瓦哎,我們看看這個(gè)方法:
這里出現(xiàn)了兩個(gè)return分支,本地調(diào)用返回和遠(yuǎn)程調(diào)用返回柔逼。
-
本地調(diào)用
如果在單個(gè)App的內(nèi)部通過bindService實(shí)現(xiàn)Activity和Service交互蒋譬,就會(huì)走queryLocalInterface本地獲取,此時(shí)這里的obj就是我們的Service里面定義的AidlTest02.Stub的匿名類對(duì)象binder愉适,通過onBind方法傳到了這里犯助,如果不為空則調(diào)用它的queryLocalInterface方法去獲取一個(gè)IInterface類型的對(duì)象,這是IBinder接口的的方法维咸,因?yàn)槲覀兊腟tub繼承了Binder剂买,所以我們直接去Binder下面去找這個(gè)方法的實(shí)現(xiàn):image-20200712092830056image-20200712093553659DESCRIPTOR是Stub內(nèi)部定義的常量坐慰,owner參數(shù)就是Stub本身较性,因?yàn)镾tub實(shí)現(xiàn)了AidlTest02接口,AidlTest02又實(shí)現(xiàn)了IInterface接口结胀。
另外赞咙,因?yàn)槭侵苯邮褂帽镜氐腷inder對(duì)象,并沒有走Proxy邏輯把跨,所以本地調(diào)用時(shí)定向tag是無效的人弓。
-
遠(yuǎn)程調(diào)用
因?yàn)槭茿ndroid間的系統(tǒng)進(jìn)程間通信,所以不是傳統(tǒng)意義上以網(wǎng)絡(luò)為傳輸介質(zhì)的RPC着逐,這里的遠(yuǎn)程調(diào)用就是App之間的進(jìn)程通信崔赌。我們需要?jiǎng)?chuàng)建兩個(gè)App(一個(gè)項(xiàng)目中的兩個(gè)Module即可),兩個(gè)App分別是client端和service端耸别,都需要共同的aidl文件健芭,而且他們的包名要嚴(yán)格相同 。
這里只貼一下另一個(gè)Module中Service相關(guān)代碼:
image-20200712200841772image-20200712200752483下面部分截圖還是上面的本地調(diào)用的秀姐,不過原理是一樣的慈迈,沒什么影響,AidlAutoCreateTest03是我新建的遠(yuǎn)程服務(wù)端aidl文件省有,這里有三個(gè)方法分別使用了in痒留、out和inout定向,下面有一些表現(xiàn)出關(guān)于in蠢沿、out和inout的不一樣的地方我會(huì)用這個(gè)新的遠(yuǎn)程代理服務(wù)的一些源碼截圖說明伸头。
如果是遠(yuǎn)程調(diào)用,那對(duì)于這個(gè)asInterface方法來說舷蟀,參數(shù)IBinder其實(shí)是client的服務(wù)端代理恤磷,所以這里會(huì)返回一個(gè)Stub的內(nèi)部類Proxy對(duì)象,它的構(gòu)造方法里的IBinder就是服務(wù)端注冊(cè)給client的代理野宜。若現(xiàn)在我們調(diào)用getDemo02()方法扫步,其實(shí)調(diào)用的就是Proxy的getDemo02方法:
image-20200712115602366取得了兩個(gè)Parcel對(duì)象,_data是用來保存Client傳遞過來的數(shù)據(jù)的匈子, _reply是用來保存返回給調(diào)用方(client)的數(shù)據(jù)的河胎。上面的是無參數(shù)的所以 _data沒有存儲(chǔ)額外數(shù)據(jù)的操作,看下面這段代碼:
image-20200712124616268client傳遞的數(shù)據(jù)是demo虎敦,demo.writeToParcel(_data仿粹,0)保存到 _data搁吓。
然后通過mRemote.transact方法(mRemote此時(shí)是代理Binder)調(diào)用服務(wù)端的真正對(duì)象的方法,這個(gè)方法后面做的工作牽扯到了Binder機(jī)制的深層邏輯吭历,這里先不討論堕仔,后面單獨(dú)整理,現(xiàn)在只要知道晌区,調(diào)用這個(gè)方法之后摩骨,最后會(huì)走到Stub的onTransact方法里(我們之前在Service里面定義的叫binder的Stub匿名類對(duì)象):
img看到this.printList、this.getDemo02朗若、this.addDemo02了吧恼五,這調(diào)用的就是我們?cè)赟ervice里面定義的那個(gè)匿名類對(duì)象的對(duì)應(yīng)方法。對(duì)于getDemo02方法哭懈,因?yàn)闆]有傳遞數(shù)據(jù)進(jìn)來灾馒,所以沒有從data中取數(shù)據(jù)的操作,但是有返回值遣总,所以通過_result.writeToParcel(reply睬罗,android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE)方法把getDemo02的返回值寫到reply;而對(duì)于addDemo02方法旭斥,會(huì)通過Parcelable類型的CREATOR從之前mRemote的transact方法傳遞進(jìn)來的data構(gòu)造出當(dāng)時(shí)客戶端傳遞的真實(shí)數(shù)據(jù)容达,然后傳遞給真正的addDemo02()方法,因?yàn)闆]有返回值垂券,所以這里reply并沒有執(zhí)行操作花盐。
中間還有部分代碼是Proxy和Stub的之間的數(shù)據(jù)傳遞的,包括判斷客戶端傳遞的非基本類型參數(shù)或引用型返回值是否為null菇爪,通過writeInt和readInt是否為0來判斷是否有必要將參數(shù)繼續(xù)往下傳遞算芯。
值得注意的是:
-
out定向
如果AIDL的方法參數(shù)是out定向,則服務(wù)端不會(huì)收到客戶端的數(shù)據(jù)凳宙,或者說它不會(huì)去取熙揍,Proxy中是這樣做的:image-20200712191124166image-20200712191531343Stub的onTransact方法里是:
image-20200712191918877可以看到,它是直接new了一個(gè)新對(duì)象堪旧,同樣削葱,作為對(duì)比,in和inout是這么做的:
image-20200712192107399這里額外說明一下這個(gè)地方:
image-20200712193927649這就是寫回給client傳遞過來的對(duì)象的值的地方淳梦,只有out和inout會(huì)有析砸,這就是前面說的為什么如果有out或者inout定向的話要給自定義Parcelable類手動(dòng)加上readFromParcel方法的原因。
-
in定向
in定向修飾的話就是參數(shù)會(huì)使用客戶端傳遞過來的爆袍,Proxy中:
image-20200712194523107Stub中:
image-20200712194829706同時(shí)首繁,它不會(huì)將參數(shù)保存到reply中作郭,Proxy中也不會(huì)有dd.readFromParcel(reply)的操作。
-
inout定向
inout沒什么特殊的弦疮,就是兼?zhèn)淞薸n和out夹攒。
-
總結(jié)
到此為止,我們順利地梳理了一遍AIDL從創(chuàng)建aidl文件到使用AIDL機(jī)制來實(shí)現(xiàn)跨進(jìn)程通信地流程胁塞,不過其中還有bindService方法之后Android框架做了什么咏尝、onTransact方法中return true之后Binder是如何去做的問號(hào),后面有時(shí)間會(huì)研究一下啸罢。