进程间通信(IPC)有很多种方法,而Binder是其中一种。也可以说,Binder是在Android系统挂载的虚拟设备,设备的驱动为/dev/binder,在Linux上没有。Binder是客户端和服务端进行通信的媒介,通过Binder,客户端可以向服务端获取服务或者数据。这里将的服务有两种,一种是普通的服务,另一种是AIDL(Android Interface Define Language,Android接口定义语言)的服务。书中主要讲解通过Binder获取AIDL的服务。
我按照书中写了一个栗子:
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;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator CREATOR = new Creator() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
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 parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
}
Book.aidl
package com.johan.bookaidl;
parcelable Book;
IBookManager.aidl
package com.johan.bookaidl;
import com.johan.bookaidl.Book;
interface IBookManager {
List getBookList();
void addBook(in Book book);
}
使用Eclipse或者Android Studio等编程工具,build一下,会出来一个IBookManager.java文件,是一个接口来的。提醒:如果是用Android Studio自动生成的话,IBookManager.java文件在app\build\generated\source\aidl\debug\ com\johan\bookaidl(包名) 可以找到。我们看一下这个代码:
IBookManager.java
package com.johan.bookaidl;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.johan.bookaidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.johan.bookaidl.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.johan.bookaidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.johan.bookaidl.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.johan.bookaidl.IBookManager))) {
return ((com.johan.bookaidl.IBookManager) iin);
}
return new com.johan.bookaidl.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 _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.johan.bookaidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.johan.bookaidl.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.johan.bookaidl.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 getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.johan.bookaidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.johan.bookaidl.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 getBookList() throws android.os.RemoteException;
public void addBook(com.johan.bookaidl.Book book) throws android.os.RemoteException;
}
详细分析一下这个自动生成的代码结构,你就可以理解Binder的原理了。
(1)IBookManager是一个接口,而且继承了IInterface接口,书中说在Binder传输的接口都要继承IInterface接口,这是规定的,没什么好说了。看看里面的结构。在最外层一个Sub类和2个抽象方法,这两个抽象方法正是我们在IBookManager.aidl定义的两个方法。我们着重看一下Sub这个类。
(2)Sub类是一个IBookManager内部抽象类,继承了Binder类,同时还有一个IBookManager接口。每个属性和方法Look A Look,不要怕麻烦,没有多少代码(鼓励一下自己^_^)。
DESCRIPTOR
这个属性应该是作为Binder的唯一标识符,一般为全类名(com.johan.bookaidl.IBookManager)。
TRANSACTION_xxx
每个我们在aidl定义方法,都是有一个对应的code,在下面onTransact方法里面用到。
asInterface(android.os.IBinder obj)
由服务器的Binder转换客户端所需要的IBookManager对象,这个转换是区分进程的。如果在服务端和客户端在同一个进程,直接返回Sub本身。如果不在同一进程,返回的则是Stub.Porxy对象。
asBinder()
返回当前Binder对象。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
这个方法运行在服务端的Binder线城池里面。当客户端发起请求时,服务端会通过code确定调用的是个方法,从data取出参数,然后执行要调用的方法,再将返回的结果写入reply(如果目标方法有返回值)。如果返回的是false,客户端请求是失败。在这里可以做一些权限控制的事情(原来还可以这样)。
Proxy
从名字可以看出是一个Stub的代理类。这个类不是一个IBookManager接口实现类。在asInterface方法也知道,如果转换过程服务端和客户端不在同一个进程,那么需要返回这个代理类。我们着重看一下我们定义两个方法在这里的实现。
Proxy.getBookList()
这个方法先是定了两个List,一个用于传入参数的_data,一个用于读取结果的_reply,因为我们getBookList是有参数的,所以还定义了一个用于方法返回的_result。_data先赋值,然后客户端调用mRemote的onTransact方法发起RPC请求,线程会挂起,onTransact方法结束后,从_reply读取返回的结果,最后return从_reply返回的结果。Proxy.addBook()和这个方法原理相似,不再做分析。
书中指出,需要注意2点:
(1)客户端发起RPC时,线程会挂起,如果调用的方法比较耗时,则不能在UI线程发起RPC请求。
(2)不管RPC怎么耗时,都应该采用同步的方法请求。
如果上面看不懂,看这个图应该就明白了:
书中还介绍我们可以不用通过aidl生成java文件,有兴趣可以看书。
最后作者还说了,怎么去监听一个Binder连接断裂。Binder提供了两个方法去设置和解除死亡代理,分别是linkToDeath和unlinkToDeath。怎么去设置死亡代理呢?
我们先定义一个DeathRecipient这么一个死亡代理对象,然后在binderDied方法编写Binder断裂之后重连或者其他处理。
public class Binder.DeathRecipient deathRecipient = new Binder.DeathRecipient {
@Override
public void binderDied() {
if (mBookManager == null) return;
mBookManager.unlinkToDeath(deathRecipient, 0);
mBookManager = null;
// TODO
}
}
然后设置代码:
service = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(deathRecipient, 0);
Binder其实还有个检查是否还连接的方法:isBinderAlive.