本篇包含進(jìn)程間通信——AIDL所涉及到的知識(shí)的自我總結(jié)(內(nèi)容詳細(xì))
通過前段時(shí)間對(duì)AIDL的學(xué)習(xí)以及最近一些資料的查閱,特在此總結(jié)一下我所理解到的AIDL,下文將從以下幾個(gè)方面來分析。
- WHY(目的)
- Support(支持的數(shù)據(jù)類型)
- How(如何使用)
- What's The Meaning(AIDL生成的java文件各部分的含義)
- Permission(客戶端調(diào)用時(shí)的權(quán)限驗(yàn)證)
- Best Practice(使用連接池管理)
注:在學(xué)習(xí)AIDL之前需要有一定的基礎(chǔ),需要了解序列化以及Service的使用。
一、WHY
首先進(jìn)程間通信(IPC,Inter-process Communication)的方式有很多,例如Bundle、文件共享、AIDL、Messenger、ContentProvider以及Socket,方法是多種多樣,而我們更應(yīng)該根據(jù)自己的情況去選擇合適的方式,下面列舉出每種方式的適用場景:
名稱 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸Bundle支持的數(shù)據(jù)類型 | 四大組件間的進(jìn)程通信 |
文件共享 | 簡單易用 | 不適合高并發(fā)場景,無法及時(shí)通信 | 無高并發(fā)訪問,交換簡單數(shù)據(jù),實(shí)時(shí)性不高 |
AIDL | 功能強(qiáng)大,支持一對(duì)多并發(fā)通信,支持及時(shí)通信 | 使用復(fù)雜,需要處理線程同步 | 一對(duì)多且有遠(yuǎn)程調(diào)用需求 |
Messenger | 功能一般,支持一對(duì)多串行,支持實(shí)時(shí)通信 | 不能很好處理高并發(fā),不支持遠(yuǎn)程調(diào)用,數(shù)據(jù)只能使用Message傳輸 | 低并發(fā)的一對(duì)多即時(shí)通信,無須直接返回結(jié)果,無遠(yuǎn)程調(diào)用 |
ContentProvider | 數(shù)據(jù)訪問方面功能強(qiáng)大,支持一對(duì)多并發(fā)數(shù)據(jù)共享 | 受約束的AIDL,主要提供數(shù)據(jù)的增刪改查 | 一對(duì)多進(jìn)程間數(shù)據(jù)共享 |
Socket | 功能強(qiáng)大,支持一對(duì)多并發(fā)實(shí)時(shí)通信 | 實(shí)現(xiàn)復(fù)雜,不支持直接的遠(yuǎn)程調(diào)用 | 網(wǎng)絡(luò)數(shù)據(jù)交互 |
注:該表格信息來自《Android開發(fā)藝術(shù)探究》
從上表格我們可以看出,AIDL出現(xiàn)的目的就是為了解決一對(duì)多并發(fā)及時(shí)通信了,雖然socket也支持,但其更多應(yīng)用于網(wǎng)絡(luò)數(shù)據(jù)交互,所以在Android中,google為我們提供了AIDL來方便開發(fā)者實(shí)現(xiàn)該功能。
二、Support
在進(jìn)程間通信中,對(duì)象的引用是無法傳遞的,因?yàn)槠鋬?nèi)存地址都不在一個(gè)區(qū)域內(nèi),所以數(shù)據(jù)的傳輸都需要序列化后才行,基礎(chǔ)數(shù)據(jù)類型自然是沒問題,系統(tǒng)提供的ArrayList以及HashMap也實(shí)現(xiàn)了序列化,所以對(duì)于自定義的對(duì)象實(shí)現(xiàn)序列化就只能實(shí)現(xiàn)serializable或者parcelable接口,其中前者是java提供的,后者是Android提供的且效率更高,所以在之后的數(shù)據(jù)序列化中都只使用實(shí)現(xiàn)parcelable接口的方法即可,總的來說支持的數(shù)據(jù)類型包括:
- 基本數(shù)據(jù)類型:int、long、char、boolean、double等
- 系統(tǒng)已實(shí)現(xiàn)類型:String、CharSequence、ArrayList、HashMap(后兩個(gè)的每個(gè)元素也得被AIDL支持才行)
- 正確實(shí)現(xiàn)parcelable接口的對(duì)象
- 所有AIDL本身
對(duì)于自定義實(shí)現(xiàn)parcelable接口的類以及AIDL都必須通過import導(dǎo)入完全限定名才可以使用,而實(shí)現(xiàn)parcelable接口的類也需要?jiǎng)?chuàng)建一個(gè)與之對(duì)應(yīng)的aidl文件才能被識(shí)別,例如:
#Person.java文件
package net.arvin.androidart.aidl;
//省略導(dǎo)入的包
public class Person implements Parcelable {
//省略具體內(nèi)容
}
#Person.aidl文件
package net.arvin.androidart.aidl;parcelable Person;
其中aidl的包名要相同才能正確的找到相應(yīng)的java文件。
除此之外還有一個(gè)知識(shí)點(diǎn)那就是對(duì)于非基礎(chǔ)數(shù)據(jù)類型的類型,在aidl中作為參數(shù)使用時(shí)有三個(gè)標(biāo)記,分別時(shí)in、out、inout,表示數(shù)據(jù)的流向:
名稱 | 含義 |
---|---|
in | 表示能讀取到傳入的對(duì)象中的值,而修改后不會(huì)修改傳入對(duì)象的內(nèi)容 |
out | 表示讀取到傳入的對(duì)象為空,修改后會(huì)把傳入對(duì)象的內(nèi)容改為修改的內(nèi)容 |
inout | 表示既能讀取到對(duì)象,又能修改對(duì)象,效果就好比統(tǒng)一進(jìn)程中對(duì)象的傳遞一樣 |
三、How
這里的實(shí)現(xiàn)時(shí)基于AS的(Eclipse只是創(chuàng)建的文件放置的目錄不同,具體細(xì)節(jié)這里就不再介紹),對(duì)于如何實(shí)現(xiàn)我認(rèn)為有以下三部分:
- 定義AIDL接口
- 在服務(wù)端返回其Binder以及所需實(shí)現(xiàn)的接口
- 在客戶端拿到Binder進(jìn)行調(diào)用
下面就一步一步的來介紹如何實(shí)現(xiàn),現(xiàn)在就以客戶端需要向服務(wù)端添加和查詢?nèi)宋镄畔槔?,其中人物信息只包含名稱和年齡即可(這里就不使用基本數(shù)據(jù)類型了,因?yàn)榛緮?shù)據(jù)類型會(huì)少了定義AIDL支持的對(duì)象這一步)
1、定義AIDL接口
a、創(chuàng)建aidl目錄
在src/main下創(chuàng)建一個(gè)aidl文件夾,再創(chuàng)建一個(gè)包,例如net.arvin.androidart.aidl,建議讓所有的aidl在同一個(gè)包下,會(huì)更方便。
b、定義Person對(duì)象并實(shí)現(xiàn)parcelable接口
定義和序列化的實(shí)現(xiàn)這里就不貼代碼了,序列化可通過AS插件生成,需要注意的是這時(shí)候只支持in那種標(biāo)記的數(shù)據(jù)流向,若要支持out,則還需定義readFromParcel(Parcel in)方法,具體原因在解釋每一部分的含義時(shí)會(huì)介紹。只需要注意,需要在a中所創(chuàng)建的包下創(chuàng)建Person.aidl,然后也需要在main/java下的net.arvin.androidart.aidl包中創(chuàng)建Person.java,其主要原因是gradle默認(rèn)的java文件都在main/java文件夾下(這里就不提供修改方法了,修改后就可以讓.java文件定義在aidl的文件下就可以了),這里就先這樣麻煩一點(diǎn)吧。
c、定義通信的AIDL接口
代碼很簡單,就是在剛才創(chuàng)建的Person.aidl包下創(chuàng)建IPersonCount.aidl文件(文件名可隨便定義,后綴卻必須為.aidl),內(nèi)容如下:
package net.arvin.androidart.aidl;
import net.arvin.androidart.aidl.Person;
interface IPersonCount {
boolean addPerson(in Person person);
List<Person> getPersons();
}
d、生成Binder
上面三步寫好后就可以點(diǎn)擊build->make project,如果沒有編譯出錯(cuò),那么恭喜你,第一步已經(jīng)成功了。那么你就可以在你的那個(gè)module的build/generated/source/aidl/debug/packagename/下看到所生成的IPersonCount.java了,其中有個(gè)靜態(tài)內(nèi)部類Stub就是之后需要的Binder了,其他的我們就暫時(shí)先不看。
2、在服務(wù)端返回其Binder以及所需實(shí)現(xiàn)的接口
注意,對(duì)于進(jìn)程間通信有兩種情況,一種在同一個(gè)應(yīng)用內(nèi),另一種是在不同的應(yīng)用內(nèi);同一應(yīng)用就沒啥可說就可少了接下來的一步,若是不同應(yīng)用內(nèi),我們還需要把第一步中定義的所有文件原封不動(dòng)的拷貝到另一個(gè)應(yīng)用中,并編譯,假如第一步中我們在服務(wù)端已經(jīng)定義好了,那么接下來我們就需要把Person.java、Person.aidl、IPersonCount.aidl三個(gè)文件在保證包名不變的情況下拷貝過去并編譯,就能生成好同樣的IPersonCount.java文件了,這一步一定需要仔細(xì)。接下來就開始在服務(wù)端返回binder,而我們最開始接觸到binder,就是在Service中,然后在bindService的時(shí)候就會(huì)用到Binder,所以我們就需要定義Service。
這個(gè)也很簡單,就是定義一個(gè)類繼承Service,然后實(shí)例化IPersonCount.Stub這時(shí)候需要實(shí)現(xiàn)剛才定義的addPerson和getPerson方法,具體代碼這里也不貼了,很簡單,然后在Service的onBind中返回剛才實(shí)例化的IPersonCount.Stub,然后再在清單文件中去聲明改Service,即可。
3、在客戶端綁定服務(wù),拿到Binder,并使用
這時(shí)候我們回到剛才的客戶端中,在Activity中通過bindService去綁定Service,其中Intent設(shè)置的ComponentName為服務(wù)端中定義的Service的報(bào)名和完全限定名,另外ServiceConnection在onServiceConnected的時(shí)候會(huì)帶有剛才服務(wù)端返回的binder,這時(shí)候我們就可以通過IPersonCount.Stub.asInterface(iBinder)獲取到Binder,例如叫做iPersonCount,然后就可以在Activity中使用iPersonCount.addPerson(person)和iPersonCount.getPersons()方法了。
這樣整個(gè)使用過程就完成了,整體來說還是比較簡單的,主要需要注意的就是對(duì)于書寫aidl文件時(shí)類型的引用。最后會(huì)附上項(xiàng)目地址。
四、What's The Meaning
在定義AIDL接口一節(jié)中說到,最終會(huì)生成一個(gè)java文件,他其實(shí)才是我們應(yīng)用的核心,AIDL其實(shí)只是一種開發(fā)語言,方便我們生成那個(gè)java文件,幫我們完成了其中每次都差不多的重復(fù)工作,雖然系統(tǒng)能為我們完成這一個(gè)工作,我們還是應(yīng)該了解其中每部分到底是做什么的。
先來看看IPersonCount.java這個(gè)類,包含了什么東西,代碼不少,這里還是不貼了,可自己對(duì)照著看,這里只提供一個(gè)我所整理的簡單的類圖:
IPersonCount類關(guān)系圖
這樣一看其實(shí)就很清晰了:
- IPersonCount是一個(gè)接口,繼承自IInterface接口,里邊有一個(gè)Stub抽象靜態(tài)內(nèi)部類以及aidl中定義的方法;
- Stub是繼承自Binder并實(shí)現(xiàn)了IPersonCount的一個(gè)抽象靜態(tài)內(nèi)部類,里邊有一個(gè)靜態(tài)內(nèi)部類、三個(gè)屬性和四個(gè)方法,還有兩個(gè);
- Proxy是Stub的靜態(tài)內(nèi)部類,有一個(gè)屬性和五個(gè)方法,后兩個(gè)是對(duì)IPersonCount接口的實(shí)現(xiàn)。
然后一個(gè)一個(gè)的介紹每個(gè)屬性和方法的含義:
1、IPersonCount類
這個(gè)類就是一個(gè)接口,比較簡單就不介紹了。
2、IPersonCount.Stub類
a 、DESCRIPTOR:
這是Binder的唯一標(biāo)識(shí),一般都是當(dāng)前AIDL生成的類名;
b、TRANSACTION_XXX:
以這個(gè)開頭的都是統(tǒng)一定義這個(gè)方法的code,通過code就能知道調(diào)用的是哪個(gè)方法;
c、asInterface:
是將傳入的Binder轉(zhuǎn)化為相應(yīng)的AIDL所定義的接口,看其實(shí)現(xiàn)可發(fā)現(xiàn)queryLocalInterface用于區(qū)分是統(tǒng)一進(jìn)程調(diào)用的,還是不同進(jìn)程調(diào)用的,其具體實(shí)現(xiàn)在Binder中,在構(gòu)造函數(shù)中賦值,若是同一進(jìn)程,則使用的其實(shí)就是這個(gè)Binder,若是不同進(jìn)程,則調(diào)用創(chuàng)建一個(gè)Proxy代理,用于調(diào)用服務(wù)端的onTransact方法,執(zhí)行具體的實(shí)現(xiàn);
d、asBinder:
這是返回當(dāng)前對(duì)象,因?yàn)樵搶?duì)象在服務(wù)端實(shí)例化后就是一個(gè)Binder,這里使用這個(gè)方法來定義,我猜想是為了更清晰的表達(dá)吧,同時(shí)要調(diào)整Binder我們也只需要重寫這個(gè)方法就可以了,也方便;
e、onTransact:
首先了解一下這些個(gè)參數(shù)的意思:
- code(表示客戶端調(diào)用的是哪個(gè)方法);
- data(表示序列化的傳入?yún)?shù));
- reply(表示序列化回應(yīng)相關(guān)的數(shù)據(jù));
- flags(這是一個(gè)可選的參數(shù),默認(rèn)都是0,可以傳1,表示立即就有返回,不會(huì)等待,只應(yīng)用在調(diào)用者與被調(diào)用者在不同的進(jìn)程中)。
再看其返回值,返回false表示調(diào)用不成功,而下文中的權(quán)限則可在這里去配置;
再看看其方法的具體實(shí)現(xiàn),首先通過switch區(qū)分調(diào)用的是哪個(gè)方法,然后執(zhí)行相應(yīng)的操作,而每個(gè)方法其實(shí)其流程都差不多,首先獲取參數(shù),然后調(diào)用該方法對(duì)應(yīng)的實(shí)現(xiàn),最后再通過reply中處理數(shù)據(jù)。
讀取數(shù)據(jù),會(huì)根據(jù)定義的這個(gè)方法的類型去讀取,如果是基本類型則調(diào)用相應(yīng)的read方法就能獲得,例如data.readInt、data.readString等,若是對(duì)象則通過readInt判斷不為0,再使用那個(gè)對(duì)象序列化中的CREATOR去createFromParcel來獲得對(duì)象;
執(zhí)行相應(yīng)方法,就是調(diào)用該方法,若有返回值則用相應(yīng)的對(duì)象接收;
操作reply,如果有返回值則就通過reply寫入,aidl所支持的類型都有相應(yīng)的write方法,例如:writeTypedList、writeToParcel、writeInt等等;
到這里這個(gè)類就差不多結(jié)束了,其中對(duì)于參數(shù)數(shù)據(jù)流向的標(biāo)記,下文中再介紹,核心的東西其實(shí)就在onTransact中,而與其它的關(guān)聯(lián)則是通過asInterface聯(lián)系;
3、IPersonCount.Stub.Proxy類
其實(shí)這個(gè)在上文中有個(gè)簡單的說明,它的作用就是在asInterface時(shí)實(shí)例化一個(gè)對(duì)象返回,當(dāng)在客戶端調(diào)用相應(yīng)方法時(shí),先獲取傳入數(shù)據(jù)data,執(zhí)行transact方法,再處理一下reply用以響應(yīng),有返回值的則返回。接下來也依次描述一下各個(gè)成員以及方法的含義。
a、mRemote
這個(gè)就是asInterface中傳入的binder,這里存儲(chǔ)起來方便使用;
b、asBinder
返回binder;
c、getInterfaceDescriptor
返回描述與Stub一致;
d、實(shí)現(xiàn)接口中定義的方法
這里以addPerson為例,因?yàn)閿?shù)據(jù)流向標(biāo)記中inout會(huì)包含in和out兩種標(biāo)記,所以這里看到的是以inout數(shù)據(jù)流向的代碼,下文會(huì)逐一分析哪部分是干什么的。
public boolean addPerson(net.arvin.androidart.aidl.Person person) throws android.os.RemoteException {
//前兩行是初始化data和reply,data是用來傳遞參數(shù)數(shù)據(jù)的,reply是用于返回?cái)?shù)據(jù)以及寫入out流向的數(shù)據(jù)的
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
//這里定義result,會(huì)根據(jù)相應(yīng)方法定義對(duì)應(yīng)類型,如果沒有返回則不定義該變量
boolean _result;
try {
//這一步都有,將DESCRIPTOR作為令牌記錄下檔前讀取的是哪個(gè)Binder,是用來在onTransact時(shí)驗(yàn)證
_data.writeInterfaceToken(DESCRIPTOR);
//這一部分是in數(shù)據(jù)流向的操作,不同的對(duì)象寫入data的方式不同,如果只是out流向則不包含這一部分
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//這一部分就是調(diào)用遠(yuǎn)程binder中的onTransact方法,其方法中會(huì)直接調(diào)用onTransact方法,傳入的參數(shù)和上文中onTransact方法的參數(shù)含義一致
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
//這一部分就是從reply中獲得result,不同類型獲取方式不同,用于最后返回;
_result = (0 != _reply.readInt());
//這一部分就是out數(shù)據(jù)流向的操作,當(dāng)然也是不同類型,操作方式不同,如果只是in標(biāo)記則沒有這一部分,需要注意的是寫出時(shí)的順序要和寫入時(shí)數(shù)據(jù)的順序相同
if ((0 != _reply.readInt())) {
person.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
通過上邊方法的注釋,我覺得對(duì)于這個(gè)方法就差不多了,其實(shí)就是讀取數(shù)據(jù),調(diào)用遠(yuǎn)程方法,處理返回?cái)?shù)據(jù)。
這里再細(xì)看細(xì)看一下在onTransact方法中不同數(shù)據(jù)流向代碼會(huì)是怎樣,這里還是以addPerson為例,數(shù)據(jù)流向也為inout。
case TRANSACTION_addPerson: {
//這一部分很明顯就對(duì)應(yīng)著proxy中data的writeInterfaceToken
data.enforceInterface(DESCRIPTOR);
//這一部分就是in數(shù)據(jù)流向的操作,不同類型之行方法不同,如果只有out則只會(huì)初始化一個(gè)對(duì)象實(shí)例,若傳入的是空,對(duì)應(yīng)著proxy中data讀取就是0,這里也就賦值為空了
net.arvin.androidart.aidl.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = net.arvin.androidart.aidl.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//這一步是數(shù)據(jù)邏輯執(zhí)行的核心,會(huì)去調(diào)用我們在服務(wù)端實(shí)現(xiàn)的方法
boolean _result = this.addPerson(_arg0);
reply.writeNoException();
//這一步是將result寫入,如果沒有返回值則沒有這一步
reply.writeInt(((_result) ? (1) : (0)));
//這一步是out數(shù)據(jù)流向的操作,是將這里操作完的傳入的參數(shù)寫入到reply中,以便在proxy中寫出
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
//這里返回true表示調(diào)用成功
return true;
}
這樣聯(lián)通起來一看,就想通,目前來看就對(duì)其內(nèi)部數(shù)據(jù)存儲(chǔ)以及讀取時(shí)的規(guī)則不是很清楚,但是對(duì)于使用確實(shí)知道都會(huì)一一對(duì)應(yīng)有寫入就有寫出。
這里邊還有一個(gè)技術(shù)點(diǎn)就是調(diào)用時(shí),線程的切換問題,當(dāng)客戶端調(diào)用服務(wù)端的代碼時(shí)由于不知道遠(yuǎn)程操作是否耗時(shí),所以盡量都在子線程中調(diào)用,而服務(wù)端的執(zhí)行代碼,即使是耗時(shí)操作也可以不用開新的線程,因?yàn)槠浔旧硪呀?jīng)在Binder的線程池中調(diào)用了;而若是服務(wù)端要調(diào)用客戶端,則同樣的道理,只是反過來了,具體的例子會(huì)在最后實(shí)踐中應(yīng)用。
五、Permission
如果任何一個(gè)客戶端臉上了我們的服務(wù)端,而執(zhí)行了這些方法,顯然時(shí)不安全的,而我們也是可以通過一些方法去限制訪問,總結(jié)起來就兩點(diǎn)
- 通過Service連接時(shí)限制
- 通過調(diào)用onTransact時(shí)限制
而我們限制的點(diǎn),也是多種多樣,由于沒在項(xiàng)目中使用過,別人的意見是設(shè)置權(quán)限以及限制包名來限制訪問,接下來就依次說說具體的實(shí)現(xiàn)。
a、通過Service連接時(shí)限制
我們就以權(quán)限為例,這里現(xiàn)在清單文件中自定義一個(gè)permission:
<permission
android:name="net.arvin.androidart.permission.BinderPoolService"
android:protectionLevel="normal" />
然后我們在Service中,在onBind方法返回參數(shù)前作判斷,例如:
int check = checkCallingOrSelfPermission("net.arvin.androidart.permission.BinderPoolService");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
這樣如果在客戶端調(diào)用時(shí)沒有申明這個(gè)權(quán)限就只會(huì)返回空,這樣就完成了簡單的限制。除此之外對(duì)于Service的連接限制還有其它辦法,我還沒實(shí)際運(yùn)用過就不再介紹了,可以自行查詢。
b、通過調(diào)用onTransact時(shí)限制
從上文的介紹中可以知道,該返回如果返回false則就不會(huì)調(diào)用成功,所以我們就可以在不符合的條件返回false,方法也很簡單,我們只需要重寫onTransact方法中去判斷即可,例如:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check = checkCallingOrSelfPermission("net.arvin.androidart.permission.BinderPoolService");
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
if (!packageName.startsWith("net.arvin")) {
return false;
}
}
return super.onTransact(code, data, reply, flags);
}
邏輯也很簡單,就不介紹了,就是限制權(quán)限和包名,都通過就執(zhí)行我們剛才的代碼就可以了。
六、Best Practice
注:標(biāo)題黨了一下,畢竟我還沒在實(shí)踐中用過,都是看著別人這么介紹的,自己總結(jié)后也確實(shí)覺得這樣的實(shí)踐方式還不錯(cuò),所以才起了這個(gè)標(biāo)題
上面已經(jīng)簡單介紹過AIDL的使用以及客戶端和服務(wù)端的調(diào)用,當(dāng)時(shí)客戶端在調(diào)用服務(wù)端的時(shí)候都是直接調(diào)用,沒有開子線程,如果服務(wù)端執(zhí)行的操作是耗時(shí)較長的,則就會(huì)出現(xiàn)ANR了。而這一點(diǎn)也只需開線程就能解決就不再詳細(xì)介紹了,下面著重介紹的是:
- 服務(wù)端回調(diào)客戶端
- 連接池的使用
a、服務(wù)端回調(diào)客戶端
我們發(fā)現(xiàn)其實(shí)之前的調(diào)用都是單向的,往往有時(shí)我們希望服務(wù)端有的數(shù)據(jù)變化了能通知客戶端,而不用一直去調(diào)用才知道,這時(shí)候這種方法就派上用場了。
其實(shí)原理和在同一個(gè)進(jìn)程中時(shí)是一樣的,就是客戶端向服務(wù)端注冊一個(gè)回調(diào),而當(dāng)有更新的時(shí)候服務(wù)端再調(diào)用回調(diào)就可以了。
當(dāng)然這個(gè)回調(diào)也得使用AIDL來定義,保證定義的接口客戶端與服務(wù)端同步,如下:
// IOnNewPersonIn.aidl
package net.arvin.androidart.aidl;
import net.arvin.androidart.aidl.Person;
interface IOnNewPersonIn {
void onNewPersonIn(in Person newPerson);
}
// IPersonCount.aidl
package net.arvin.androidart.aidl;
import net.arvin.androidart.aidl.Person;
import net.arvin.androidart.aidl.IOnNewPersonIn;
interface IPersonCount {
boolean addPerson(in Person person);
List<Person> getPersons();
void registerListener(in IOnNewPersonIn listener);
void unregisterListener(in IOnNewPersonIn listener);
}
然后我們再服務(wù)端去實(shí)現(xiàn)這個(gè)注冊與反注冊,其中存儲(chǔ)用普通的List來存的話直接反注冊時(shí)無效的,要用RemoteCallbackList<E extends IInterface>來存,這樣才能在反注冊時(shí)取消掉,因?yàn)槠鋬?nèi)部是以binder為key,listener為value存儲(chǔ)的,具體的實(shí)現(xiàn)還沒去深究。還有一點(diǎn)需要注意的就是對(duì)于這個(gè)RemoteCallbackList的讀取,一定需要以beginBroadcast開始,finishBroadcast結(jié)束,中間遍歷使用getBroadcastItem獲取,這里就這一部分代如下:
@Override
public void registerListener(IOnNewPersonIn listener) throws RemoteException {
mListeners.register(listener);
}
@Override
public void unregisterListener(IOnNewPersonIn listener) throws RemoteException {
mListeners.unregister(listener);
}
private void onNewPersonIn(Person newPerson) {
int size = mListeners.beginBroadcast();
for (int i = 0; i < size; i++) {
IOnNewPersonIn onNewPersonIn = mListeners.getBroadcastItem(i);
try {
if (onNewPersonIn != null) {
onNewPersonIn.onNewPersonIn(newPerson);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
mListeners.finishBroadcast();
}
這里onNewPersonIn的調(diào)用需要在子線程,具體代碼中開了個(gè)線程去處理,這里就不展示了。
客戶端需要做的就是通過binder注冊這個(gè)回調(diào),然后再轉(zhuǎn)換到主線程中去操作,如下:
private IOnNewPersonIn.Stub onNewPersonIn = new IOnNewPersonIn.Stub() {
@Override
public void onNewPersonIn(Person newPerson) throws RemoteException {
Message message = mHandler.obtainMessage();
message.what = 0;
message.obj = newPerson;
mHandler.sendMessage(message);
}
};
private IPersonCount iPersonCount;
try {
if (iPersonCount != null) {
iPersonCount.registerListener(onNewPersonIn);
}
} catch (RemoteException e) {
e.printStackTrace();
}
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) {
Person person = (Person) msg.obj;
edAge.setText(person.getAge()+"");
edName.setText(person.getName());
}
}
這也是部分代碼,具體的操作也很簡單,連接得到IpersonCount后,再注冊回調(diào),回調(diào)中通過Handler處理消息轉(zhuǎn)化到主線程再顯示出來。
這樣這一部分的大體邏輯就已經(jīng)出來了,最主要就是Listener的存儲(chǔ)那里以及回調(diào)的處理要轉(zhuǎn)換到主線程。
b、連接池的使用
如果你按照這種例子多寫幾個(gè)會(huì)發(fā)現(xiàn),怎么一個(gè)連接的就得寫一個(gè)Service,這豈不是不好很繁瑣,而且Service開多了對(duì)性能也有影響,所以我們引入了中間人,幫助我們通過code去連接我們需要的AIDL對(duì)應(yīng)的Binder,而Binder我們通過AIDL就已經(jīng)實(shí)現(xiàn)了大多的邏輯,只需要再去實(shí)現(xiàn)定義的具體方法即可。
首先定義AIDL接口
// IBinderPool.aidl
package net.arvin.androidart.aidl;
// Declare any non-default types here with import statements
interface IBinderPool {
/**
* @param binderCode, the unique token of specific Binder<br/>
* @return specific Binder who's token is binderCode.
*/
IBinder queryBinder(int binderCode);
}
實(shí)現(xiàn)生成的Binder
public class BinderPoolImpl extends IBinderPool.Stub {
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_PERSON_COUNT = 1;
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_PERSON_COUNT: {
binder = new PersonCountImpl();
break;
}
case BINDER_COMPUTE: {
binder = new IntegerAddImpl();
break;
}
default:
break;
}
return binder;
}
}
邏輯很簡單,就是根據(jù)這里提供的binderCode去查詢返回相應(yīng)的的binder實(shí)例。同理,我們可以把字簽在PersonCountService中的binder提取出來。
實(shí)現(xiàn)BinderPoolService
public class BinderPoolService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new BinderPoolImpl();
}
}
對(duì)于配置那些就不貼出來了。
這樣服務(wù)端的連接池相關(guān)代碼就已經(jīng)寫完了,接下來再看看客戶端的。
由于連接池是為了通用,為了避免重復(fù)創(chuàng)建的開銷,所以這里會(huì)把連接池作為單例模式使用。代碼如下:
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_PERSON_COUNT = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static BinderPool sInstance;
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
Intent service = new Intent();
service.setComponent(new ComponentName("net.arvin.androidart",
"net.arvin.androidart.multiProcess.BinderPoolService"));
mContext.bindService(service, mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
}
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
}
代碼也不多,很好理解,里邊主要就是三個(gè)事:
- 實(shí)現(xiàn)單例
- 連接服務(wù)端獲取Binder
- 斷開后重連
前兩個(gè)技術(shù)點(diǎn)沒什么好說的都差不多,第三個(gè)重連機(jī)制,使用的是Binder的DeathRecipient接口回調(diào),使用方法也很簡單,就是在獲取到binder后linkToDeath,其中傳入DeathRecipient接口的實(shí)現(xiàn),若是被斷開連接后回調(diào),先調(diào)用unlinkToDeath,再將binder設(shè)置為空,再重新連接,就實(shí)現(xiàn)了整個(gè)過程。
然后是客戶端對(duì)BinderPool的使用
mBinderPool = BinderPool.getInstance(this);
iPersonCount = IPersonCount.Stub.asInterface(mBinderPool.queryBinder(BinderPool.BINDER_PERSON_COUNT));
第一句獲取Binder池實(shí)例,第二句就是通過binder池獲取對(duì)應(yīng)的binder,再獲取到相應(yīng)的接口,這樣就能正常使用了,這里就不貼具體調(diào)用代碼了。
到這里,整篇就結(jié)束了,若有差錯(cuò),請多多指教。