概述
AIDL是一個(gè)縮寫慨灭,全稱是Android Interface Definition Language亏栈,也就是Android接口定義語(yǔ)言溉浙。是的,首先我們知道的第一點(diǎn)就是:AIDL是一種語(yǔ)言炸客。既然是一種語(yǔ)言疾棵,那么相應(yīng)的就很自然的衍生出了一些問(wèn)題:
- 為什么要設(shè)計(jì)出這么一門語(yǔ)言?
- 它有哪些語(yǔ)法痹仙?
- 我們應(yīng)該如何使用它是尔?
PS:在研究AIDL的時(shí)候首先需要熟悉service和序列化的一些知識(shí)。
為什么要設(shè)計(jì)這門語(yǔ)言开仰?
AIDL主要是為了實(shí)現(xiàn)進(jìn)程間通信拟枚,尤其是在涉及到多進(jìn)程并發(fā)情況下的進(jìn)程間通信。
我們都知道众弓,安卓中每一個(gè)進(jìn)程都對(duì)應(yīng)一個(gè)Dalvik VM實(shí)例恩溅,都有一塊自己獨(dú)立的內(nèi)存,都在自己的內(nèi)存上存儲(chǔ)數(shù)據(jù)谓娃,執(zhí)行自己的操作脚乡,各個(gè)進(jìn)程就像海上的小島,在同一個(gè)世界滨达,但又有自己獨(dú)立的世界奶稠。AIDL就相當(dāng)于兩座島之間的橋梁,我們使用上帝之手弦悉,通過(guò)AIDL制定一些規(guī)則窒典,蟆炊,規(guī)定他們能進(jìn)行哪些交流——比如在我們定制的規(guī)則下他們只能傳輸一些固定格式的數(shù)據(jù)稽莉。
總之,通過(guò)這門語(yǔ)言涩搓,我們可以愉快的在一個(gè)進(jìn)程訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù)污秆,甚至調(diào)用它的一些方法劈猪,當(dāng)然,只能是特定的方法良拼。
但是战得,如果僅僅是要進(jìn)行跨進(jìn)程通信的話,其實(shí)我們還有其他的一些選擇庸推,比如 BroadcastReceiver , Messenger 等常侦,但是 BroadcastReceiver 占用的系統(tǒng)資源比較多,如果是頻繁的跨進(jìn)程通信的話顯然是不可取的贬媒;Messenger 進(jìn)行跨進(jìn)程通信時(shí)請(qǐng)求隊(duì)列是同步進(jìn)行的聋亡,無(wú)法并發(fā)執(zhí)行,在有些要求多進(jìn)程的情況下不適用——這種時(shí)候就需要使用 AIDL 了际乘。
它有哪些語(yǔ)法坡倔?
其實(shí)AIDL這門語(yǔ)言非常的簡(jiǎn)單,基本上它的語(yǔ)法和 Java 是一樣的脖含,只是在一些細(xì)微處有些許差別——畢竟它只是被創(chuàng)造出來(lái)簡(jiǎn)化Android程序員工作的罪塔,太復(fù)雜不好。所以我們來(lái)總結(jié)一下它和java的不同之處:
文件類型:
用AIDL書(shū)寫的文件的后綴是 .aidl养葵,而不是 .java征堪。
數(shù)據(jù)類型:
AIDL默認(rèn)支持一些數(shù)據(jù)類型,在使用這些數(shù)據(jù)類型的時(shí)候是不需要導(dǎo)包的关拒,但是除了這些類型之外的數(shù)據(jù)類型请契,在使用之前必須導(dǎo)包,就算目標(biāo)文件與當(dāng)前正在編寫的 .aidl 文件在同一個(gè)包下——在 Java 中夏醉,這種情況是不需要導(dǎo)包的爽锥。比如,現(xiàn)在我們編寫了兩個(gè)文件畔柔,一個(gè)叫做 Book.java 氯夷,另一個(gè)叫做 BookManager.aidl,它們都在 com.lypeer.aidldemo 包下 靶擦,現(xiàn)在我們需要在 .aidl 文件里使用 Book 對(duì)象腮考,那么我們就必須在 .aidl 文件里面寫上 import com.lypeer.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一個(gè)包下。
AIDL默認(rèn)支持的數(shù)據(jù)類型包括:
Java中的八種基本數(shù)據(jù)類型玄捕,包括 byte踩蔚,short,int枚粘,long馅闽,float,double,boolean福也,char局骤。
String 類型。
CharSequence類型暴凑。
List類型:List中的所有元素必須是AIDL支持的類型之一峦甩,或者是一個(gè)其他AIDL生成的接口,或者是定義的parcelable(下文關(guān)于這個(gè)會(huì)有詳解)现喳。List可以使用泛型凯傲。
Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個(gè)其他AIDL生成的接口嗦篱,或者是定義的parcelable泣洞。Map是不支持泛型的。
定向tag:
這是一個(gè)極易被忽略的點(diǎn)——這里的“被忽略”指的不是大家都不知道默色,而是很少人會(huì)正確的使用它球凰。在我的理解里,定向 tag 是這樣的:AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向腿宰,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端呕诉, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通吃度。其中甩挫,數(shù)據(jù)流向是針對(duì)在客戶端中的那個(gè)傳入方法的對(duì)象而言的。in 為定向 tag 的話表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)那個(gè)對(duì)象的完整數(shù)據(jù)椿每,但是客戶端的那個(gè)對(duì)象不會(huì)因?yàn)榉?wù)端對(duì)傳參的修改而發(fā)生變動(dòng)伊者;out 的話表現(xiàn)為服務(wù)端將會(huì)接收到那個(gè)對(duì)象的的空對(duì)象,但是在服務(wù)端對(duì)接收到的空對(duì)象有任何修改之后客戶端將會(huì)同步變動(dòng)间护;inout 為定向 tag 的情況下亦渗,服務(wù)端將會(huì)接收到客戶端傳來(lái)對(duì)象的完整信息,并且客戶端將會(huì)同步服務(wù)端對(duì)該對(duì)象的任何變動(dòng)汁尺。
兩種AIDL文件:
在我的理解里法精,所有的AIDL文件大致可以分為兩類。一類是用來(lái)定義parcelable對(duì)象痴突,以供其他AIDL文件使用AIDL中非默認(rèn)支持的數(shù)據(jù)類型的搂蜓。一類是用來(lái)定義方法接口,以供系統(tǒng)使用來(lái)完成跨進(jìn)程通信的辽装“锱觯可以看到,兩類文件都是在“定義”些什么拾积,而不涉及具體的實(shí)現(xiàn)殉挽,這就是為什么它叫做“Android接口定義語(yǔ)言”丰涉。
注:所有的非默認(rèn)支持?jǐn)?shù)據(jù)類型必須通過(guò)第一類AIDL文件定義才能被使用。
下面是兩個(gè)例子此再,對(duì)于常見(jiàn)的AIDL文件都有所涉及:
// Book.aidl
//第一類AIDL文件的例子
//這個(gè)文件的作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package com.lypeer.ipcclient;
//注意parcelable是小寫
parcelable Book;
// BookManager.aidl
//第二類AIDL文件的例子
package com.lypeer.ipcclient;
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類型的包
import com.lypeer.ipcclient.Book;
interface BookManager {
//所有的返回值前都不需要加任何東西,不管是什么數(shù)據(jù)類型
List<Book> getBooks();
Book getBook();
int getBookCount();
//傳參時(shí)除了Java基本類型以及String玲销,CharSequence之外的類型
//都需要在前面加上定向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);
}
如何使用AIDL文件來(lái)完成跨進(jìn)程通信?
在進(jìn)行跨進(jìn)程通信的時(shí)候贤斜,在AIDL中定義的方法里包含非默認(rèn)支持的數(shù)據(jù)類型與否策吠,我們要進(jìn)行的操作是不一樣的。如果不包含瘩绒,那么我們只需要編寫一個(gè)AIDL文件猴抹,如果包含,那么我們通常需要寫 n+1 個(gè)AIDL文件( n 為非默認(rèn)支持的數(shù)據(jù)類型的種類數(shù))——顯然锁荔,包含的情況要復(fù)雜一些蟀给。所以我接下來(lái)將只介紹AIDL文件中包含非默認(rèn)支持的數(shù)據(jù)類型的情況,至于另一種簡(jiǎn)單些的情況相信大家是很容易從中觸類旁通的阳堕。
1. 使數(shù)據(jù)類實(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ù)從客戶端傳到服務(wù)端去贱纠,我們就可以在客戶端對(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è)合格的可序列化的類(以Book.java為例)辫继。
注:若AIDL文件中涉及到的所有數(shù)據(jù)類型均為默認(rèn)支持的數(shù)據(jù)類型,則無(wú)此步驟俗慈。因?yàn)槟J(rèn)支持的那些數(shù)據(jù)類型都是可序列化的姑宽。
1.1 編譯器自動(dòng)生成
我當(dāng)前用的編譯器是Android Studio 2.1.3,它是自帶了 Parcelable 接口的模板的闺阱,只需要我們敲幾下鍵盤就可以輕松的生成一個(gè)可序列化的 Parcelable 實(shí)現(xiàn)類炮车。
首先,創(chuàng)建一個(gè)類酣溃,正常的書(shū)寫其成員變量瘦穆,建立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)類里多了一些代碼黔姜,但是現(xiàn)在還是會(huì)報(bào)錯(cuò),Book下面仍然有一條小橫線蒂萎,再次將鼠標(biāo)移到那里秆吵,按下 alt+enter 讓它自動(dòng)解決錯(cuò)誤:
這次解決完錯(cuò)誤之后就不會(huì)報(bào)錯(cuò)了,這個(gè) Book 類也基本上實(shí)現(xiàn)了 Parcelable 接口五慈,可以執(zhí)行序列化操作了纳寂。
但是請(qǐng)注意,這里有一個(gè)坑:默認(rèn)生成的模板類的對(duì)象只支持為 in 的定向 tag 。為什么呢?因?yàn)槟J(rèn)生成的類里面只有 writeToParcel() 方法娄柳,而如果要支持為 out 或者 inout 的定向 tag 的話,還需要實(shí)現(xiàn) readFromParcel() 方法——而這個(gè)方法其實(shí)并沒(méi)有在 Parcelable 接口里面腋粥,所以需要我們從頭寫。
@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 類的對(duì)象在AIDL文件里就可以用 out 或者 inout 來(lái)作為它的定向 tag 了。
此時(shí)绑雄,完整的 Book 類的代碼是這樣的:
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ù)類型的序列化操作就完成了万牺。
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 接口。
2. 書(shū)寫AIDL文件
首先我們需要一個(gè) Book.aidl 文件來(lái)將 Book 類引入使得其他的 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)系,我們也可以自己寫膜楷。打開(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)就該寫AIDL文件的內(nèi)容了。內(nèi)容的話如果上一節(jié)有認(rèn)真看的話基本上是沒(méi)什么問(wèn)題的揍障。在這里目养,我們需要兩個(gè)AIDL文件,我是這樣寫的:
// Book.aidl
//第一類AIDL文件
//這個(gè)文件的作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package com.lypeer.ipcclient;
//注意parcelable是小寫
parcelable Book;
// BookManager.aidl
//第二類AIDL文件
//作用是定義方法接口
package com.lypeer.ipcclient;
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類型的包
import com.lypeer.ipcclient.Book;
interface BookManager {
//所有的返回值前都不需要加任何東西毒嫡,不管是什么數(shù)據(jù)類型
List<Book> getBooks();
//傳參時(shí)除了Java基本類型以及String癌蚁,CharSequence之外的類型
//都需要在前面加上定向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ū)寫完畢了,clean 一下項(xiàng)目希坚,如果沒(méi)有報(bào)錯(cuò)边苹,這一塊就算是大功告成了。
3. 移植相關(guān)文件
我們需要保證裁僧,在客戶端和服務(wù)端中都有我們需要用到的 .aidl 文件和其中涉及到的 .java 文件个束,因此不管在哪一端寫的這些東西,寫完之后我們都要把這些文件復(fù)制到另一端去聊疲。如果是用的上面兩個(gè)方法中的第一個(gè)解決的找不到 .java 文件的問(wèn)題茬底,那么直接將 aidl 包復(fù)制到另一端的 main 目錄下就可以了;如果是使用第二個(gè)方法的話获洲,就除了把把整個(gè) aidl 文件夾拿過(guò)去阱表,還要單獨(dú)將 .java 文件放到 java 文件夾里去。
4. 編寫服務(wù)端代碼
通過(guò)上面幾步昌妹,我們已經(jīng)完成了AIDL及其相關(guān)文件的全部?jī)?nèi)容捶枢,那么我們究竟應(yīng)該如何利用這些東西來(lái)進(jìn)行跨進(jìn)程通信呢握截?其實(shí)飞崖,在我們寫完AIDL文件并 clean 或者 rebuild 項(xiàng)目之后,編譯器會(huì)根據(jù)AIDL文件為我們生成一個(gè)與AIDL文件同名的 .java 文件谨胞,這個(gè) .java 文件才是與我們的跨進(jìn)程通信密切相關(guān)的東西固歪。事實(shí)上,基本的操作流程就是:在服務(wù)端實(shí)現(xiàn)AIDL中定義的方法接口的具體邏輯胯努,然后在客戶端調(diào)用這些方法接口牢裳,從而達(dá)到跨進(jìn)程通信的目的。
接下來(lái)我直接貼上我寫的服務(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ù)叶沛,主要是為了觀察其到客戶端的反饋
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表蒲讯,觀察客戶端傳過(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ù)的初始化操作判帮。第二塊是重寫 BookManager.Stub 中的方法。在這里面提供AIDL里面定義的方法接口的具體實(shí)現(xiàn)邏輯溉箕。第三塊是重寫 onBind() 方法晦墙。在里面返回寫好的 BookManager.Stub 。
接下來(lái)在 Manefest 文件里面注冊(cè)這個(gè)我們寫好的 Service 肴茄,這個(gè)不寫的話我們前面做的工作都是無(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>
5. 編寫客戶端代碼
前面說(shuō)過(guò)晌畅,在客戶端我們要完成的工作主要是調(diào)用服務(wù)端的方法,但是在那之前寡痰,我們首先要連接上服務(wù)端抗楔,完整的客戶端代碼是這樣的:
/**
* 客戶端的AIDLActivity.java
* 由于測(cè)試機(jī)的無(wú)用debug信息太多棋凳,故log都是用的e
* <p/>
* Created by lypeer on 2016/7/17.
*/
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java類
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ù)端的方法。
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)用客戶端的 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]
客戶端的信息是這樣的:
//客戶端的 log 信息
service connected
[name : Android開(kāi)發(fā)藝術(shù)探索 , price : 28]
name : APP研發(fā)錄In , price : 30
所有的 log 信息都很正常并且符合預(yù)期——這說(shuō)明我們到這里為止的步驟都是正確的,按照上面說(shuō)的來(lái)做是能夠正確的使用AIDL來(lái)進(jìn)行跨進(jìn)程通信的勇婴。