AIDL:Android Interface Definition Language,即 Android 接口定義語言男图。
AIDL 是什么
Android 系統(tǒng)中的進(jìn)程之間不能共享內(nèi)存,因此磺送,需要提供一些機(jī)制在不同進(jìn)程之間進(jìn)行數(shù)據(jù)通信车份。
為了使其他的應(yīng)用程序也可以訪問本應(yīng)用程序提供的服務(wù)谋减,Android 系統(tǒng)采用了遠(yuǎn)程過程調(diào)用(Remote Procedure Call,RPC)方式來實(shí)現(xiàn)扫沼。與很多其他的基于 RPC 的解決方案一樣出爹,Android 使用一種接口定義語言(Interface Definition Language,IDL)來公開服務(wù)的接口缎除。我們知道 Android 四大組件中的 3 種(Activity严就、BroadcastReceiver和ContentProvider)都可以進(jìn)行跨進(jìn)程訪問,另外一種 Android 組件 Service 同樣可以器罐。因此梢为,可以將這種可以跨進(jìn)程訪問的服務(wù)稱為 AIDL(Android Interface Definition Language)服務(wù)。
在介紹 AIDL 的使用以及其它特性前轰坊,我們先來了解下 AIDL 的核心——Binder铸董。
Android 的 Binder 機(jī)制淺析
看過一些關(guān)于 Binder 的文章,總得來說 Binder 機(jī)制的底層實(shí)現(xiàn)很復(fù)雜肴沫,相當(dāng)復(fù)雜粟害,要完全搞清楚,得花大量的時(shí)間颤芬。從某種角度來說我磁,個(gè)人覺得孽文,對于 Binder,我們只需要了解其上層原理以及使用方法即可夺艰。
直觀來看,從代碼的角度來說沉衣,Binder 是 Android 系統(tǒng)源碼中的一個(gè)類郁副,它實(shí)現(xiàn)了 IBinder 接口;從 IPC 角度來說豌习,Binder 是 Android 中的一種跨進(jìn)程通信方式存谎;從 Android Framework 角度來講,Binder 是 ServiceManager 連接各種 Manager(ActivityManager肥隆、WindowManager 等等)和相應(yīng) ManagerService 的橋梁既荚;從 Android 應(yīng)用層來說,Binder 是客戶端和服務(wù)端進(jìn)行通信的媒介栋艳,當(dāng) bindService 的時(shí)候恰聘,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的 Binder 對象,通過這個(gè) Binder 對象吸占,客戶端就可以和服務(wù)端進(jìn)行通信晴叨,這里的服務(wù)包括普通服務(wù)和基于 AIDL 的服務(wù)。
接下來矾屯,我們通過一個(gè) AIDL 示例兼蕊,來分析 Binder 的工作機(jī)制。在工程目錄中新建一個(gè)名為 aidl 的 package件蚕,然后新建 Book.Java孙技、Book.aidl(創(chuàng)建此文件時(shí),as 會(huì)提示已存在排作,需要先用其它命令牵啦,創(chuàng)建成功后再重命名為 Book.aidl )和 IBookManager.aidl,代碼如下:
// Book.java
package com.cy.ipcsample.aidl;
import android.os.Parcel;
import android.os.Parcelable;
/**
* 數(shù)據(jù)類
* @author cspecialy
* @version v1.0.0
* @date 2018/5/14 21:38
*/
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = 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 parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
'}';
}
}
// Book.aidl
package com.cy.ipcsample.aidl;
parcelable Book;
// IBookManager.aidl
package com.cy.ipcsample.aidl;
import com.cy.ipcsample.aidl.Book;
import com.cy.ipcsample.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
創(chuàng)建好三個(gè)文件之后纽绍,編譯一下蕾久,as 會(huì)在 app/build/generated/source/aidl/debug
目錄下的 com.cy.ipcsample 包中生成一個(gè)名為 IBookManager.Java 的類,如下圖所示:
這是系統(tǒng)生成的 Binder 類拌夏,接下來我們要利用這個(gè)類來分析 Binder 的工作原理僧著。其代碼如下:(生成的代碼格式很亂,可以格式化代碼之后看)
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample
* \\aidl\\IBookManager.aidl
*/
package com.cy.ipcsample.aidl;
public interface IBookManager extends android.os.IInterface {
public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException;
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager {
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.cy.ipcsample.aidl.IBookManager))) {
return ((com.cy.ipcsample.aidl.IBookManager) iin);
}
return new com.cy.ipcsample.aidl.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws
android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.cy.ipcsample.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.cy.ipcsample.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.cy.ipcsample.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.cy.ipcsample.aidl.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
} @Override
public android.os.IBinder asBinder() {
return mRemote;
}
@Override
public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.cy.ipcsample.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
}
}
可見障簿,系統(tǒng)為我們生成了一個(gè) IBookManager 接口盹愚,它繼承了 IInterface 這個(gè)接口,所以這里要注意下站故,所有可以在 Binder 中傳輸?shù)慕涌诮耘拢夹枰^承 IInterface 接口毅舆。
接下來分析下,該類的工作機(jī)制愈腾。仔細(xì)看憋活,可以發(fā)現(xiàn),該類主要分成三個(gè)部分:
- 定義自身方法( getBookList 方法和 addBook 方法)虱黄;
- 內(nèi)部靜態(tài)類-Stub悦即,該類繼承 Binder,同時(shí)實(shí)現(xiàn) IBookManager 接口
- Stub的內(nèi)部代理類-Proxy橱乱,也實(shí)現(xiàn) IBookManager 接口
第一部分我們不用管它辜梳,主要看 Stub 類和 Proxy 類。在 Stub 中泳叠,首先聲明了兩個(gè)用于標(biāo)識(shí) IBookManager 方法的整型變量作瞄,這兩個(gè)變量用于標(biāo)識(shí)在 transact 過程中客戶端所請求的是哪個(gè)方法。接著危纫,是 asInterface 方法宗挥,該方法用于將服務(wù)端的 Binder 對象轉(zhuǎn)換成客戶端所需的 AIDL 接口類型的對象,該方法通過調(diào)用 Binder 的 queryLocalInterface 方法叶摄,判斷客戶端和服務(wù)端是否處于同一進(jìn)程属韧,如果客戶端、服務(wù)端處于同一進(jìn)程蛤吓,那么此方法直接返回服務(wù)端的 Stub宵喂,否則,返回 Stub 的代理對象 Proxy会傲。queryLocalInterface 實(shí)現(xiàn)如下:
/**
* Use information supplied to attachInterface() to return the
* associated IInterface if it matches the requested
* descriptor.
*/
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
mOwner 是在 Stub 構(gòu)造函數(shù)中傳進(jìn)去的 this 參數(shù)锅棕。
接下來是 Stub 的代理類 Stub.Proxy,由上面的分析可知淌山,Stub.Proxy 是運(yùn)行在客戶端的(由 asInterface 方法返回給客戶端的對象)裸燎,Stub.Proxy對象創(chuàng)建后,持有服務(wù)端的 Binder 對象泼疑,用于客戶端請求時(shí)調(diào)用服務(wù)端方法進(jìn)行遠(yuǎn)程調(diào)用德绿。客戶端在向服務(wù)端發(fā)起請求時(shí)退渗,調(diào)用 Stub.Proxy 的相應(yīng)方法移稳,Stub.Proxy 方法的流程如下:
- 首先創(chuàng)建該方法所需要的輸入型 Parcel 對象 _data、輸出型對象 _reply 和返回值對象(如果有)会油;
- 然后把該方法的參數(shù)信息寫入 _data 中(如果有)个粱;
- 接著調(diào)用 transact 方法進(jìn)行 RPC(遠(yuǎn)程過程調(diào)用)請求,同時(shí)當(dāng)前線程掛起翻翩;
- 然后在 transact 方法通過服務(wù)端的 Binder 對象調(diào)用服務(wù)端的 onTransact 方法都许,即 Stub 中的 onTransact 方法稻薇;
- onTransact 方法返回后,當(dāng)前線程繼續(xù)執(zhí)行胶征,并從 _reply 中取出 RPC 過程返回的結(jié)果塞椎;
- 最后返回 _reply 中的數(shù)據(jù)(如果客戶端請求方法需要返回值)。
以上睛低,就是系統(tǒng)生成的 IBookManager 的工作過程忱屑,需要注意下,服務(wù)端的 onTransact 方法是運(yùn)行在 Binder 線程池中的暇昂。由于 IBookManager.Stub 類繼承 Binder,所以上述分析即 Binder 的工作機(jī)制伴嗡,簡單總結(jié)下:
- 在客戶端和服務(wù)端連接時(shí)(一般通過 bindService 方法)急波,服務(wù)端通過 asInterface 方法給客戶端返回一個(gè) IInterface 接口類型的對象(如果客戶端和服務(wù)端在同一個(gè)進(jìn)程,則返回服務(wù)端的 Binder 對象瘪校,否則返回 服務(wù)端 Binder 對象的代理)澄暮;
- 客戶端通過該對象向服務(wù)端進(jìn)行請求,如果客戶端和服務(wù)端不在同一個(gè)進(jìn)程阱扬,則通過該對象所代理的 Binder 對象進(jìn)行 RPC 請求泣懊,否則,直接通過 Binder 對象調(diào)用服務(wù)端相應(yīng)方法麻惶。
或者參考下圖理解下:
由此可見馍刮,Binder 在 AIDL 中承載著重要的職能,是 AIDL 的核心窃蹋,理解了 Binder 的工作機(jī)制卡啰,其實(shí)在很多方面都很有用。
AIDL 的使用
一套 AIDL 服務(wù)搭建的步驟如下:
- 創(chuàng)建 .aidl 文件警没,系統(tǒng)生成相應(yīng)的繼承 IInterface 的接口類(暴露給客戶端的接口)匈辱。
- 創(chuàng)建一個(gè) Service(服務(wù)端),實(shí)現(xiàn) .aidl 文件中的接口杀迹。
- 創(chuàng)建客戶端亡脸,綁定服務(wù)端的 Service。
- 客戶端綁定服務(wù)端成功后树酪,將服務(wù)端返回的 Binder 對象轉(zhuǎn)成 AIDL 接口所屬的 IInterface 類型浅碾。調(diào)用 AIDL 中的方法,實(shí)現(xiàn)和服務(wù)端的通信嗅回。
接下來及穗,我們使用上面的 IBookManager 來實(shí)現(xiàn) AIDL。
創(chuàng)建 .aidl 文件
直接使用上面創(chuàng)建好的 Book.aidl绵载、IBookManager.aidl 文件即可-
創(chuàng)建服務(wù)端
創(chuàng)建一個(gè) Service埂陆,命名為 BookManagerService苛白,代碼如下:package com.cy.ipcsample.aidl import android.app.Service import android.content.Intent import android.os.IBinder import android.os.RemoteCallbackList import android.util.Log import java.util.concurrent.CopyOnWriteArrayList class BookManagerService : Service() { private val TAG = "BookManagerService" private val mBookList = CopyOnWriteArrayList<Book>() private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>() /** * 實(shí)現(xiàn) AIDL 接口的 Binder 對象,客戶端綁定服務(wù)端時(shí)焚虱,直接返回此 Binder 對象 */ private val mBinder = object : IBookManager.Stub() { override fun getBookList(): MutableList<Book> { return mBookList } override fun addBook(book: Book?) { mBookList.add(book) } } override fun onBind(intent: Intent): IBinder { return mBinder } override fun onCreate() { super.onCreate() // 創(chuàng)建兩本圖書 mBookList.add(Book(1, "Android")) mBookList.add(Book(2, "iOS")) } }
然后在 AndroidManifest 中注冊 Service购裙,注意啟動(dòng)多進(jìn)程:
<service android:name=".aidl.BookManagerService" android:process="com.cy.ipcsample.bookManagerService"> </service>
-
客戶端綁定服務(wù)端的 Service
客戶端的創(chuàng)建,直接使用 Activity 即可鹃栽,綁定遠(yuǎn)程服務(wù)的代碼如下:private val mConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { } /** * 連接遠(yuǎn)程服務(wù)成功的回調(diào) */ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { } } // 綁定遠(yuǎn)程服務(wù) bindService(Intent(this, BookManagerService::class.java), mConnection, Context.BIND_AUTO_CREATE)
-
與服務(wù)端通信
與服務(wù)器綁定成功后躏率,首先在 ServiceConnection 的回調(diào)中,將服務(wù)端返回的 Binder 對象轉(zhuǎn)換成 AIDL 接口所屬的對象民鼓,就可以調(diào)用相應(yīng)方法和服務(wù)端通信了薇芝,代碼如下:// 將服務(wù)端返回的 Binder 對象轉(zhuǎn)換成 IBookManager 對象 val bookManager = IBookManager.Stub.asInterface(service) // 與服務(wù)端通信 try { // 獲取圖書列表 val list = bookManager.bookList Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}") Log.i(TAG, "query book list: $list") // 添加一本圖書 val book = Book(3, "Android開發(fā)藝術(shù)探索") bookManager.addBook(book) Log.i(TAG, "add book: $book") // 獲取圖書列表 val newList = bookManager.bookList Log.i(TAG, "query book list: $newList") } catch (e: RemoteException) { e.printStackTrace() }
代碼中,我們先查詢了服務(wù)端的圖書列表丰嘉,接著向服務(wù)端添加一本書
Android藝術(shù)開發(fā)探索
夯到,然后再次查詢看是否添加成功。運(yùn)行下看 log饮亏,如下圖所示:
可見耍贾,運(yùn)行結(jié)果和預(yù)期結(jié)果一致。完整的客戶端代碼如下:package com.cy.ipcsample.aidl import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.Bundle import android.os.IBinder import android.os.RemoteException import android.support.v7.app.AppCompatActivity import android.util.Log import com.cy.ipcsample.R class BookManagerActivity : AppCompatActivity() { private val TAG = "BookManagerActivity" private val mConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { Log.d(TAG, "binder died.") } /** * 連接遠(yuǎn)程服務(wù)成功的回調(diào) */ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { // 將服務(wù)端返回的 Binder 對象轉(zhuǎn)換成 IBookManager 對象 val bookManager = IBookManager.Stub.asInterface(service) // 與服務(wù)端通信 try { // 獲取圖書列表 val list = bookManager.bookList Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}") Log.i(TAG, "query book list: $list") // 添加一本圖書 val book = Book(3, "Android開發(fā)藝術(shù)探索") bookManager.addBook(book) Log.i(TAG, "add book: $book") // 獲取圖書列表 val newList = bookManager.bookList Log.i(TAG, "query book list: $newList") } catch (e: RemoteException) { e.printStackTrace() } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_book_manager) // 綁定遠(yuǎn)程服務(wù) bindService(Intent(this, BookManagerService::class.java), mConnection, Context.BIND_AUTO_CREATE) } override fun onDestroy() { unbindService(mConnection) super.onDestroy() } }
到此路幸,一個(gè)簡單的 AIDL 示例就完成了荐开,當(dāng)然,AIDL 的使用遠(yuǎn)沒有那么簡單简肴,還有很多情景需要考慮的晃听,比如:客戶端需要隨時(shí)服務(wù)端在狀態(tài)變化時(shí)同時(shí)客戶端,類似觀察這模式着帽,那么訂閱與反訂閱怎么實(shí)現(xiàn)杂伟;Binder 意外死亡,怎么重連等等仍翰,更多內(nèi)容赫粥,可以參考《Android藝術(shù)開發(fā)探索》,電子書下載
AIDL 的異步調(diào)用
AIDL 的調(diào)用過程是同步還是異步的予借?
這個(gè)問題其實(shí)看這一節(jié)的標(biāo)題大家都知道了越平,AIDL 的調(diào)用過程是同步的。同時(shí)灵迫,上面分析 Binder 的機(jī)制時(shí)秦叛,也提到了,客戶端進(jìn)行遠(yuǎn)程 RPC 請求時(shí)瀑粥,線程會(huì)掛起挣跋,等待結(jié)果,由此也可知狞换,AIDL 的調(diào)用過程是同步的避咆,下面來驗(yàn)證下舟肉。
首先,在服務(wù)端的 BookManagerService 中實(shí)現(xiàn)的 Binder 對象的 getBookList 方法添加延時(shí)執(zhí)行查库,如下圖所示:運(yùn)行后整慎,連續(xù)點(diǎn)擊按鈕,結(jié)果如下圖所示:
由圖所知围苫,連續(xù)點(diǎn)擊按鈕過后一段時(shí)間裤园,出現(xiàn)了無響應(yīng)錯(cuò)誤( ANR ),由此可知剂府,客戶端向服務(wù)端做 RPC 請求時(shí)比然,是同步的,也就是說:AIDL 的調(diào)用過程是同步的周循。
AIDL 的調(diào)用過程是同步的,當(dāng)我們需要服務(wù)端做耗時(shí)操作時(shí)万俗,肯定是不能使用同步調(diào)用的湾笛,否則輕者影響用戶體驗(yàn),重者直接 ANR 或者應(yīng)用崩潰闰歪。那么如何使 AIDL 的調(diào)用過程是異步的呢嚎研?
其實(shí)也很簡單,只需要把調(diào)用放到非 UI 線程即可库倘,如果要對調(diào)用的返回做 UI 更新的話临扮,再通過 Handler 處理即可。如下圖所示:本文參考
- 《Android開發(fā)藝術(shù)探索》 第二章