探究与 Binder 相关的 Java 类
要说这些类,我们首先要用到它们,最简单的方式就是去创建一个 Service,让它运行在单独的进程中,然后编写 AIDL,实现一个接口,再到 Activity 中去使用这个接口。(注意:本文不介绍 Remote Service 与 AIDL 的相关知识,如果读者对这部分内容还不够了解,请先将它们弄懂再回来看本文)
AIDL 代码生成器为我们创建了一个 java 文件,这里面涉及到几个类:
image
这些就是在应用层使用 Binder 所需的所有类了,看类图有些错综,但实际还是很简单的。
AIDL 生成的就是 IMyService
这个接口,而 Stub
和 Proxy
则是这个接口的两个内部类,分别是 Binder
类和 BinderProxy
类的子类(Proxy
类虽然是用组合方式来持有 BinderProxy
的,但实际就是在直接用这个类,只不过做了一层封装,让其更易使用而已),Stub
和 Proxy
都实现了 IMyService
。
所以 IInterface
到底是什么,它就是一个用于表达 Service
提供的功能的一个契约,也就是说 IInterface
里有的方法,Service
都能提供,调用者你别管用的是 BinderProxy
还是什么,只要拿到 IInterface
,你就可以直接调用里面的方法,它就是一个接口。
同时 Stub
虽然实现了 IMyService
,但是并没有实现厘米的任何方法,它是一个抽象类,开发者需要自己子类化 Stub
去实现具体的功能。
Proxy
实现了 IMyService
,并且实现了里面的方法,这些方法的实现我们下面再说。
为什么 IMyService
要分 Stub
和 Proxy
呢?这是为了要适用于本地调用和远程调用两种情况。如果 Service 运行在同一个进程,那就直接用 Stub
,因为它直接实现了 Service 提供的功能,不需要任何 IPC 过程。如果 Service 运行在其他进程,那客户端使用的就是 Proxy
,这里这个 Proxy
的功能大家应该能想到了吧,就是把参数封装后发送给 Binder 驱动,然后执行一系列 IPC 操作最后再取出结果返回。
好,到这里请求用 Proxy
发出去了,Service 怎么接受请求并作出响应呢,这就需要 Stub
了,还记得我们的 Stub
是继承自 Binder
的吗?Binder
有一个 onTransact
方法,而 Stub
重写了这个函数,这个函数三个重要参数:int code
、Parcel data
、Parcel reply
,分别对应了被调函数编号、参数包、响应包。当 Proxy
发起了一个请求,服务端中相应的响应线程就会通过 JNI 调用到 Stub
类,然后执行里面的 execTransact
方法,进而转到 onTransact
方法。(这一系列调用链大家可以从源码中分析出来,我这里作为抛砖引玉,就不带大家分析 native 层的代码了)
我们来看一眼这个 onTransact
:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_increaseCounter:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _result = this.increaseCounter(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
是不是非常清晰易懂,就是根据传过来的数据包来做相应的操作,然后把结果写回数据包,Binder 驱动会来帮我们做好这些数据包的分发工作。而这段代码是运行在 Service 本地进程中的,它可以直接调用实现好的 Stub 类中的相关方法(本例子中是 increaseCounter
方法)。
下面我们趁热打铁再来看一眼 Proxy
类中的 increaseCounter
是怎么实现的:
@Override
public int increaseCounter(int increment) throws RemoteException{
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(increment);
mRemote.transact(Stub.TRANSACTION_increaseCounter, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
正好与 Stub
中的处理能够对应起来,其实这两段代码就是整个 IPC 的核心了,Binder 驱动和 Binder 类在底层帮我们做好了其他一切事情。
休息一下。
下面我们来思考另一件事情,如何判断 Service 运行在同一进程还是不同进程?
我们知道,Service
有一个 onBind
方法,这里面就返回了我们实现好的 Stub
类,而客户端 bind service 时拿到的又是一个 IBinder
对象,我们每次只需要调用 Stub
的 asInterface
静态方法,把这个 IBinder
对象传进去就能拿到 Stub
类或者 Proxy
类了,看起来十分智能!那么这个 asInterface
又蕴藏什么玄机呢?我们来看一眼实现代码:
public static IBackgroundService asInterface(IBinder obj){
if ((obj==null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBackgroundService))) {
asInterface(IBinder obj){
if ((obj==null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBackgroundService))) {