本文包含内容如下:
- 实现一个客户端调用远程服务的Demo,包括添加书籍addBook,获取书籍getBookList功能
- 在Demo基础上分析AIDL底层通过Binder来实现跨进程通信的流程
如果文中有不正确的地方还望指出,不要误人误己,感谢。学习自《Android开发艺术探索》
实现Demo
实现步骤:
- 定义Bean
- 定义AIDL接口
- 定义远程Service实现AIDL接口
- 客户端调用远程服务
Setp1 定义Bean:
说明:
AIDL接口支持的数据类型:
- 基本数据类型:int long char boolean double
- String和CharSequence
- List:只支持ArrayList,里面的每个元素都必须能够被AIDL支持
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- Parcelable:所有实现了Parcelable接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
Book.java,实现了Parcelable接口
public class Book implements Parcelable {
private String bookName;
private int bookId;
public Book(String bookName, int bookId) {
this.bookName = bookName;
this.bookId = bookId;
}
protected Book(Parcel in) {
bookName = in.readString();
bookId = in.readInt();
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
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) {
dest.writeString(bookName);
dest.writeInt(bookId);
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", bookId=" + bookId +
'}';
}
}
Setp2 定义AIDL接口:
说明:
-
在AIDL中定义Parcelable和AIDL对象必须显示的import进来,不管他们是否位于和当前的AIDL文件位于同一个包内
-
如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型
package com.thc.binderdemo parcelable Book;
-
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in(输入型参数)、out(输出型参数)、或者inout(输入输出型参数)
-
AIDL只支持方法,不支持声明静态常量
所以我们除了定义IBookManager.aidl文件外还要定义Book.aidl,尽管它内部只是声明了parcelable Book
Book.aidl
// Book.aidl
package com.thc.binderdemo;
parcelable Book;
IBookManager.aidl
// IBookManager.aidl
package com.thc.binderdemo;
// Declare any non-default types here with import statements
import com.thc.binderdemo.Book;
import com.thc.binderdemo.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
Setp3 定义远程Service实现AIDL接口:
MyRemoteService.java
用到了CopyOnWriteArrayList,看注释即可
public class MyRemoteService extends Service {
/**
* 因为AIDL方法是在服务端的Binder线程池中执行的,因此多个客户端同时连接的时候会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步。
* CopyOnWriteArrayList支持并发读/写,和CopyOnWriteArrayList相似的还有ConcurrentHashMap
* AIDL中能够使用的List只有ArrayList,但是这里使用了CopyOnWriteArrayList(不继承ArraryList),这里为什么能够工作呢?
* 因为 AIDL 中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,
* 但是再Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList返回给客户端。
*
*/
CopyOnWriteArrayList<Book> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
public MyRemoteService() {
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onCreate() {
super.onCreate();
//在Service创建后先添加两本书
copyOnWriteArrayList.add(new Book("三国演义", 0));
copyOnWriteArrayList.add(new Book("水浒传", 1));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return copyOnWriteArrayList;
}
@Override
public void addBook(Book book) throws RemoteException {
copyOnWriteArrayList.add(book);
}
};
}
在AndroidManifest.xml中定义:
<service
android:name=".MyRemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote" />
Setp3 客户端调用远程服务:
客户端通过bindService来绑定远程服务,绑定成功后将服务端返回的Binder对象转成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。
public void bindRemoteService(View view) {
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> bookList = iBookManager.getBookList();
Log.e("result", bookList.get(0).getBookName() + "--" + bookList.get(1).getBookName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
bindService(new Intent(this, MyRemoteService.class), connection, BIND_AUTO_CREATE);
}
当然AIDL的使用不止这些内容,还有通过RemoteCallbackList实现一个观察者模式、远程服务死亡处理、权限验证等
Binder工作原理
从Android应用层来讲,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和AIDL服务。
所以AIDL的底层通信就是通过Binder来实现的。
分析步骤如下:
- 分析根据AIDL生成的Binder类
- Binder工作机制流程
Setp1 分析根据AIDL生成的Binder类:
在创建AIDL之后,SDK会自动给我们生成AIDL对应的Binder类,比如根据我们上述的IBookManager.adil生成的IBookManager.java这个Binder类。代码如下:
package com.thc.binderdemo;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.thc.binderdemo.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.thc.binderdemo.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.thc.binderdemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.thc.binderdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.thc.binderdemo.IBookManager))) {
return ((com.thc.binderdemo.IBookManager) iin);
}
return new com.thc.binderdemo.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.thc.binderdemo.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.thc.binderdemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.thc.binderdemo.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.thc.binderdemo.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.thc.binderdemo.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.thc.binderdemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.thc.binderdemo.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.thc.binderdemo.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.thc.binderdemo.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.thc.binderdemo.Book book) throws android.os.RemoteException;
}
注释:
![](https://img.haomeiwen.com/i1430230/5ec26f1404441f8e.png)
- 这是根据我们IBookManager.aidl生成的对应的IBookManager.java这个类,它继承了IInterface这个接口,同时自己也是一个接口,所有可以在Binder中传输的接口都需要继承IInterface接口
- 声明了两个方法getBookList和addBook,这是在IBookManager.aidl中声明的方法
- 同时声明了两个整型的id分别标识这两个方法
- 声明了一个内部类Stub,Stub是一个Binder类,当客户端和服务端位于同一个进程时,方法调用不会走跨进程的transact过程,两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成
- 核心是内部类Stub和Stub的内部代理Proxy
声明的内容注释:
- DESCRIPTOR:Binder的唯一标识一般用Binder的类名标识
- asInterface():将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,这种转换过程是需要区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的是服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象
- asBinder():此方法用于返回当前Binder对象
- onTransact():这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过底层封装后交给此方法来处理
- Proxy#getBookList/addBook:这个方法运行在客户端
注意:
- 客户端发起请求时,由于当前线程会被挂起知道服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能再UI线程发起此次远程请求
- 由于服务端的Binder方法运行在Binder线程池中,所以Binder方法不管是耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了
Setp2 Binder工作机制流程:
-
定义AIDL,SDK根据我们定义的AIDL文件生成对应的Binder类
-
在客户端通过bindService调用远程服务,在生成的Binder类内部根据是否处于同一个进程,将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,返回给客户端
-
客户端拿到Binder对象就可以调用服务端的方法,比如调用了getBookList这个方法
-
进入getBookList方法中执行顺序如下:
-
创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;
-
把该方法的参数信息写入_data中(如果有参数的话)
-
调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起(在此之前的操作还是在客户端线程,从下一步开始在服务端Binder线程池执行)
//transact调用示例 @Override public java.util.List<com.thc.binderdemo.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.thc.binderdemo.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); /** *Stub.TRANSACTION_getBookList 标识方法的int值 *_data : 方法需要的参数 *_reply: 结果 */ mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.thc.binderdemo.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; }
-
服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果
//onTransact调用示例 @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; } /** * onTransact调用示例: * * 1. 根据方法标识的值,判断客户端调用的是哪个方法 * 2. 取出参数data(该方法没有用到,在addBook中有用到) * 3. 将返回的结果写到reply中 * 4. 最后有个boolean类型的返回值,如果返回false,客户端的请求就会失败,可以利用这里来做权限验证 */ case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.thc.binderdemo.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); /** * 取出参数data来作为参数去执行目标方法 */ com.thc.binderdemo.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.thc.binderdemo.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); }
-
返回_reply中的结果
-
一图解千愁
![](https://img.haomeiwen.com/i1430230/5d50f69fdbd709a6.jpg)
网友评论