最近一些在準(zhǔn)備各種校招面試匣椰,在安卓面試的時(shí)候,總會(huì)被問到AIDL,跨進(jìn)程通信的知識(shí)绩脆,由于平時(shí)基本沒有AIDL跨進(jìn)程通信的需求(一般用廣播和contentprovider),所以AIDL這一塊一直很不熟悉橄抹,于是馬上去查閱了相關(guān)的資料靴迫,引發(fā)了一些思考。
跨進(jìn)程
為什么要有跨進(jìn)程通信這個(gè)概念呢楼誓?我們學(xué)習(xí)操作系統(tǒng)時(shí)都知道玉锌,進(jìn)程是系統(tǒng)分配資源的基本單位(雖然不太嚴(yán)謹(jǐn)),每個(gè)進(jìn)程都有自己獨(dú)立的空間地址疟羹,這個(gè)地址是邏輯地址主守,同一個(gè)進(jìn)程內(nèi)的線程共用這些資源,所以我們平時(shí)編程的時(shí)候用多線程去訪問同一個(gè)對(duì)象榄融,是沒有問題的参淫,從java的角度來說就是它們?cè)L問到的是同一個(gè)堆。但是多進(jìn)程就不一樣了愧杯,一個(gè)進(jìn)程里面的對(duì)象是不能直接被另一個(gè)進(jìn)程訪問到的涎才,多進(jìn)程應(yīng)用要正常工作,必需要有一個(gè)方式民效,來實(shí)現(xiàn)進(jìn)程之間的溝通憔维。
通信與Binder
什么是通信涛救,就是信息的傳遞或流動(dòng),在計(jì)算機(jī)的世界里业扒,所有的信息都是二進(jìn)制流检吆,如果你實(shí)現(xiàn)了將一些字節(jié)從一個(gè)進(jìn)程發(fā)送給另一個(gè)進(jìn)程,那就是實(shí)現(xiàn)了跨進(jìn)程通信程储,很明顯Socket就是一種跨進(jìn)程通信的方式蹭沛,只不過Socket從發(fā)送到接收,期間經(jīng)過了多次數(shù)據(jù)的拷貝章鲤,效率低摊灭。Linux上還有其他的跨進(jìn)程通信方式,它們各有優(yōu)缺點(diǎn)败徊,但谷歌在Android上提出了一個(gè)新的跨進(jìn)程通信的方案Binder帚呼。什么是Binder呢?要把這個(gè)東西說清楚皱蹦,涉及到的內(nèi)容太多太深了煤杀,想要深入學(xué)習(xí)的推薦一個(gè)博客Binder系列—開篇,里面有圖有文共花了十篇文章從源碼的角度分析了一遍,總得來說沪哺,它就是一種通過內(nèi)存拷貝的方式實(shí)現(xiàn)了從一個(gè)進(jìn)程向另一個(gè)進(jìn)程發(fā)送字節(jié)的技術(shù)(好象說了等于沒說)
AIDL
常說AIDL是安卓的一種跨進(jìn)程通信的方式沈自,其實(shí)不太嚴(yán)謹(jǐn),應(yīng)該說Binder才是一種跨進(jìn)程通信的方式辜妓,而AIDL是Binder的一種具體應(yīng)用枯途,有點(diǎn)類似于網(wǎng)絡(luò)中的傳輸層和應(yīng)用層,IBinder有自己的通信協(xié)議籍滴,負(fù)責(zé)建立和維護(hù)連接酪夷,發(fā)送數(shù)據(jù),而AIDL則定義了更上一層的傳輸內(nèi)容异逐,而像ContentProvider捶索、廣播同樣是利用IBinder進(jìn)行工作的。
那么AIDL到底是做什么的呢灰瞻?
用一句話來總結(jié)就是--接口的跨進(jìn)程調(diào)用腥例。舉個(gè)栗子,進(jìn)程A調(diào)用 Book getBook(int id)方法酝润,另一個(gè)進(jìn)程B響應(yīng)這個(gè)方法并返回內(nèi)容Book燎竖,A拿到從B過來的這個(gè)Book,如果愿意要销,不只A可以通過getBook獲取Book對(duì)象构回,其他進(jìn)程CDE同樣可以調(diào)用這個(gè)方法來獲取來自B的對(duì)象,典型的C/S模式。
實(shí)現(xiàn)之前
先看在這過程中兩者之間傳遞了哪些信息纤掸。首先A要讓B知道脐供,你調(diào)用的是getBook這個(gè)方法,而不是getName或者其它方法,其次方法的參數(shù)id也要傳遞給B借跪。而對(duì)B來說政己,要把找到的book對(duì)象傳遞給A。這就引出了一個(gè)問題掏愁,對(duì)象和方法是一個(gè)編程的概念歇由,我們只可以傳字節(jié),那怎么用二進(jìn)制數(shù)據(jù)來表達(dá)對(duì)象和方法呢果港?
- 二進(jìn)制與對(duì)象
對(duì)象與二進(jìn)制數(shù)據(jù)的轉(zhuǎn)換沦泌,也就是我們常說的對(duì)象的序列化和反序列化,安卓中的具體的方案就是Parcelable接口辛掠。Parcelable的具體思想是谢谦,它不關(guān)心你的對(duì)象有哪些成員,而是給你提供了一個(gè)Parcel對(duì)象公浪,你可以把Parcel對(duì)象當(dāng)作一個(gè)數(shù)據(jù)包一樣他宛,當(dāng)你要序列化的時(shí)候,往Parcel里面寫對(duì)象的成員值欠气,具體寫哪些成員、成員的順序等自己決定镜撩,因?yàn)榈綍r(shí)候從Parcel讀數(shù)據(jù)也是你自己來讀预柒,Parcel不關(guān)心你的數(shù)據(jù)結(jié)構(gòu)。而另一端袁梗,也就是需要反序列化的一端宜鸯,需要你根據(jù)之前寫的順序去讀Parcel里的值并利用這些值去構(gòu)造你的對(duì)象。下面是一個(gè)實(shí)現(xiàn)了Parcelable的Book類
public class Book implements Parcelable {
public int id;
public String name;
protected Book(Parcel in)
{
//在構(gòu)造的時(shí)候遮怜,從Parcel里面讀成員值淋袖,順序和寫的時(shí)候一樣
id = in.readInt();
name = in.readString();
}
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)
{
//在序列化的時(shí)候被調(diào)用,往Parcel里面寫數(shù)據(jù)
dest.writeInt(id);
dest.writeString(name);
}
}
- 表示方法
方法的二進(jìn)制表達(dá)就更簡(jiǎn)單了锯梁,方法說白了就是一個(gè)符號(hào)即碗,那就可以直接用數(shù)字0、1陌凳、2...去代表每個(gè)方法剥懒,而數(shù)據(jù)的含義由我們自己來決定。
用Binder通信
IBinder是一個(gè)接口(這里指的是java層的IBinder)合敦,當(dāng)我們綁定另一個(gè)進(jìn)程的Service的時(shí)候初橘,會(huì)在onBind方法里面返回一個(gè)IBinder對(duì)象,而客戶端也會(huì)在onServiceConnected拿到一個(gè)代表服務(wù)的IBinder對(duì)象,但這兩個(gè)對(duì)象不是同一個(gè)對(duì)象來的保檐,畢竟來自兩個(gè)進(jìn)程耕蝉,IBinder有一個(gè)核心方法,注意方法的參數(shù)
public boolean transact(int code, Parcel data, Parcel reply, int flags)
// code 用來代表方法
// data 可以裝方法的參數(shù)
// reply 可以裝方法的返回值
進(jìn)程之間就是通過調(diào)用IBinder的這個(gè)方法來進(jìn)行通信的夜只,雖然兩個(gè)IBinder不是同一個(gè)對(duì)象赔硫,但ServiceManger負(fù)責(zé)將它們關(guān)聯(lián)起來,當(dāng)我們本地調(diào)用transact方法時(shí)盐肃,遠(yuǎn)程的IBinder的transact就會(huì)被調(diào)用爪膊,其中間經(jīng)過了ServiceManger,顯然這也是一次跨進(jìn)程調(diào)用砸王,這樣一來就好像為了實(shí)現(xiàn)跨進(jìn)程調(diào)用推盛,就要先實(shí)現(xiàn)跨進(jìn)程調(diào)用,這就要涉及到最初的ServiceManger是怎么跑起來的谦铃,等以后把IBinder的native層吃透了再寫一篇文章來講了耘成。
現(xiàn)在,兩個(gè)進(jìn)程可以通過transact方法來通信了驹闰,可是怎么調(diào)用getBook呢瘪菌?現(xiàn)在來實(shí)現(xiàn)Book getBook(int id)的整個(gè)過程:調(diào)用Ibinder的transact方法,用一個(gè)數(shù)字(比如2)代表getBook嘹朗,即參數(shù)code為2师妙,把id寫進(jìn)data里面,服務(wù)端IBinder響應(yīng)transact方法屹培,通過code知道調(diào)用的是getBook默穴,從data中取出id并找到對(duì)應(yīng)的Book,然后將Book寫進(jìn)reply里面褪秀,客戶端再?gòu)膔eply里面讀出返回值蓄诽,再將返回值構(gòu)造成Book對(duì)象。整個(gè)getBook過程就完成了媒吗。
可是這樣也太麻煩了吧仑氛,調(diào)用一個(gè)接口就這么麻煩,如果接口很多豈不是更麻煩闸英,而且怎么保證通信的規(guī)范,比如怎么返回Null值自阱,怎么返回異常?
AIDL文件定義接口
剛才說到沛豌,調(diào)用一個(gè)接口太麻煩了赃额,而且每個(gè)調(diào)用邏輯都是差不多的,那么有沒有辦法可以少寫代碼跳芳,我只要直接調(diào)用getBook()就可以幫我做完所有工作呢?那就是用AIDL文件來定義接口飞盆。當(dāng)我們用AIDL文件來描述我們的接口時(shí),SDK就會(huì)自動(dòng)幫我們生成相應(yīng)的Java代碼吓歇,主要內(nèi)容就是實(shí)現(xiàn)我剛才說的繁瑣流程。所以說城看,AIDL文件并不是什么高級(jí)的東西,只是用來描述我們的接口测柠,然后sdk會(huì)根據(jù)AIDL文件來生成Java代碼,如果你不用AIDL文件缘滥,直接手寫代碼也是可以的。下面看一個(gè)簡(jiǎn)單的例子
//book.aidl
package com.xxx.aidlsample.aidl;
parcelable Book;
// BookManagerInterface.aidl
package com.xxx.aidlsample.aidl;
import com.xxx.aidlsample.aidl.Book;
interface BookManagerInterface {
Book getBook(int id);
}
定義兩個(gè)aidl文件并編譯后朝扼,自動(dòng)生成了一個(gè)BookManagerInterface類,這個(gè)類代碼十分好理解吟税,下面只說關(guān)鍵的幾個(gè)地方凹耙。
首先,它為getBook方法生成了一個(gè)靜態(tài)常量肠仪,也就是TRANSACTION_getBook代表getBook這個(gè)方法,用作transact的code參數(shù)
static final int TRANSACTION_getBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
生成一個(gè)描述字符串备典,作為IBinder的代表
private static final java.lang.String DESCRIPTOR = "com.xxx.aidlsample.aidl.BookManagerInterface";
BookManagerInterface有一個(gè)sub的內(nèi)部抽象類(抽象方法為getBook)异旧,作為服務(wù)端的IBinder實(shí)現(xiàn),我們?cè)趕ervice的onBind中返回的就是sub的實(shí)現(xiàn)類提佣∷庇迹看它的transact方法實(shí)現(xiàn)
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code) //匹配方法
{
...
case TRANSACTION_getBook: //如果是getBook
{
data.enforceInterface(DESCRIPTOR);//驗(yàn)證描述符,確卑杵粒客戶端和服務(wù)端是對(duì)應(yīng)的
int _arg0;
_arg0 = data.readInt();//讀方法參數(shù)潮针,book id
com.dzy.aidlsample.aidl.Book _result = this.getBook(_arg0);//調(diào)用真正的getBook邏輯,這里的getBook是個(gè)抽象方法
reply.writeNoException();//表示沒有異常
if ((_result != null))
{
reply.writeInt(1);//表示結(jié)果不為null
//將找到的Book寫入reply
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else
{
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
再看客戶端的代理類倚喂,同樣是一個(gè)內(nèi)部類每篷,當(dāng)我們調(diào)用代理類的getBook
public com.dzy.aidlsample.aidl.Book getBook(int id) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.dzy.aidlsample.aidl.Book _result;
try
{
_data.writeInterfaceToken(DESCRIPTOR);//寫入描述符
_data.writeInt(id); //寫入?yún)?shù) id
//調(diào)用transact瓣戚,code就是getBook的指示常量
mRemote.transact(Stub.TRANSACTION_getBook, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) //如果返回結(jié)果不為null
{
//通過reply構(gòu)造返回的對(duì)象
_result = com.dzy.aidlsample.aidl.Book.CREATOR.createFromParcel(_reply);
} else
{
_result = null;
}
} finally
{
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到data和reply參數(shù),客戶端這邊是怎么寫入數(shù)據(jù)的焦读,服務(wù)端就怎么讀出來子库,一一對(duì)應(yīng),就像我們的網(wǎng)絡(luò)協(xié)議一樣矗晃。
最后總結(jié)一下仑嗅,AIDL的核心是Binder,我們通過AIDL文件來描述接口张症,使得到一個(gè)封裝好的IBinder代理仓技,來實(shí)現(xiàn)接口的遠(yuǎn)程調(diào)用。Binder是Android里面一個(gè)很重要的概念俗他,是Android各種ManagerService比如AMS脖捻、PMS的工作基礎(chǔ),如果要深入理解Android系統(tǒng)拯辙,就不得不理解Binder的原理郭变。本文跳過了很多核心的內(nèi)容,只講JAVA層的一些原理涯保。