前言
在決定用這個(gè)標(biāo)題之前甚是忐忑励翼,主要是擔(dān)心自己對(duì)AIDL的理解不夠深入蜈敢,到時(shí)候大家看了之后說(shuō)——你這是什么玩意兒,就這么點(diǎn)東西就敢說(shuō)夠了汽抚?簡(jiǎn)直是坐井觀天不知所謂——那樣就很尷尬了抓狭。不過(guò)又轉(zhuǎn)念一想,我輩年輕人自當(dāng)有一種一往無(wú)前的銳氣造烁,標(biāo)題大氣一點(diǎn)豈不更好否过?并且大家都是文明人,總歸更多的是理解與補(bǔ)充而不是侮辱與謾罵惭蟋?所以最終還是厚顏用了這么一個(gè)不怎么有恥的標(biāo)題苗桂。
好了,接下來(lái)進(jìn)入正題告组,談?wù)勎覍?duì)AIDL的理解和認(rèn)識(shí)煤伟。
正文
1,概述
AIDL是一個(gè)縮寫(xiě)木缝,全稱(chēng)是Android Interface Definition Language便锨,也就是Android接口定義語(yǔ)言。是的氨肌,首先我們知道的第一點(diǎn)就是:AIDL是一種語(yǔ)言鸿秆。既然是一種語(yǔ)言,那么相應(yīng)的就很自然的衍生出了一些問(wèn)題:
- 為什么要設(shè)計(jì)出這么一門(mén)語(yǔ)言怎囚?
- 它有哪些語(yǔ)法卿叽?
- 我們應(yīng)該如何使用它?
- 再深入一點(diǎn)恳守,我們可以思考考婴,我們是如何通過(guò)它來(lái)達(dá)到我們的目的的?
- 更深入一點(diǎn)催烘,為什么要這么設(shè)計(jì)這門(mén)語(yǔ)言沥阱?會(huì)不會(huì)有更好的方式來(lái)實(shí)現(xiàn)我們的目的?
接下來(lái)伊群,我們就一步步的來(lái)解答上面的這些問(wèn)題考杉。
ps:1,在研究AIDL相關(guān)的東西之前舰始,一些必要的知識(shí)儲(chǔ)備是要有的崇棠。一方面是關(guān)于Android中service相關(guān)的知識(shí),要了解的比較通透才行丸卷,關(guān)于這方面的東西可以參考 Android中的Service:默默的奉獻(xiàn)者 (1)枕稀,Android中的Service:Binder,Messenger,AIDL(2) 這兩篇博文萎坷。另一方面是關(guān)于Android中序列化的相關(guān)知識(shí)凹联,這方面的東西文中會(huì)簡(jiǎn)單提及,但是如果想要深入的研究一下的話最好還是去找一些這方面的資料看一下哆档。 2蔽挠,我的編譯環(huán)境為Android Studio2.1.2,SDK Version 23虐呻,JDK 1.7象泵。
2寞秃,為什么要設(shè)計(jì)這門(mén)語(yǔ)言斟叼?
設(shè)計(jì)這門(mén)語(yǔ)言的目的是為了實(shí)現(xiàn)進(jìn)程間通信。
每一個(gè)進(jìn)程都有自己的Dalvik VM實(shí)例春寿,都有自己的一塊獨(dú)立的內(nèi)存朗涩,都在自己的內(nèi)存上存儲(chǔ)自己的數(shù)據(jù),執(zhí)行著自己的操作绑改,都在自己的那片狹小的空間里過(guò)完自己的一生谢床。每個(gè)進(jìn)程之間都你不知我,我不知你厘线,就像是隔江相望的兩座小島一樣识腿,都在同一個(gè)世界里,但又各自有著自己的世界造壮。而AIDL渡讼,就是兩座小島之間溝通的橋梁。相對(duì)于它們而言耳璧,我們就好像造物主一樣成箫,我們可以通過(guò)AIDL來(lái)制定一些規(guī)則,規(guī)定它們能進(jìn)行哪些交流——比如旨枯,它們可以在我們制定的規(guī)則下傳輸一些特定規(guī)格的數(shù)據(jù)蹬昌。
總之,通過(guò)這門(mén)語(yǔ)言攀隔,我們可以愉快的在一個(gè)進(jìn)程訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù)皂贩,甚至調(diào)用它的一些方法,當(dāng)然昆汹,只能是特定的方法明刷。
3,它有哪些語(yǔ)法筹煮?
其實(shí)AIDL這門(mén)語(yǔ)言非常的簡(jiǎn)單遮精,基本上它的語(yǔ)法和 Java 是一樣的,只是在一些細(xì)微處有些許差別——畢竟它只是被創(chuàng)造出來(lái)簡(jiǎn)化Android程序員工作的,太復(fù)雜不好——所以在這里我就著重的說(shuō)一下它和 Java 不一樣的地方本冲。主要有下面這些點(diǎn):
- 文件類(lèi)型:用AIDL書(shū)寫(xiě)的文件的后綴是 .aidl准脂,而不是 .java。
- 數(shù)據(jù)類(lèi)型:AIDL默認(rèn)支持一些數(shù)據(jù)類(lèi)型檬洞,在使用這些數(shù)據(jù)類(lèi)型的時(shí)候是不需要導(dǎo)包的狸膏,但是除了這些類(lèi)型之外的數(shù)據(jù)類(lèi)型,在使用之前必須導(dǎo)包添怔,就算目標(biāo)文件與當(dāng)前正在編寫(xiě)的 .aidl 文件在同一個(gè)包下——在 Java 中湾戳,這種情況是不需要導(dǎo)包的。比如广料,現(xiàn)在我們編寫(xiě)了兩個(gè)文件砾脑,一個(gè)叫做 Book.java ,另一個(gè)叫做 BookManager.aidl艾杏,它們都在 com.lypeer.aidldemo 包下 韧衣,現(xiàn)在我們需要在 .aidl 文件里使用 Book 對(duì)象,那么我們就必須在 .aidl 文件里面寫(xiě)上
import com.lypeer.aidldemo.Book;
哪怕 .java 文件和 .aidl 文件就在一個(gè)包下购桑。
默認(rèn)支持的數(shù)據(jù)類(lèi)型包括: - Java中的八種基本數(shù)據(jù)類(lèi)型畅铭,包括 byte,short勃蜘,int硕噩,long,float缭贡,double炉擅,boolean,char匀归。
- String 類(lèi)型坑资。
- CharSequence類(lèi)型。
- List類(lèi)型:List中的所有元素必須是AIDL支持的類(lèi)型之一穆端,或者是一個(gè)其他AIDL生成的接口袱贮,或者是定義的parcelable(下文關(guān)于這個(gè)會(huì)有詳解)。List可以使用泛型体啰。
- Map類(lèi)型:Map中的所有元素必須是AIDL支持的類(lèi)型之一攒巍,或者是一個(gè)其他AIDL生成的接口,或者是定義的parcelable荒勇。Map是不支持泛型的柒莉。
- 定向tag:這是一個(gè)極易被忽略的點(diǎn)——這里的“被忽略”指的不是大家都不知道,而是很少人會(huì)正確的使用它沽翔。在我的理解里兢孝,定向 tag 是這樣的:AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向窿凤,其中 in 表示數(shù)據(jù)只能由客戶(hù)端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶(hù)端跨蟹,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶(hù)端之間雙向流通雳殊。其中,數(shù)據(jù)流向是針對(duì)在客戶(hù)端中的那個(gè)傳入方法的對(duì)象而言的窗轩。in 為定向 tag 的話表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)那個(gè)對(duì)象的完整數(shù)據(jù)夯秃,但是客戶(hù)端的那個(gè)對(duì)象不會(huì)因?yàn)榉?wù)端對(duì)傳參的修改而發(fā)生變動(dòng);out 的話表現(xiàn)為服務(wù)端將會(huì)接收到那個(gè)對(duì)象的的空對(duì)象痢艺,但是在服務(wù)端對(duì)接收到的空對(duì)象有任何修改之后客戶(hù)端將會(huì)同步變動(dòng)仓洼;inout 為定向 tag 的情況下,服務(wù)端將會(huì)接收到客戶(hù)端傳來(lái)對(duì)象的完整信息堤舒,并且客戶(hù)端將會(huì)同步服務(wù)端對(duì)該對(duì)象的任何變動(dòng)色建。具體的分析大家可以移步我的另一篇博文:你真的理解AIDL中的in,out植酥,inout么镀岛?
另外弦牡,Java 中的基本類(lèi)型和 String 友驮,CharSequence 的定向 tag 默認(rèn)且只能是 in 。還有驾锰,請(qǐng)注意卸留,請(qǐng)不要濫用定向 tag ,而是要根據(jù)需要選取合適的——要是不管三七二十一椭豫,全都一上來(lái)就用 inout 耻瑟,等工程大了系統(tǒng)的開(kāi)銷(xiāo)就會(huì)大很多——因?yàn)榕帕姓韰?shù)的開(kāi)銷(xiāo)是很昂貴的。 - 兩種AIDL文件:在我的理解里赏酥,所有的AIDL文件大致可以分為兩類(lèi)喳整。一類(lèi)是用來(lái)定義parcelable對(duì)象,以供其他AIDL文件使用AIDL中非默認(rèn)支持的數(shù)據(jù)類(lèi)型的裸扶。一類(lèi)是用來(lái)定義方法接口框都,以供系統(tǒng)使用來(lái)完成跨進(jìn)程通信的『浅浚可以看到魏保,兩類(lèi)文件都是在“定義”些什么,而不涉及具體的實(shí)現(xiàn)摸屠,這就是為什么它叫做“Android接口定義語(yǔ)言”谓罗。
注:所有的非默認(rèn)支持?jǐn)?shù)據(jù)類(lèi)型必須通過(guò)第一類(lèi)AIDL文件定義才能被使用。
下面是兩個(gè)例子季二,對(duì)于常見(jiàn)的AIDL文件都有所涉及:
// Book.aidl
//第一類(lèi)AIDL文件的例子
//這個(gè)文件的作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package com.lypeer.ipcclient;
//注意parcelable是小寫(xiě)
parcelable Book;
// BookManager.aidl
//第二類(lèi)AIDL文件的例子
package com.lypeer.ipcclient;
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類(lèi)型的包
import com.lypeer.ipcclient.Book;
interface BookManager {
//所有的返回值前都不需要加任何東西吉嚣,不管是什么數(shù)據(jù)類(lèi)型
List<Book> getBooks();
Book getBook();
int getBookCount();
//傳參時(shí)除了Java基本類(lèi)型以及String灵汪,CharSequence之外的類(lèi)型
//都需要在前面加上定向tag市埋,具體加什么量需而定
void setBookPrice(in Book book , int price)
void setBookName(in Book book , String name)
void addBookIn(in Book book);
void addBookOut(out Book book);
void addBookInout(inout Book book);
}
4,如何使用AIDL文件來(lái)完成跨進(jìn)程通信蜂筹?
在進(jìn)行跨進(jìn)程通信的時(shí)候,在AIDL中定義的方法里包含非默認(rèn)支持的數(shù)據(jù)類(lèi)型與否芦倒,我們要進(jìn)行的操作是不一樣的艺挪。如果不包含,那么我們只需要編寫(xiě)一個(gè)AIDL文件兵扬,如果包含麻裳,那么我們通常需要寫(xiě) n+1 個(gè)AIDL文件( n 為非默認(rèn)支持的數(shù)據(jù)類(lèi)型的種類(lèi)數(shù))——顯然,包含的情況要復(fù)雜一些器钟。所以我接下來(lái)將只介紹AIDL文件中包含非默認(rèn)支持的數(shù)據(jù)類(lèi)型的情況津坑,至于另一種簡(jiǎn)單些的情況相信大家是很容易從中觸類(lèi)旁通的。
4.1傲霸,使數(shù)據(jù)類(lèi)實(shí)現(xiàn) Parcelable 接口
由于不同的進(jìn)程有著不同的內(nèi)存區(qū)域疆瑰,并且它們只能訪問(wèn)自己的那一塊內(nèi)存區(qū)域,所以我們不能像平時(shí)那樣昙啄,傳一個(gè)句柄過(guò)去就完事了——句柄指向的是一個(gè)內(nèi)存區(qū)域穆役,現(xiàn)在目標(biāo)進(jìn)程根本不能訪問(wèn)源進(jìn)程的內(nèi)存,那把它傳過(guò)去又有什么用呢梳凛?所以我們必須將要傳輸?shù)臄?shù)據(jù)轉(zhuǎn)化為能夠在內(nèi)存之間流通的形式耿币。這個(gè)轉(zhuǎn)化的過(guò)程就叫做序列化與反序列化。簡(jiǎn)單來(lái)說(shuō)是這樣的:比如現(xiàn)在我們要將一個(gè)對(duì)象的數(shù)據(jù)從客戶(hù)端傳到服務(wù)端去韧拒,我們就可以在客戶(hù)端對(duì)這個(gè)對(duì)象進(jìn)行序列化的操作淹接,將其中包含的數(shù)據(jù)轉(zhuǎn)化為序列化流,然后將這個(gè)序列化流傳輸?shù)椒?wù)端的內(nèi)存中去叛溢,再在服務(wù)端對(duì)這個(gè)數(shù)據(jù)流進(jìn)行反序列化的操作塑悼,從而還原其中包含的數(shù)據(jù)——通過(guò)這種方式,我們就達(dá)到了在一個(gè)進(jìn)程中訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù)的目的楷掉。
而通常厢蒜,在我們通過(guò)AIDL進(jìn)行跨進(jìn)程通信的時(shí)候,選擇的序列化方式是實(shí)現(xiàn) Parcelable 接口靖诗。關(guān)于實(shí)現(xiàn) Parcelable 接口之后里面具體有那些方法啦郭怪,每個(gè)方法是干嘛的啦,這些我就不展開(kāi)來(lái)講了刊橘,那並非這篇文章的重點(diǎn)鄙才,我下面主要講一下如何快速的生成一個(gè)合格的可序列化的類(lèi)(以Book.java為例)。
注:若AIDL文件中涉及到的所有數(shù)據(jù)類(lèi)型均為默認(rèn)支持的數(shù)據(jù)類(lèi)型促绵,則無(wú)此步驟攒庵。因?yàn)槟J(rèn)支持的那些數(shù)據(jù)類(lèi)型都是可序列化的嘴纺。
4.1.1,編譯器自動(dòng)生成
我當(dāng)前用的編譯器是Android Studio 2.1.2浓冒,它是自帶了 Parcelable 接口的模板的栽渴,只需要我們敲幾下鍵盤(pán)就可以輕松的生成一個(gè)可序列化的 Parcelable 實(shí)現(xiàn)類(lèi)。
首先稳懒,創(chuàng)建一個(gè)類(lèi)闲擦,正常的書(shū)寫(xiě)其成員變量,建立getter和setter并添加一個(gè)無(wú)參構(gòu)造场梆,比如:
public class Book{
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
private String name;
private int price;
public Book() {}
}
然后 implements Parcelable 墅冷,接著 as 就會(huì)報(bào)錯(cuò),將鼠標(biāo)移到那里或油,按下 alt+enter(as默認(rèn)的自動(dòng)解決錯(cuò)誤的快捷鍵寞忿,如果你們的as有修改過(guò)快捷鍵的話以修改后的為準(zhǔn)) 讓它自動(dòng)解決錯(cuò)誤,這個(gè)時(shí)候它會(huì)幫你完成一部分的工作:
在彈出來(lái)的框里選擇所有的成員變量顶岸,然后確定腔彰。你會(huì)發(fā)現(xiàn)類(lèi)里多了一些代碼,但是現(xiàn)在還是會(huì)報(bào)錯(cuò)辖佣,Book下面仍然有一條小橫線霹抛,再次將鼠標(biāo)移到那里,按下 alt+enter 讓它自動(dòng)解決錯(cuò)誤:
這次解決完錯(cuò)誤之后就不會(huì)報(bào)錯(cuò)了凌简,這個(gè) Book 類(lèi)也基本上實(shí)現(xiàn)了 Parcelable 接口上炎,可以執(zhí)行序列化操作了。
但是請(qǐng)注意雏搂,這里有一個(gè)坑:默認(rèn)生成的模板類(lèi)的對(duì)象只支持為 in 的定向 tag 。為什么呢寇损?因?yàn)槟J(rèn)生成的類(lèi)里面只有 writeToParcel() 方法凸郑,而如果要支持為 out 或者 inout 的定向 tag 的話,還需要實(shí)現(xiàn) readFromParcel() 方法——而這個(gè)方法其實(shí)并沒(méi)有在 Parcelable 接口里面矛市,所以需要我們從頭寫(xiě)芙沥。具體為什么大家可以去看看:你真的理解AIDL中的in,out浊吏,inout么而昨?
那么這個(gè) readFromParcel() 方法應(yīng)當(dāng)怎么寫(xiě)呢?這樣寫(xiě):
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
/**
* 參數(shù)是一個(gè)Parcel,用它來(lái)存儲(chǔ)與傳輸數(shù)據(jù)
* @param dest
*/
public void readFromParcel(Parcel dest) {
//注意找田,此處的讀值順序應(yīng)當(dāng)是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
}
像上面這樣添加了 readFromParcel() 方法之后歌憨,我們的 Book 類(lèi)的對(duì)象在AIDL文件里就可以用 out 或者 inout 來(lái)作為它的定向 tag 了。
此時(shí)墩衙,完整的 Book 類(lèi)的代碼是這樣的:
package com.lypeer.ipcclient;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Book.java
*
* Created by lypeer on 2016/7/16.
*/
public class Book implements Parcelable{
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
private String name;
private int price;
public Book(){}
public Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
/**
* 參數(shù)是一個(gè)Parcel,用它來(lái)存儲(chǔ)與傳輸數(shù)據(jù)
* @param dest
*/
public void readFromParcel(Parcel dest) {
//注意务嫡,此處的讀值順序應(yīng)當(dāng)是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
}
//方便打印數(shù)據(jù)
@Override
public String toString() {
return "name : " + name + " , price : " + price;
}
}
至此甲抖,關(guān)于AIDL中非默認(rèn)支持?jǐn)?shù)據(jù)類(lèi)型的序列化操作就完成了。
4.1.2心铃,插件生成
我不是很清楚 Eclipse 或者較低版本的 as 上會(huì)不會(huì)像 as 2.1.2 這樣幫我們?cè)趯?shí)現(xiàn) Parcelable 接口的過(guò)程中做如此多的操作准谚,但是就算不會(huì),我們還有其他的招數(shù)——通過(guò)插件來(lái)幫我們實(shí)現(xiàn) Parcelable 接口去扣。
具體的實(shí)現(xiàn)方式和實(shí)現(xiàn)過(guò)程大家可以參見(jiàn)這篇文章:告別手寫(xiě)parcelable
4.2柱衔,書(shū)寫(xiě)AIDL文件
首先我們需要一個(gè) Book.aidl 文件來(lái)將 Book 類(lèi)引入使得其他的 AIDL 文件其中可以使用 Book 對(duì)象。那么第一步愉棱,如何新建一個(gè) AIDL 文件呢秀存?Android Studio已經(jīng)幫我們把這個(gè)集成進(jìn)去了:
鼠標(biāo)移到app上面去,點(diǎn)擊右鍵羽氮,然后 new->AIDL->AIDL File或链,按下鼠標(biāo)左鍵就會(huì)彈出一個(gè)框提示生成AIDL文件了。生成AIDL文件之后档押,項(xiàng)目的目錄會(huì)變成這樣的:
比起以前多了一個(gè)叫做 aidl 的包澳盐,而且他的層級(jí)是和 java 包相同的,并且 aidl 包里默認(rèn)有著和 java 包里默認(rèn)的包結(jié)構(gòu)令宿。那么如果你用的是 Eclipse 或者較低版本的 as 叼耙,編譯器沒(méi)有這個(gè)選項(xiàng)怎么辦呢?沒(méi)關(guān)系粒没,我們也可以自己寫(xiě)筛婉。打開(kāi)項(xiàng)目文件夾,依次進(jìn)入 app->src->main癞松,在 main 包下新建一個(gè)和 java 文件夾平級(jí)的 aidl 文件夾爽撒,然后我們手動(dòng)在這個(gè)文件夾里面新建和 java 文件夾里面的默認(rèn)結(jié)構(gòu)一樣的文件夾結(jié)構(gòu),再在最里層新建 .aidl 文件就可以了:
注意看圖中的文件目錄响蓉。
Ok硕勿,如何新建AIDL文件說(shuō)的差不多了,接下來(lái)就該寫(xiě)AIDL文件的內(nèi)容了枫甲。內(nèi)容的話如果上一節(jié)有認(rèn)真看的話基本上是沒(méi)什么問(wèn)題的源武。在這里,我們需要兩個(gè)AIDL文件想幻,我是這樣寫(xiě)的:
// Book.aidl
//第一類(lèi)AIDL文件
//這個(gè)文件的作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package com.lypeer.ipcclient;
//注意parcelable是小寫(xiě)
parcelable Book;
// BookManager.aidl
//第二類(lèi)AIDL文件
//作用是定義方法接口
package com.lypeer.ipcclient;
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類(lèi)型的包
import com.lypeer.ipcclient.Book;
interface BookManager {
//所有的返回值前都不需要加任何東西粱栖,不管是什么數(shù)據(jù)類(lèi)型
List<Book> getBooks();
//傳參時(shí)除了Java基本類(lèi)型以及String,CharSequence之外的類(lèi)型
//都需要在前面加上定向tag脏毯,具體加什么量需而定
void addBook(in Book book);
}
注意:這里又有一個(gè)坑闹究!大家可能注意到了,在 Book.aidl 文件中抄沮,我一直在強(qiáng)調(diào):Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的跋核。這似乎理所當(dāng)然的意味著這兩個(gè)文件應(yīng)當(dāng)是在同一個(gè)包里面的——事實(shí)上岖瑰,很多比較老的文章里就是這樣說(shuō)的,他們說(shuō)最好都在 aidl 包里同一個(gè)包下砂代,方便移植——然而在 Android Studio 里并不是這樣蹋订。如果這樣做的話,系統(tǒng)根本就找不到 Book.java 文件刻伊,從而在其他的AIDL文件里面使用 Book 對(duì)象的時(shí)候會(huì)報(bào) Symbol not found 的錯(cuò)誤露戒。為什么會(huì)這樣呢?因?yàn)?Gradle 捶箱。大家都知道智什,Android Studio 是默認(rèn)使用 Gradle 來(lái)構(gòu)建 Android 項(xiàng)目的,而 Gradle 在構(gòu)建項(xiàng)目的時(shí)候會(huì)通過(guò) sourceSets 來(lái)配置不同文件的訪問(wèn)路徑丁屎,從而加快查找速度——問(wèn)題就出在這里荠锭。Gradle 默認(rèn)是將 java 代碼的訪問(wèn)路徑設(shè)置在 java 包下的,這樣一來(lái)晨川,如果 java 文件是放在 aidl 包下的話那么理所當(dāng)然系統(tǒng)是找不到這個(gè) java 文件的证九。那應(yīng)該怎么辦呢?
又要 java文件和 aidl 文件的包名是一樣的共虑,又要能找到這個(gè) java 文件——那么仔細(xì)想一下的話愧怜,其實(shí)解決方法是很顯而易見(jiàn)的。首先我們可以把問(wèn)題轉(zhuǎn)化成:如何在保證兩個(gè)文件包名一樣的情況下妈拌,讓系統(tǒng)能夠找到我們的 java 文件拥坛?這樣一來(lái)思路就很明確了:要么讓系統(tǒng)來(lái) aidl 包里面來(lái)找 java 文件,要么把 java 文件放到系統(tǒng)能找到的地方去尘分,也即放到 java 包里面去猜惋。接下來(lái)我詳細(xì)的講一下這兩種方式具體應(yīng)該怎么做:
- 修改 build.gradle 文件:在 android{} 中間加上下面的內(nèi)容:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
也就是把 java 代碼的訪問(wèn)路徑設(shè)置成了 java 包和 aidl 包,這樣一來(lái)系統(tǒng)就會(huì)到 aidl 包里面去查找 java 文件音诫,也就達(dá)到了我們的目的惨奕。只是有一點(diǎn),這樣設(shè)置后 Android Studio 中的項(xiàng)目目錄會(huì)有一些改變竭钝,我感覺(jué)改得挺難看的。
- 把 java 文件放到 java 包下去:把 Book.java 放到 java 包里任意一個(gè)包下雹洗,保持其包名不變香罐,與 Book.aidl 一致。只要它的包名不變时肿,Book.aidl 就能找到 Book.java 庇茫,而只要 Book.java 在 java 包下,那么系統(tǒng)也是能找到它的螃成。但是這樣做的話也有一個(gè)問(wèn)題旦签,就是在移植相關(guān) .aidl 文件和 .java 文件的時(shí)候沒(méi)那么方便查坪,不能直接把整個(gè) aidl 文件夾拿過(guò)去完事兒了,還要單獨(dú)將 .java 文件放到 java 文件夾里去宁炫。
我們可以用上面兩個(gè)方法之一來(lái)解決找不到 .java 文件的坑偿曙,具體用哪個(gè)就看大家怎么選了,反正都挺簡(jiǎn)單的羔巢。
到這里我們就已經(jīng)將AIDL文件新建并且書(shū)寫(xiě)完畢了望忆,clean 一下項(xiàng)目,如果沒(méi)有報(bào)錯(cuò)竿秆,這一塊就算是大功告成了启摄。
4.3,移植相關(guān)文件
我們需要保證幽钢,在客戶(hù)端和服務(wù)端中都有我們需要用到的 .aidl 文件和其中涉及到的 .java 文件歉备,因此不管在哪一端寫(xiě)的這些東西,寫(xiě)完之后我們都要把這些文件復(fù)制到另一端去匪燕。如果是用的上面兩個(gè)方法中的第一個(gè)解決的找不到 .java 文件的問(wèn)題蕾羊,那么直接將 aidl 包復(fù)制到另一端的 main 目錄下就可以了;如果是使用第二個(gè)方法的話谎懦,就除了把把整個(gè) aidl 文件夾拿過(guò)去肚豺,還要單獨(dú)將 .java 文件放到 java 文件夾里去。
4.4界拦,編寫(xiě)服務(wù)端代碼
通過(guò)上面幾步吸申,我們已經(jīng)完成了AIDL及其相關(guān)文件的全部?jī)?nèi)容,那么我們究竟應(yīng)該如何利用這些東西來(lái)進(jìn)行跨進(jìn)程通信呢享甸?其實(shí)截碴,在我們寫(xiě)完AIDL文件并 clean 或者 rebuild 項(xiàng)目之后,編譯器會(huì)根據(jù)AIDL文件為我們生成一個(gè)與AIDL文件同名的 .java 文件蛉威,這個(gè) .java 文件才是與我們的跨進(jìn)程通信密切相關(guān)的東西日丹。事實(shí)上,基本的操作流程就是:在服務(wù)端實(shí)現(xiàn)AIDL中定義的方法接口的具體邏輯蚯嫌,然后在客戶(hù)端調(diào)用這些方法接口哲虾,從而達(dá)到跨進(jìn)程通信的目的。
接下來(lái)我直接貼上我寫(xiě)的服務(wù)端代碼:
/**
* 服務(wù)端的AIDLService.java
* <p/>
* Created by lypeer on 2016/7/17.
*/
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//包含Book對(duì)象的list
private List<Book> mBooks = new ArrayList<>();
//由AIDL文件生成的BookManager
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in In");
book = new Book();
}
//嘗試修改book的參數(shù)择示,主要是為了觀察其到客戶(hù)端的反饋
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表束凑,觀察客戶(hù)端傳過(guò)來(lái)的值
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
}
}
};
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android開(kāi)發(fā)藝術(shù)探索");
book.setPrice(28);
mBooks.add(book);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
return mBookManager;
}
}
整體的代碼結(jié)構(gòu)很清晰,大致可以分為三塊:第一塊是初始化栅盲。在 onCreate() 方法里面我進(jìn)行了一些數(shù)據(jù)的初始化操作汪诉。第二塊是重寫(xiě) BookManager.Stub 中的方法。在這里面提供AIDL里面定義的方法接口的具體實(shí)現(xiàn)邏輯谈秫。第三塊是重寫(xiě) onBind() 方法扒寄。在里面返回寫(xiě)好的 BookManager.Stub 鱼鼓。
接下來(lái)在 Manefest 文件里面注冊(cè)這個(gè)我們寫(xiě)好的 Service ,這個(gè)不寫(xiě)的話我們前面做的工作都是無(wú)用功:
<service
android:name=".service.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.lypeer.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
到這里我們的服務(wù)端代碼就編寫(xiě)完畢了该编,如果你對(duì)里面的一些地方感覺(jué)有些陌生或者根本不知所云的話迄本,說(shuō)明你對(duì) Service 相關(guān)的知識(shí)已經(jīng)有些遺忘了,建議再去看看這兩篇博文:Android中的Service:默默的奉獻(xiàn)者 (1)上渴,Android中的Service:Binder岸梨,Messenger,AIDL(2) 稠氮。
4.5曹阔,編寫(xiě)客戶(hù)端代碼
前面說(shuō)過(guò),在客戶(hù)端我們要完成的工作主要是調(diào)用服務(wù)端的方法隔披,但是在那之前赃份,我們首先要連接上服務(wù)端,完整的客戶(hù)端代碼是這樣的:
/**
* 客戶(hù)端的AIDLActivity.java
* 由于測(cè)試機(jī)的無(wú)用debug信息太多奢米,故log都是用的e
* <p/>
* Created by lypeer on 2016/7/17.
*/
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java類(lèi)
private BookManager mBookManager = null;
//標(biāo)志當(dāng)前與服務(wù)端連接狀況的布爾值抓韩,false為未連接,true為連接中
private boolean mBound = false;
//包含Book對(duì)象的list
private List<Book> mBooks;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 按鈕的點(diǎn)擊事件鬓长,點(diǎn)擊之后調(diào)用服務(wù)端的addBookIn方法
*
* @param view
*/
public void addBook(View view) {
//如果與服務(wù)端的連接處于未連接狀態(tài)谒拴,則嘗試連接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "當(dāng)前與服務(wù)端處于未連接狀態(tài),正在嘗試重連涉波,請(qǐng)稍后再試", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研發(fā)錄In");
book.setPrice(30);
try {
mBookManager.addBook(book);
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 嘗試與服務(wù)端建立連接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.lypeer.aidl");
intent.setPackage("com.lypeer.ipcserver");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
同樣很清晰英上,首先建立連接,然后在 ServiceConnection 里面獲取 BookManager 對(duì)象啤覆,接著通過(guò)它來(lái)調(diào)用服務(wù)端的方法苍日。
4.6,開(kāi)始通信吧窗声!
通過(guò)上面的步驟相恃,我們已經(jīng)完成了所有的前期工作,接下來(lái)就可以通過(guò)AIDL來(lái)進(jìn)行跨進(jìn)程通信了笨觅!將兩個(gè)app同時(shí)運(yùn)行在同一臺(tái)手機(jī)上拦耐,然后調(diào)用客戶(hù)端的 addBook() 方法,我們會(huì)看到服務(wù)端的 logcat 信息是這樣的:
//服務(wù)端的 log 信息见剩,我把無(wú)用的信息頭去掉了揩魂,然后給它編了個(gè)號(hào)
1,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }
2炮温,invoking getBooks() method , now the list is : [name : Android開(kāi)發(fā)藝術(shù)探索 , price : 28]
3,invoking addBooks() method , now the list is : [name : Android開(kāi)發(fā)藝術(shù)探索 , price : 28, name : APP研發(fā)錄In , price : 2333]
客戶(hù)端的信息是這樣的:
//客戶(hù)端的 log 信息
1牵舵,service connected
2柒啤,[name : Android開(kāi)發(fā)藝術(shù)探索 , price : 28]
3倦挂,name : APP研發(fā)錄In , price : 2333
所有的 log 信息都很正常并且符合預(yù)期——這說(shuō)明我們到這里為止的步驟都是正確的,按照上面說(shuō)的來(lái)做是能夠正確的使用AIDL來(lái)進(jìn)行跨進(jìn)程通信的担巩。
結(jié)語(yǔ)
這一篇文章主要介紹了我們?cè)诟攀隼锾岬降那叭齻€(gè)問(wèn)題方援,即:
- 為什么要設(shè)計(jì)AIDL語(yǔ)言?
- AIDL的語(yǔ)法是什么涛癌?
- 如何使用AIDL語(yǔ)言完成跨進(jìn)程通信犯戏?
本來(lái)我是準(zhǔn)備在這篇文章里把我那五個(gè)問(wèn)題都講完的,結(jié)果寫(xiě)到這里發(fā)現(xiàn)篇幅已經(jīng)有些長(zhǎng)了拳话,再寫(xiě)的話可能就少有人有這個(gè)耐性讀下去了——那么寫(xiě)在后面的這些又有什么意義呢先匪?于是就干脆從這里截?cái)啵瑢IDL的工作原理和它的設(shè)計(jì)思想以及我對(duì)于它的這種設(shè)計(jì)的一些看法放在下一篇博文里來(lái)講述——?jiǎng)偤闷埽心敲袋c(diǎn)基礎(chǔ)篇和提高篇的意思呀非,哈哈。
文中相關(guān)代碼可點(diǎn)擊 傳送門(mén) 下載镜盯。
謝謝大家岸裙。