Android AIDL詳解

概述

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)去了:

新建AIDL文件

鼠標(biāo)移到app上面去扣唱,點(diǎn)擊右鍵藕坯,然后 new->AIDL->AIDL File,按下鼠標(biāo)左鍵就會(huì)彈出一個(gè)框提示生成AIDL文件了画舌。生成AIDL文件之后堕担,項(xiàng)目的目錄會(huì)變成這樣的:

建立AIDL文件后的項(xiàng)目目錄

比起以前多了一個(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 文件就可以了:

自己新建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)程通信的勇婴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忱嘹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耕渴,更是在濱河造成了極大的恐慌拘悦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橱脸,死亡現(xiàn)場(chǎng)離奇詭異础米,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)慰技,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門椭盏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吻商,你說(shuō)我怎么就攤上這事掏颊。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵乌叶,是天一觀的道長(zhǎng)盆偿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)准浴,這世上最難降的妖魔是什么事扭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮乐横,結(jié)果婚禮上求橄,老公的妹妹穿的比我還像新娘。我一直安慰自己葡公,他們只是感情好罐农,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著催什,像睡著了一般涵亏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒲凶,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天气筋,我揣著相機(jī)與錄音,去河邊找鬼旋圆。 笑死宠默,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臂聋。 我是一名探鬼主播光稼,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼或南,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孩等!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起采够,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肄方,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蹬癌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體权她,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年逝薪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隅要。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡董济,死狀恐怖步清,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤廓啊,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布欢搜,位于F島的核電站,受9級(jí)特大地震影響谴轮,放射性物質(zhì)發(fā)生泄漏炒瘟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一第步、第九天 我趴在偏房一處隱蔽的房頂上張望疮装。 院中可真熱鬧,春花似錦粘都、人聲如沸斩个。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)受啥。三九已至,卻和暖如春鸽心,著一層夾襖步出監(jiān)牢的瞬間滚局,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工顽频, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藤肢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓糯景,卻偏偏與公主長(zhǎng)得像嘁圈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蟀淮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • 前言 在決定用這個(gè)標(biāo)題之前甚是忐忑怠惶,主要是擔(dān)心自己對(duì)AIDL的理解不夠深入涨缚,到時(shí)候大家看了之后說(shuō)——你這是什么玩意...
    lypeer閱讀 37,823評(píng)論 22 289
  • 什么是AIDL? AIDL:它是一種android內(nèi)部進(jìn)程通信接口的描述語(yǔ)言策治,通過(guò)它我們可以定義進(jìn)程間的通信接口脓魏。...
    鄭在學(xué)_blog閱讀 610評(píng)論 0 1
  • 一、Android IPC簡(jiǎn)介 IPC是Inter-Process Communication的縮寫通惫,含義就是進(jìn)程...
    SeanMa閱讀 1,770評(píng)論 0 8
  • Android跨進(jìn)程通信IPC整體內(nèi)容如下 1茂翔、Android跨進(jìn)程通信IPC之1——Linux基礎(chǔ)2、Andro...
    隔壁老李頭閱讀 10,721評(píng)論 13 43
  • 一履腋、概述 AIDL 意思即 Android Interface Definition Language珊燎,翻譯過(guò)來(lái)就...
    業(yè)志陳閱讀 117,908評(píng)論 45 174