在应用Binder探索(上)中提到Binder测试的一个demo程序,主要功能为,有一个名为BookManagerService的远程服务,它持有一个用于保存Book对象的list,并对外提供该list的get和add方法,而客户端Activity绑定服务并访问其接口。在本文将描述getBookList和addBook方法的调用流程,其中涉及到Binder的通信。
一、asBinder与asInterface
我们知道客户端持有Proxy,是对BinderProxy包装,而服务端持有Stub,继承了Binder。对于IInterface接口的asBinder方法和Stub类的asInterface静态方法,它们起到什么作用?为什么要有它们?可以看下代码:
//Stub类
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static IBookManager asInterface(IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBookManager))) {
return ((IBookManager) iin);
}
return new IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
//Proxy类
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
我们看到asInterface方法,传入的参数类型是IBinder(因为Binder和BinderProxy都继承了IBinder接口,这里的参数可接收Binder或BinderProxy对象)。接着是queryLocalInterface(String)方法:
//Binder实现
private IInterface mOwner;
private String mDescriptor;
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
//BinderProxy实现
public IInterface queryLocalInterface(String descriptor) {
return null;
}
- 传入的是Binder对象,就会返回mOwner,而mOwner是在Stub构造函数里调用attachInterface(this,DESCRIPTOR)赋值的,也就是说仅仅返回它自己。
- 传入的是BinderProxy对象,把其作为Proxy类构造函数参数,new一个Proxy对象返回。
总的来说,asInterface方法就是把IBinder转化成IBookManager,在客户端输入BinderProxy对象,输出Proxy对象,在服务端输入Binder对象,输出Stub对象。而此时asBinder方法就能一目了然,与asInterface恰恰相反,在服务端通过Stub类返回的是this,即BInder对象,而在客户端通过Proxy类返回的是mRemote,即BinderProxy对象。在客户端调用asBinder获取BinderProxy对象有什么作用呢(在服务端调用asBinder作用不大)?BinderProxy对象可以ping服务端,判断连接是否存活,甚至还能传入连接死亡通知的监听,而asInterface方法获取Stub对象或Proxy对象有什么作用呢?就是我们接下来要讲的。
二、getBookList和Parcel
1. getBookList调用流程
首先在客户端Activity通过onServiceConnected回调拿到BinderProxy对象,从而生成Proxy对象,进而调用Proxy的getBookList方法,代码如下:
@Override
public List<Book> getBookList()throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List<Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data,_reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
这里涉及到Parcel和Book以及BinderProxy对象mRemote,它们都有什么关系呢?Book实现了Parcelable接口,具有可序列化能力,而Parcel类具有能处理序列化数据的能力,用于处理Book传输前的基本操作,BinderProxy具有传输能力。有一种很形象的说法,实现了Parcelable接口的Book对象是货物,而Parcel类是包装货物的箱子,writeInterfaceToken用于标明箱子地址,BinderProxy是快递员,负责把装好箱子的货物运输到服务端。
回到代码,从Parcel对象池中获取两个对象,分别是用于存储输入参数的_data对象和接收返回值的_reply对象,writeInterfaceToken给_data对象打上标识,由于getBookList没有输入参数,因此没有_data的填充,直接调用BinderProxy的transact方法传输出去了(底下native代码,Binder驱动,直到服务端),默认情况下,Binder服务端交互是非oneway模式,因此程序会被阻塞在transact方法里,直到服务端返回数据。
接着数据传递到服务端onTransact响应,Stub的getBookList方法被调用,代码如下:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
... ... ...
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
List<Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
... ... ...
return super.onTransact(code, data, reply, flags);
}
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
服务端BookManagerService接收到客户端请求,Stub的onTransact方法被回调,code表示getBookList方法标识,data是客户端传来的参数,reply是服务端返回的数据。代码里首先是enforceInterface验证DESCRIPTOR(与客户端的writeInterfaceToken相对应),然后是调用服务端getBookList的具体实现,返回List对象,接下来就是把数据填充reply,writeNoException表示接口正常执行无异常,writeTypeList是list对象的数据填充,它们会根据简单协议写数据,最后返回客户端请求。
到了客户端,也就是transact方法后,parcel对象reply调用readException(),有异常则会被抛出,否则执行createTypedArrayList,构造出一个客户端的list对象返回到客户端activity调用处。
2. Parcel
2.1 Parcel的Exception处理
readException:
public final void readException() {
int code = readExceptionCode();
if (code != 0) {
String msg = readString();
readException(code, msg);
}
}
public final int readExceptionCode() {
int code = readInt();
if (code == EX_HAS_REPLY_HEADER) {
int headerSize = readInt();
if (headerSize == 0) {
Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
} else {
StrictMode.readAndHandleBinderCallViolations(this);
}
// And fat response headers are currently only used when
// there are no exceptions, so return no error:
return 0;
}
return code;
}
public final void readException(int code, String msg) {
switch (code) {
case EX_SECURITY:
throw new SecurityException(msg);
case EX_BAD_PARCELABLE:
throw new BadParcelableException(msg);
case EX_ILLEGAL_ARGUMENT:
throw new IllegalArgumentException(msg);
case EX_NULL_POINTER:
throw new NullPointerException(msg);
case EX_ILLEGAL_STATE:
throw new IllegalStateException(msg);
}
throw new RuntimeException("Unknown exception code: " + code
+ " msg " + msg);
}
readException首先会readExceptionCode,里面readInt,从序列化数据的开头读取int大小的数据(EX_HAS_REPLY_HEADER也是无异常的一种,用于GatheredViolations,不影响暂时不用管,因为其最后还是返回0),readExceptionCode返回值是0表示无异常,readException便可返回了;而非零表示有异常(不同异常拥有不同异常码),然后ReadString,序列化数据int之后字符串String大小的数据被读取,表示异常的message信息,包含int和String两个参数的readException被调用,用于new并抛出一个java异常。
writeException:
public final void writeException(Exception e) {
int code = 0;
if (e instanceof SecurityException) {
code = EX_SECURITY;
} else if (e instanceof BadParcelableException) {
code = EX_BAD_PARCELABLE;
} else if (e instanceof IllegalArgumentException) {
code = EX_ILLEGAL_ARGUMENT;
} else if (e instanceof NullPointerException) {
code = EX_NULL_POINTER;
} else if (e instanceof IllegalStateException) {
code = EX_ILLEGAL_STATE;
}
writeInt(code);
StrictMode.clearGatheredViolations();
if (code == 0) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
writeString(e.getMessage());
}
public final void writeNoException() {
if (StrictMode.hasGatheredViolations()) {
writeInt(EX_HAS_REPLY_HEADER);
final int sizePosition = dataPosition();
writeInt(0); // total size of fat header, to be filled in later
StrictMode.writeGatheredViolationsToParcel(this);
final int payloadPosition = dataPosition();
setDataPosition(sizePosition);
writeInt(payloadPosition - sizePosition); // header size
setDataPosition(payloadPosition);
} else {
writeInt(0);
}
}
writeNoException直接writeInt 0 值,即使是写EX_HAS_REPLY_HEADER,在readException时会当做无异常处理。接下来是writeException,通常在catch语句块里调用,用于把catch来的exception写入到序列化数据里,如果写入的exception是可识别的,那么就可成功写入到序列化数据里,否则writeException方法会抛出一个runtime异常。
2.2 getBookList Parcel的包装与解包
服务端包装List<Book>对象返回,writeTypedList(List<Book>):
public final <T extends Parcelable> void writeTypedList(List<T> val) {
if (val == null) {
writeInt(-1);
return;
}
int N = val.size();
int i=0;
writeInt(N);
while (i < N) {
T item = val.get(i);
if (item != null) {
writeInt(1);
item.writeToParcel(this, 0);
} else {
writeInt(0);
}
i++;
}
}
//Book.java
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
writeTypedList是把List<Book>对象转化成序列化数据,首先如果List<Book>对象为null,则序列化数据里写入int值-1并返回,否则序列化数据写入list长度,然后循环写入Book对象的成员变量,假如list里有空对象存在,写入int值0表示。
客户端解包获取List<Book>对象,createTypedArrayList(Parcelable.Creator<T>):
public final <T> ArrayList<T> createTypedArrayList(Parcelable.Creator<T> c) {
int N = readInt();
if (N < 0) {
return null;
}
ArrayList<T> l = new ArrayList<T>(N);
while (N > 0) {
if (readInt() != 0) {
l.add(c.createFromParcel(this));
} else {
l.add(null);
}
N--;
}
return l;
}
//Book.java
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
createTypedArrayList是把序列化数据转换成List<Book>对象,首先读取出序列化数据开始存的list长度int信息,然后用于构建一个ArrayList对象,之后就是循环读序列化数据然后填充到L该ArrayList对象中,如果读到0,add一个空对象,最后改ArrayList对象作为返回值返回。
值得注意的是,Book的writeToParcel和构造函数Book(Parcel),里面字段write打包的顺序要和read解包顺序要对应上,否则会得到一堆错误的数据。
addBook的流程与上类似,不在说明。至此demo的应用Binder探索已全部讲完。
三、Binder扩展
上面demo通过bindService,让客户端Activity进程能与服务端BookManagerService进程能相互通信,然而这种方式与在客户端activity里调用getSystemService获取远程服务并与其交互的方式有什么区别呢?参照网络上的资源加上自己的理解,在此做个记录。
两者通信原理都是Binder机制,getSystemService方式的服务在一开始便向serviceManager注册的binder实体和名字(列表保存着各个服务的BinderProxy及其包装类),这样客户端便获得该服务的Binderproxy对象,从而进行交互,这种Binder被称为实名Binder;相对应的bindService方式,并没有向serviceManager发起注册和请求,这种Binder称为匿名Binder,匿名Binder为通信双方建立一条私密通道,只要服务端没有把自己的binder对象发给别的进程,别的进程就无法通过穷举或猜测等方式获得该Binder的引用,向该服务端发送请求,然而这种方式,客户端是如何获取服务端的匿名binder的呢?
原来,服务端可以通过已经建立好的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。什么意思呢?我们知道A进程只要拿到B进程的BinderProxy对象,便可与进程B通信,而BinderProxy的获取方式有两种,前面提到过
- 客户端通过查询ServiceManager获取,一开始要注册好,也就是实名Binder方式。
- 客户端和服务端都连接到第三方的实名binder通信进程上,相互交换各自的Binder,也就是匿名Binder方式。
举个例子,客户端Activity调用bindService,通过实名ActivityManagerService的Binder连接到了AMS,而在新进程里创建的BookManagerSevice服务端也同样要也连接到了AMS上,当bindService执行到服务端,BookManagerService会返回自己的Binder对象,该Binder是没有在ServiceManager注册过得,然后通过第三方AMS实名Binder传输,最终作为BinderProxy返回给客户端Activity,这个传送匿名Binder是通过Parcel的writeStrongBinder/readStrongBinder。
bindService的具体流程图,来自于网络
Binder在用户空间和内核空间的一次拷贝完成一次交互,来自于网络