AIDL:Android Interface Definition Language,即Android接口定義語言潭兽。
AIDL文件的本質(zhì)是系統(tǒng)為我們提供了一種快速實現(xiàn)Binder的工具囊蓝,僅此而已。
AIDL進行進程間通信的流程:
服務(wù)端
服務(wù)端首先要創(chuàng)建一個Service來監(jiān)聽客戶端的連接請求,然后創(chuàng)建一個AIDL文件诫睬,將暴露給客戶端的接口在這個AIDL文件中聲明,最后在Service中實現(xiàn)這個AIDL接口即可帕涌。客戶端
客戶端首先需要綁定服務(wù)端的Service摄凡,綁定成功后,將服務(wù)端返回的Binder對象轉(zhuǎn)成AIDL接口所屬的類型宵膨,接著就可以調(diào)用AIDL中的方法了架谎。AIDL接口的創(chuàng)建
// IBookManager.aidl
package com.wonder.myapp.tests.aidl;
import com.wonder.myapp.tests.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
系統(tǒng)為我們自動生成的Binder類如下:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\workspace\\TestApp\\app\\src\\main\\aidl\\com\\wonder\\myapp\\tests\\aidl\\IBookManager.aidl
*/
package com.wonder.myapp.tests.aidl;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.wonder.myapp.tests.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.wonder.myapp.tests.aidl.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.wonder.myapp.tests.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.wonder.myapp.tests.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.wonder.myapp.tests.aidl.IBookManager))) {
return ((com.wonder.myapp.tests.aidl.IBookManager) iin);
}
return new com.wonder.myapp.tests.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.wonder.myapp.tests.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.wonder.myapp.tests.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.wonder.myapp.tests.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.wonder.myapp.tests.aidl.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.wonder.myapp.tests.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.wonder.myapp.tests.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.wonder.myapp.tests.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.wonder.myapp.tests.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();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.wonder.myapp.tests.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.wonder.myapp.tests.aidl.Book book) throws android.os.RemoteException;
}
Binder工作機制分析:
類的結(jié)構(gòu)其實很簡單,首先它聲明了兩個方法getBookList和addBook辟躏,正是在IBookManager.aidl中所聲明的方法谷扣,同時還聲明了兩個整型的id標(biāo)識這兩個方法,用于標(biāo)識在transact過程中客戶端所請求的到底是哪個方法。接著会涎,它聲明了一個內(nèi)部類Stub裹匙,這是一個Binder類,當(dāng)客戶端和服務(wù)端都位于同一個進程時末秃,方法調(diào)用不會走跨進程的transact過程概页,而當(dāng)兩者位于不同進程時,方法調(diào)用需要走跨進程的transact過程练慕,這個邏輯有Stub的內(nèi)部代理類Proxy來完成惰匙。下面詳細每個方法的含義。
DESCRIPTOR
Binder的唯一標(biāo)識铃将,一般用當(dāng)前Binder的類名標(biāo)識项鬼。
asInterface(android.os.IBinder obj)
用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象,這種轉(zhuǎn)換過程是區(qū)分進程的劲阎,如果客戶端和服務(wù)端位于同一進程绘盟,那么該方法返回的就是服務(wù)端的Stub對象本身,否則返回的是系統(tǒng)封裝后的Stub.Proxy對象悯仙。
asBinder()
此方法用于返回當(dāng)前Binder對象龄毡。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
這個方法運行在服務(wù)端的Binder線程池中,當(dāng)客戶端發(fā)起跨進程請求時锡垄,遠程請求會通過系統(tǒng)底層封裝后交由此方法來處理沦零。服務(wù)端通過code可以確定客戶端所請求的目標(biāo)方法是什么,接著從data中取出目標(biāo)方法所需的參數(shù)(目標(biāo)方法有參的話)偎捎,然后執(zhí)行目標(biāo)方法蠢终。當(dāng)目標(biāo)方法執(zhí)行完畢后,就向reply中寫入返回值(目標(biāo)方法有返回值的話)茴她。需要注意的是寻拂,如果此方法返回false,那么客戶端的請求會失敗丈牢,因此可以利用這個特性做權(quán)限驗證祭钉。
Proxy # getBookList
這個方法運行在客戶端,當(dāng)客戶端遠程調(diào)用此方法時己沛,內(nèi)部實現(xiàn)如下:首先創(chuàng)建該方法所需的輸入型Parcel對象_data慌核、輸出型Parcel對象_reply和返回值對象List;然后把該方法的參數(shù)信息寫入_data中(有參的話)申尼,接著調(diào)用transact方法來發(fā)起RPC請求垮卓,同時當(dāng)前線程掛起;然后服務(wù)端的onTransact方法會被調(diào)用师幕,知道RPC過程返回后粟按,當(dāng)前線程繼續(xù)執(zhí)行诬滩,并從_reply中取出RPC過程的返回結(jié)果;最后灭将,返回_reply中的數(shù)據(jù)疼鸟。
Proxy # addBook
這個方法運行在客戶端,執(zhí)行過程和getBookList是一樣的庙曙,不過這個方法沒有返回值空镜,不需要從_reply中取出數(shù)據(jù)。
注意:
- 客戶端發(fā)起遠程請求時捌朴,當(dāng)前線程會掛起直至服務(wù)端進程返回數(shù)據(jù)吴攒,所以如果一個遠程方法是耗時的,則不能在UI線程發(fā)起此遠程請求男旗;
- 服務(wù)端的Binder方法運行在Binder的線程池中舶斧,所以不管Binder方法是否耗時都應(yīng)該采用同步的方式去實現(xiàn),因為它已經(jīng)運行在一個線程中了察皇。
Binder是可能意外死亡的,這往往是由于服務(wù)端進程意外停止泽台,為了程序的健壯性什荣,需要重新連接服務(wù)。有如下方法:
- linkToDeath()方法可用于注冊一個DeathRecipient監(jiān)聽怀酷。DeathRecipient是一個接口稻爬,內(nèi)部只有一個方法binderDied,當(dāng)Binder死亡的時候蜕依,系統(tǒng)會回調(diào)該方法桅锄。該方法在客戶端的Binder線程池中被回調(diào);
- 在onServiceDisconnected中重連遠程服務(wù)样眠,該方法在UI線程被回調(diào)友瘤。
從安全性考慮,遠程服務(wù)需要加入權(quán)限驗證功能檐束,避免客戶端隨意連接辫秧。AIDL的權(quán)限驗證常用方法介紹:
- onBind中進行驗證,驗證不通過返回null被丧;
- 在服務(wù)端的onTransact方法中進行驗證盟戏,驗證失敗就返回false。
補充知識:
IBinder是輕量級遠程調(diào)用機制的核心甥桂,高性能地進行進程內(nèi)和進程間通信柿究。不要直接實現(xiàn)這個接口,從Binder進行擴展黄选。
AIDL支持的所有數(shù)據(jù)類型
- 基本數(shù)據(jù)類型(int蝇摸、long、char、boolean探入、double等)狡孔;
- String 和 CharAequence;
- List:只支持ArrayList蜂嗽,里面每個元素都必須被AIDL支持苗膝;
- Map:只支持HashMap,里面每個元素都必須被AIDL支持植旧;
- Parcelable:所有實現(xiàn)Parcelable接口的對象辱揭;
- AIDL:所有的AISL接口本身也可以在AIDL文件中使用。
自定義的Parcelable對象和AIDL對象必須要顯式import進來病附,不管它們是否和當(dāng)前的AIDL文件位于同一個包內(nèi)问窃;
如果AIDL文件中用到了自定義的Parcelable對象,那么必須新建一個和它同名的AIDL文件完沪,并在其中聲明它為Parcelable類型域庇。
AIDL中除了基本數(shù)據(jù)類型,其他類型的參數(shù)必須標(biāo)上方向:in覆积、out或inout听皿,in表示輸入型參數(shù),out表示輸出型參數(shù)宽档,inout表示輸入輸出型參數(shù)尉姨。要根據(jù)實際情況指定參數(shù)類型,不同的類型在底層實現(xiàn)上有不同的開銷吗冤。
AIDL接口中只支持方法又厉,不支持聲明靜態(tài)常量。
AIDL的包結(jié)構(gòu)在服務(wù)端和客戶端要保持一致椎瘟,否則會運行出錯覆致。因為客戶端需要反序列化服務(wù)端中和AIDL接口相關(guān)的所有類,如果類的完整路徑不一樣的話降传,就不能成功反序列化篷朵,程序也就無法正常運行。
服務(wù)端有List婆排、Map的時候声旺,可以用CopyOnWriteArrayList、ConcurrentHashMap段只。
CopyOnWriteArrayList腮猖、ConcurrentHashMap支持并發(fā)讀/寫。
前面有提到赞枕,AIDL中只支持ArrayList型的List澈缺,在這卻推薦使用CopyOnWriteArrayList(不是繼承自ArrayList)坪创,為什么能正常工作呢?因為AIDL中所支持的是抽象的List姐赡,而List只是一個接口莱预,因此雖然服務(wù)端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)并最終形成一個新的ArrayList傳遞給客戶端项滑。服務(wù)端管理客戶端注冊的監(jiān)聽器的時候依沮,可以用RemoteCallbackList。
RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進程listener的接口枪狂。它是一個泛型危喉,支持管理任意的AIDL接口。它的工作原理為:在它的內(nèi)部有一個Map結(jié)構(gòu)州疾,專門用來保存所有的AIDL回調(diào)辜限,這個Map的key是IBinder類型,value是Callback類型严蓖。
RemoteCallbackList可以在客戶端進程終止后薄嫡,自動移除客戶端所注冊的listener;RemoteCallbackList內(nèi)部自動實現(xiàn)了線程同步的功能颗胡。
出現(xiàn)原因:對象不能跨進程直接傳輸,對象的跨進程傳輸本質(zhì)上是反序列化的過程杭措。跨進程傳輸钾恢,客戶端的同一個對象會在服務(wù)端生成一個新的不同的對象手素,但它們底層的Binder對象是同一個,所以RemoteCallbackList可以解決此問題瘩蚪。客戶端的onServiceConnected和onServiceDisconnected方法運行在UI線程泉懦。
服務(wù)端Binder的方法本身就運行在Binder線程池中,可以執(zhí)行大量耗時操作疹瘦,這個時候就不要在服務(wù)端方法中開啟子線程去執(zhí)行異步任務(wù)了崩哩,除非有特殊需求,否則不建議這么做言沐。
Demo代碼
package com.wonder.myapp.tests.service;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by zxh on 2018/7/23.
* Book.java
*/
public class Book implements Parcelable {
private int bookId;
private String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
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];
}
};
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
'}';
}
}
// Book.aidl
package com.wonder.myapp.tests.service;
parcelable Book;
// IBookListChangeListener.aidl
package com.wonder.myapp.tests.service;
import com.wonder.myapp.tests.service.Book;
interface IBookListChangeListener {
void onNewBookArrived(in Book book);
}
// IBookManager.aidl
package com.wonder.myapp.tests.service;
import com.wonder.myapp.tests.service.Book;
import com.wonder.myapp.tests.service.IBookListChangeListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IBookListChangeListener listener);
void unregisterListener(IBookListChangeListener listener);
}
package com.wonder.myapp.tests.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import com.wonder.myapp.bean.LogTag;
import com.wonder.myapp.utils.LogUtils;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class MyService extends Service {
private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IBookListChangeListener> callbackList = new RemoteCallbackList<>();
//IBookManager.Stub的四個方法都是在子線程執(zhí)行
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
LogUtils.d(LogTag.TAG_SERVICE, "getBookList " + (Looper.myLooper() == Looper.getMainLooper()));
return bookList;
}
@Override
public void addBook(Book book) throws RemoteException {
bookList.add(book);
int count = callbackList.beginBroadcast();
for (int i = 0; i < count; i ++) {
callbackList.getBroadcastItem(i).onNewBookArrived(book);
}
callbackList.finishBroadcast();
}
@Override
public void registerListener(IBookListChangeListener listener) throws RemoteException {
callbackList.register(listener);
}
@Override
public void unregisterListener(IBookListChangeListener listener) throws RemoteException {
callbackList.unregister(listener);
}
};
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(LogTag.TAG_SERVICE, "onCreate " + (Looper.myLooper() == Looper.getMainLooper()));
bookList.add(new Book(1, "語文書"));
bookList.add(new Book(2, "數(shù)學(xué)書"));
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int j = 0; j < 10; j++) {
Thread.sleep(10 * 1000);
Book book = new Book(bookList.size() + 1, "Book # " + (bookList.size() + 1));
bookList.add(book);
int count = callbackList.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
callbackList.getBroadcastItem(i).onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
callbackList.finishBroadcast();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(LogTag.TAG_SERVICE, "onStartCommand startId = " + startId);
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
LogUtils.d(LogTag.TAG_SERVICE, "onBind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
LogUtils.d(LogTag.TAG_SERVICE, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(LogTag.TAG_SERVICE, "onDestroy");
}
}
package com.wonder.myapp.tests.service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.view.View;
import com.wonder.myapp.R;
import com.wonder.myapp.bean.LogTag;
import com.wonder.myapp.ui.common.BaseActivity;
import com.wonder.myapp.utils.LogUtils;
import java.util.List;
import butterknife.OnClick;
public class ServiceTestActivity extends BaseActivity {
private IBookManager mBinder;
private IBookListChangeListener bookListChangeListener;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(LogTag.TAG_SERVICE, "ServiceConnection onServiceConnected " + (Looper.myLooper() == Looper.getMainLooper()));
mBinder = IBookManager.Stub.asInterface(service);
try {
bookListChangeListener = new IBookListChangeListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
LogUtils.d(LogTag.TAG_SERVICE, "onNewBookArrived " + book.toString());
}
};
mBinder.registerListener(bookListChangeListener);
List<Book> list = mBinder.getBookList();
for (Book book : list) {
LogUtils.d(LogTag.TAG_SERVICE, "bookList :" + book.toString());
}
mBinder.addBook(new Book(3, "人民法院細則"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(LogTag.TAG_SERVICE, "ServiceConnection onServiceDisconnected");
}
};
@Override
protected void setContentView() {
setContentView(R.layout.activity_service_test);
}
@OnClick({R.id.service_start, R.id.service_stop, R.id.service_bind, R.id.service_unbind})
public void onClick(View v) {
switch (v.getId()) {
case R.id.service_start:
LogUtils.d(LogTag.TAG_SERVICE, "click to startService");
startService(new Intent(this, MyService.class));
break;
case R.id.service_stop:
LogUtils.d(LogTag.TAG_SERVICE, "click to stopService");
stopService(new Intent(this, MyService.class));
break;
case R.id.service_bind:
LogUtils.d(LogTag.TAG_SERVICE, "click to bindService");
bindService(new Intent(this, MyService.class), connection, BIND_AUTO_CREATE);
break;
case R.id.service_unbind:
LogUtils.d(LogTag.TAG_SERVICE, "click to unbindService");
//沒有bindService直接unbindService會報錯:java.lang.IllegalArgumentException: Service not registered:
unbindService(connection);
if (mBinder != null)
try {
mBinder.unregisterListener(bookListChangeListener);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}