談談對AIDL的理解
最近一些在準備各種校招面試,在安卓面試的時候,總會被問到AIDL,跨進程通信的知識,由于平時基本沒有AIDL跨進程通信的需求(一般用廣播和contentprovider),所以AIDL這一塊一直很不熟悉,于是馬上去查閱了相關的資料,引發了一些思考。
跨進程
為什么要有跨進程通信這個概念呢?我們學習操作系統時都知道,進程是系統分配資源的基本單位(雖然不太嚴謹),每個進程都有自己獨立的空間地址,這個地址是邏輯地址,同一個進程內的線程共用這些資源,所以我們平時編程的時候用多線程去訪問同一個對象,是沒有問題的,從java的角度來說就是它們訪問到的是同一個堆。但是多進程就不一樣了,一個進程里面的對象是不能直接被另一個進程訪問到的,多進程應用要正常工作,必需要有一個方式,來實現進程之間的溝通。
通信與IBinder
什么是通信,就是信息的傳遞或流動,在計算機的世界里,所有的信息都是二進制流,如果你實現了將一些字節從一個進程發送給另一個進程,那就是實現了跨進程通信,很明顯Socket就是一種跨進程通信的方式,只不過Socket從發送到接收,期間經過了多次數據的拷貝,效率低。Linux上還有其他的跨進程通信方式,它們各有優缺點,但谷歌在Android上提出了一個新的跨進程通信的方案IBinder。什么是IBinder呢?要把這個東西說清楚,涉及到的內容太多太深了,想要深入學習的推薦一個博客 Binder系列—開篇 ,里面有圖有文共花了十篇文章從源碼的角度分析了一遍, 總得來說,它就是一種通過內存拷貝的方式實現了從一個進程向另一個進程發送字節的技術 (好象說了等于沒說)
AIDL
常說AIDL是安卓的一種跨進程通信的方式,其實不太嚴謹,應該說IBinder才是一種跨進程通信的方式,而AIDL是IBinder的一種具體應用,有點類似于網絡中的傳輸層和應用層,IBinder有自己的通信協議,負責建立和維護連接,發送數據,而AIDL則定義了更上一層的傳輸內容,而像ContentProvider、廣播同樣是利用IBinder進行工作的。
那么AIDL到底是做什么的呢?
用一句話來總結就是-- 接口的跨進程調用 。舉個栗子,進程A調用 Book getBook(int id)方法,另一個進程B響應這個方法并返回內容Book,A拿到從B過來的這個Book,如果愿意,不只A可以通過getBook獲取Book對象,其他進程CDE同樣可以調用這個方法來獲取來自B的對象,典型的C/S模式。
實現之前
先看在這過程中兩者之間傳遞了哪些信息。首先A要讓B知道,你調用的是getBook這個方法,而不是getName或者其它方法,其次方法的參數id也要傳遞給B。而對B來說,要把找到的book對象傳遞給A。這就引出了一個問題,對象和方法是一個編程的概念,我們只可以傳字節,那怎么用二進制數據來表達對象和方法呢?
-
二進制與對象
對象與二進制數據的轉換,也就是我們常說的對象的 序列化和反序列化 ,安卓中的具體的方案就是Parcelable接口。Parcelable的具體思想是,它不關心你的對象有哪些成員,而是給你提供了一個Parcel對象,你可以把Parcel對象當作一個 數據包 一樣,當你要序列化的時候,往Parcel里面寫對象的成員值,具體寫哪些成員、成員的順序等自己決定,因為到時候從Parcel讀數據也是你自己來讀,Parcel不關心你的數據結構。而另一端,也就是需要反序列化的一端,需要你根據之前寫的順序去讀Parcel里的值并利用這些值去構造你的對象。下面是一個實現了Parcelable的Book類
public class Book implements Parcelable { public int id; public String name; protected Book(Parcel in) { //在構造的時候,從Parcel里面讀成員值,順序和寫的時候一樣 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) { //在序列化的時候被調用,往Parcel里面寫數據 dest.writeInt(id); dest.writeString(name); } }
-
表示方法
方法的二進制表達就更簡單了,方法說白了就是一個符號,那就可以直接用數字0、1、2...去代表每個方法,而數據的含義由我們自己來決定。
用IBinder通信
IBinder是一個接口(這里指的是java層的IBinder),當我們綁定另一個進程的Service的時候,會在onBind方法里面返回一個IBinder對象,而客戶端也會在onServiceConnected拿到一個代表服務的IBinder對象,但這兩個對象不是同一個對象來的,畢竟來自兩個進程,IBinder有一個核心方法,注意方法的參數
public boolean transact(int code, Parcel data, Parcel reply, int flags)
// code 用來代表方法
// data 可以裝方法的參數
// reply 可以裝方法的返回值
進程之間就是通過調用IBinder的這個方法來進行通信的,雖然兩個IBinder不是同一個對象,但ServiceManger負責將它們關聯起來,當我們本地調用transact方法時,遠程的IBinder的transact就會被調用,其中間經過了ServiceManger,顯然這也是一次跨進程調用,這樣一來就好像為了實現跨進程調用,就要先實現跨進程調用,這就要涉及到最初的ServiceManger是怎么跑起來的,等以后把IBinder的native層吃透了再寫一篇文章來講了。
現在,兩個進程可以通過transact方法來通信了,可是怎么調用getBook呢?現在來實現Book getBook(int id)的整個過程: 調用Ibinder的transact方法,用一個數字(比如2)代表getBook,即參數code為2,把id寫進data里面,服務端IBinder響應transact方法,通過code知道調用的是getBook,從data中取出id并找到對應的Book,然后將Book寫進reply里面,客戶端再從reply里面讀出返回值,再將返回值構造成Book對象 。整個getBook過程就完成了。
可是這樣也太麻煩了吧,調用一個接口就這么麻煩,如果接口很多豈不是更麻煩,而且怎么保證通信的規范,比如怎么返回Null值,怎么返回異常?
AIDL文件定義接口
剛才說到,調用一個接口太麻煩了,而且每個調用邏輯都是差不多的,那么有沒有辦法可以少寫代碼,我只要直接調用getBook()就可以幫我做完所有工作呢?那就是用AIDL文件來定義接口。當我們用AIDL文件來描述我們的接口時,SDK就會自動幫我們生成相應的Java代碼,主要內容就是實現我剛才說的繁瑣流程。所以說,AIDL文件并不是什么高級的東西,只是用來描述我們的接口,然后sdk會根據AIDL文件來生成Java代碼,如果你不用AIDL文件,直接手寫代碼也是可以的。下面看一個簡單的例子
//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);
}
定義兩個aidl文件并編譯后,自動生成了一個BookManagerInterface類,這個類代碼十分好理解,下面只說關鍵的幾個地方。
首先,它為getBook方法生成了一個靜態常量,也就是TRANSACTION_getBook代表getBook這個方法,用作transact的code參數
static final int TRANSACTION_getBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
生成一個描述字符串,作為IBinder的代表
private static final java.lang.String DESCRIPTOR = "com.xxx.aidlsample.aidl.BookManagerInterface";
BookManagerInterface有一個sub的內部抽象類(抽象方法為getBook),作為服務端的IBinder實現,我們在service的onBind中返回的就是sub的實現類。看它的transact方法實現
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);//驗證描述符,確保客戶端和服務端是對應的
int _arg0;
_arg0 = data.readInt();//讀方法參數,book id
com.dzy.aidlsample.aidl.Book _result = this.getBook(_arg0);//調用真正的getBook邏輯,這里的getBook是個抽象方法
reply.writeNoException();//表示沒有異常
if ((_result != null))
{
reply.writeInt(1);//表示結果不為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);
}
再看客戶端的代理類,同樣是一個內部類,當我們調用代理類的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); //寫入參數 id
//調用transact,code就是getBook的指示常量
mRemote.transact(Stub.TRANSACTION_getBook, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) //如果返回結果不為null
{
//通過reply構造返回的對象
_result = com.dzy.aidlsample.aidl.Book.CREATOR.createFromParcel(_reply);
} else
{
_result = null;
}
} finally
{
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到data和reply參數,客戶端這邊是怎么寫入數據的,服務端就怎么讀出來,一一對應,就像我們的網絡協議一樣。
最后總結一下,AIDL的核心是IBinder,我們通過AIDL文件來描述接口,使得到一個封將好的IBinder代理,來實現接口的遠程調用。IBinder是Android里面一個很重要的概念,是Android各種ManagerService比如AMS、PMS的工作基礎,如果要深入理解Android系統,就不得不理解IBinder的原理。
來自:http://www.jianshu.com/p/4fb29c5012de