AIDL:Android Interface Definition Language,即Android接口定义语言。
AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具,仅此而已。
AIDL进行进程间通信的流程:
-
服务端
服务端首先要创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。 -
客户端
客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。 -
AIDL接口的创建
// 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);
}
系统为我们自动生成的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类结构图.png
Binder工作机制分析:
类的结构其实很简单,首先它声明了两个方法getBookList和addBook,正是在IBookManager.aidl中所声明的方法,同时还声明了两个整型的id标识这两个方法,用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走跨进程的transact过程,这个逻辑有Stub的内部代理类Proxy来完成。下面详细每个方法的含义。
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名标识。
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么该方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象。
asBinder()
此方法用于返回当前Binder对象。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(目标方法有参的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(目标方法有返回值的话)。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此可以利用这个特性做权限验证。
Proxy # getBookList
这个方法运行在客户端,当客户端远程调用此方法时,内部实现如下:首先创建该方法所需的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(有参的话),接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后,返回_reply中的数据。
Proxy # addBook
这个方法运行在客户端,执行过程和getBookList是一样的,不过这个方法没有返回值,不需要从_reply中取出数据。
注意:
- 客户端发起远程请求时,当前线程会挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,则不能在UI线程发起此远程请求;
- 服务端的Binder方法运行在Binder的线程池中,所以不管Binder方法是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
Binder是可能意外死亡的,这往往是由于服务端进程意外停止,为了程序的健壮性,需要重新连接服务。有如下方法:
- linkToDeath()方法可用于注册一个DeathRecipient监听。DeathRecipient是一个接口,内部只有一个方法binderDied,当Binder死亡的时候,系统会回调该方法。该方法在客户端的Binder线程池中被回调;
- 在onServiceDisconnected中重连远程服务,该方法在UI线程被回调。
从安全性考虑,远程服务需要加入权限验证功能,避免客户端随意连接。AIDL的权限验证常用方法介绍:
- onBind中进行验证,验证不通过返回null;
- 在服务端的onTransact方法中进行验证,验证失败就返回false。
补充知识:
-
IBinder是轻量级远程调用机制的核心,高性能地进行进程内和进程间通信。不要直接实现这个接口,从Binder进行扩展。
-
AIDL支持的所有数据类型
- 基本数据类型(int、long、char、boolean、double等);
- String 和 CharAequence;
- List:只支持ArrayList,里面每个元素都必须被AIDL支持;
- Map:只支持HashMap,里面每个元素都必须被AIDL支持;
- Parcelable:所有实现Parcelable接口的对象;
- AIDL:所有的AISL接口本身也可以在AIDL文件中使用。
自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内;
如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。要根据实际情况指定参数类型,不同的类型在底层实现上有不同的开销。
AIDL接口中只支持方法,不支持声明静态常量。
-
AIDL的包结构在服务端和客户端要保持一致,否则会运行出错。因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就不能成功反序列化,程序也就无法正常运行。
-
服务端有List、Map的时候,可以用CopyOnWriteArrayList、ConcurrentHashMap。
CopyOnWriteArrayList、ConcurrentHashMap支持并发读/写。
前面有提到,AIDL中只支持ArrayList型的List,在这却推荐使用CopyOnWriteArrayList(不是继承自ArrayList),为什么能正常工作呢?因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端。 -
服务端管理客户端注册的监听器的时候,可以用RemoteCallbackList。
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。它是一个泛型,支持管理任意的AIDL接口。它的工作原理为:在它的内部有一个Map结构,专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型。
RemoteCallbackList可以在客户端进程终止后,自动移除客户端所注册的listener;RemoteCallbackList内部自动实现了线程同步的功能。
出现原因:对象不能跨进程直接传输,对象的跨进程传输本质上是反序列化的过程。跨进程传输,客户端的同一个对象会在服务端生成一个新的不同的对象,但它们底层的Binder对象是同一个,所以RemoteCallbackList可以解决此问题。 -
客户端的onServiceConnected和onServiceDisconnected方法运行在UI线程。
-
服务端Binder的方法本身就运行在Binder线程池中,可以执行大量耗时操作,这个时候就不要在服务端方法中开启子线程去执行异步任务了,除非有特殊需求,否则不建议这么做。
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的四个方法都是在子线程执行
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, "数学书"));
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;
}
}
}
网友评论