通过AIDL看Binder

上一章我们简单的了解了一下Binder的知识,但是日常开发中我们最容易接触到的关于Binder相关的就数Aidl开发最多了,于是每次做Aidl开发的时候我们都需要打开百度,一顿操作,一顿CV。最后程序一执行还是不停报错,或者勉强完成了这次的小功能,下次来个新功能,又要重复上述操作。究其根本原因就是我们只是为了完成任务,却没有去真正的弄明白Aidl开发是个怎么回事,没有弄明白它的基本原理是什么。本人其实也是上述描述的这样的状态,所以才有了这篇文章。那么接下来就是敲黑板,划重点了。

一、初识Aidl

一天,师傅让我去完成一个跨应用调用的功能,说是用Aidl来完成。听完我是懵逼了好几秒。因为我根本没听过Aidl这个词。于是开始打开百度(菜鸟面向百度编程,你懂的)一顿搜索,看了不下十篇文章,但还是一脸懵逼,因为网上好像都只是教你怎么简单是去使用,比如他们的代码都只是相互之间传递基本数据类型或者String。但是却没有说怎么传递自定义类对象,更不用说去讲解Aidl的内部结构,以及它的实现原理了。但是任务又很急容不得我去学习这么多。于是就照虎画猫去完成任务了。代码一提交,一身轻松,索然无味。之前所有的不解也都抛诸脑后了(管它怎么实现的,是什么原理。先快活来局吃鸡再说)。就这样不懂依旧不懂,恶性循环着。

今天我们就来讲一讲上面提到的几点困惑。

二、Aidl的创建和使用

1、创建Aidl类

Android中Aidl文件需要一个特定的文件夹来存放,所以首先我们需要创建存放Aidl文件的文件夹。

 创建完Aidl文件夹后,接下来需要做的就是去定义我们的Aidl接口了,也就是.aidl文件。

 这里我们定义的一个Aidl接口是这样子的:

 这里我们就用到了上面说到的自定义类用于跨进程传输,而且是个嵌套的自定义类,嵌套的自定义类?啥意思呢:就是说虽然aidl接口传输的直接对象只有一个,但是实际上在这个类对象的内部还有其他的类对象,这如同上图的AidlChild对象。敲黑板了哈:这个时候就需要注意了,只要用于传输的对象,不管是直接的还是间接的(内部变量)都需要对应的声明一个同包名同类名的aidl文件,且在接口文件中要导入这些文件的全路径类名。

2、如何创建自定义类的声明

上面提到了自定义类的aidl声明,但是这个自定义类的adil声明长啥样子呢?

再来看看Java实现类是啥样子的:

Java中进行进程间通信的时候是需要做序列化和反序列化操作,目的是将字节码转换成底层识别的二进制码。至于还是不懂为什么要继承Parcelable接口的同学,可以去看一下之前的序列化机制。这里就不再细说了。

这里需要说的是另外一个需要注意坑,又敲黑板了哈:很多同学可能在初用Aidl的时候,在为自定义类创建aidl声明的时候可能会遇到Interface Name must be unique,如下面这样的错误:

解决方案,已在截图中给出,请自行脑补。

3、使用Aidl接口

当我们定义好Aidl接口和声明、定义好需要传输的自定义类之后离我们使用它就更进了一步了。但是在使用Aidl之间我们还是要重新说一下Binder的C/S模型。由于Aidl是基于Binder机制实现IPC通信的,那么它必然也需要遵循这样的设计模型。于是它就有了两个重要的内部内Stub和Proxy。下面我们在讲解Aidl内部构造和原理的时候会详细讲解这两个类。这里我们只需要弄清楚Stub和Proxy在整个模型中分别充当着Service和Client的角色就行了。但是Stub又是个继承自IBinder的抽象子类,需要我们自己去继承它并实现aidl接口。还是不明白?那我们简单点:Proxy就是负责发送接口调用请求,Stub负责接收调用结果。如果还是不懂,等会看下面Aidl内部结构详解吧。我们先来看看Aidl如何使用。

(1)、实现Aidl接口

上面我们一直都在说Aid接口,那么既然是接口就一定需要人去实现。而Aidl的使用都是配合Service一起使用的

这里我们就能够看到Binder的影子了。每个通过bindService方法启动的Service都需要返回一个IBinder代理。前面我们也说了Stub就是IBinder的抽象子类,所以这里我们返回Stub实例。

(2)跨进程调用

上面我们已经说过了,Aidl是协同Service来使用的,所以跨进程调用也是通过绑定服务的方式来实现远程调用。

(3)、运行结果

到底能不能完成功能要跑起来才算数,所以:

完美获得婧儿小宝贝一枚。

三、Aidl内部结构以及原理

1、Android studio通过Aidl工具帮我们完成的Aidl接口实现类是什么样的

想要知道它长什么样,扒开看就是了:

public interface IMyAidlInterface extends android.os.IInterface
{
public static abstract class Stub extends android.os.Binder implements com.demo.singlecode.myaidl.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.demo.singlecode.myaidl.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
private static class Proxy implements com.demo.singlecode.myaidl.IMyAidlInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
..........
}
..........
static final int TRANSACTION_getGroupByName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
/**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
public com.demo.singlecode.myaidl.AidlGroup getGroupByName(java.lang.String name) throws android.os.RemoteException;
}

将中间的一些方法全都去掉,我们发现Aidl接口中就两个东西,一个抽象的内部类Stub和一个私有静态内部类Proxy。于是问题来了,这两个内部类都是干嘛的呢?眼尖的朋友可能还会发现Proxy中有一个IBinder对象mRemote.而Stub又正好是继承自Binder的子类。他们之间是否有某种关联呢?

2、Stub和Proxy各自扮演什么角色

要弄明白Stub和Proxy各自扮演什么角色,我们先来看一张图吧。

通过这张图的流程我们能够了解到如下几点:

  • Proxy通过mRemote的transact方法将请求发送到对端的Stub
  • Stub在onTransact方法中处理接收到的请求,然后将请求结果返回。
  • mRemote作为Binder在Java层的代理,完成链接C/S端并传输请求和数据的任务,这里需要清楚C端的mRemote对应着S端的Stub实例。

但是上面这几点,代码是怎么实现的呢?还是得扣代码来看。但是代码怎么扣呢?我们就从C端绑定服务后获取Aidl接口的地方作为入口吧

(1)重新认识 Stub.asInterface()

public static com.demo.singlecode.myaidl.IMyAidlInterface asInterface(android.os.IBinder obj)
{
    //判断绑定的S端是否返回有效的Binder代理,所以小伙伴们要注意了,我们调用该方法获取返回值后需要做NPE判断
if ((obj==null)) {
return null;
}
/**
 * 有人很纳闷这里返回为什么要分情况啊?这里就解释一下
 * 1、第一个return我们可以看到有一个instanceof这样的判断,如果为Ture直接返回。
 *      这种情况是因为有可能我们在同一个进程中使用Aidl了,并没有去跨进程。所以这时候就能够直接返回本地的Aidl接口实例了
 * 2、当发现是跨进程调用时,就返回一个Proxy对象,且将Binder代理作为入参。
 */

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.demo.singlecode.myaidl.IMyAidlInterface))) {
return ((com.demo.singlecode.myaidl.IMyAidlInterface)iin);
}
return new com.demo.singlecode.myaidl.IMyAidlInterface.Stub.Proxy(obj);
}

(2)神秘的Proxy到底在干嘛

通过上面的asInterface()我们知道了,只要跨进程,最终返回的是一个Proxy对象,然后调用的方法就是在调用Proxy。显然Proxy相当于一个继承了Aidl接口的壳(像极了动态代理模式有没有?)。那么它是如何实现将调用请求发送出去并接收返回结果的呢?我们直接来看它内部重写的Aidl接口方法都干了什么

@Override public com.demo.singlecode.myaidl.AidlGroup getGroupByName(java.lang.String name) throws android.os.RemoteException
{
    /**
     * 这里分配了两个Parcel对象。也可以结合前面Binder一次拷贝的原理,将它理解为在当前进程中分配的两个虚拟内存区,
     * 一个用来存要发送的请求数据,一个用来接收返回的数据。binder驱动分别与该内存区产生映射关系
     *
     */
android.os.Parcel _data = android.os.Parcel.obtain();//发送的数据
android.os.Parcel _reply = android.os.Parcel.obtain();//接收的数据
com.demo.singlecode.myaidl.AidlGroup _result;
try {
    //填充请求的参数数据
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
/** 
 * 调用Binder的transact方法发送请求到对端
 * 但是对端怎么去知道这个请求对应的是那个方法呢?我们来看一下这个transact方法
 * public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)。这里的Code就是每个方法对应的标签
 * static final int TRANSACTION_getGroupByName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);这个标签是依次递增的。这也是为什么要保持C/S端Aidl相关代码和文件一致性的原因之一啦
 */
mRemote.transact(Stub.TRANSACTION_getGroupByName, _data, _reply, 0);
//读取异常
_reply.readException();
if ((0!=_reply.readInt())) {
//读取返回结果
_result = com.demo.singlecode.myaidl.AidlGroup.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
    //最后释放内存,并返回结果
_reply.recycle();
_data.recycle();
}
return _result;
}

上面的代码里面最重要的transact发送请求的方法我们好像还是没有什么概念,但是如果我们在Proxy代码里面去跟代码的时候会发现,只能跟到IBinder这个接口里面。于是问题来了:这个mRmote指向的哪一个对象?答案我们已经在前面给过了,C端Proxy中的mRemote指向S端的Stub对象。而Stub又是继承自Binder的所以我们知道了,最终调用的是Binder的transact。

public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
        int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
//是否看起来好熟悉啊?这个怎么和Stub中的onTransact那么像呢?
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

到这里Proxy的活就完啦,请求也发出去了,返回结果也接收了。但是这个请求发出去后到底怎么处理,结果如何返回就通过Binder交给对端的Stub了。所以前面我们说Proxy的角色就是发送请求是不是很贴切!

(3)Stub是如何处理C端请求的

当C端通过绑定服务的方式与S端建立链接时,就会获取到S端的Stub代理(IBinder),而C端通过Proxy这个壳利用IBinder发送调用请求后,Binder将请求传到S端的Stub。再由Stub的onTransact来完成请求处理和返回结果。

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
    /**
     * 这里的code、data和reply就和Proxy相对应了
 *code:每个Aidl接口的唯一标志,Proxy和Stub两边都保持一致
     * data:Proxy发送过来的请求数据的虚拟内存区
     * reply:Proxy等待接收接口返回值的虚拟内存区
     */
    
java.lang.String descriptor = DESCRIPTOR;
//看到没,这里就是根据code值来判断C端具体调用的是哪一个方法,每个方法对应一个唯一的Code值
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getGroupByName:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();//读取Proxy发送请求时传入的参数值
com.demo.singlecode.myaidl.AidlGroup _result = this.getGroupByName(_arg0);//调用S端的Aidl接口获取接口结果,这里的接口具体实现在上文绑定服务的时候已经讲过啦,不知道的可以翻会去看一下
reply.writeNoException();//接口调用成功,写入无异常
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);//将接口返回值写入接收返回值的虚拟内存中,这里关于虚拟内存的说法是个人为了方便理解的讲法。个人觉得这样能够更容易与Binder的一次拷贝原理对应起来。
}
else {
reply.writeInt(0);
}
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}

在整个通信过程中我们都能够看到这样一个身影IBinder,它就是Binder在Java层的一个代理,我们所有与Binder驱动相关的操作都离不开它的存在。但是日常开发中除了Aidl开发我们几乎很少看到它的影子(这里常看源码的朋友忽略哈,因为源码里面这个东西几乎无处不在)。所以就导致了很多初级的开发人员根本就不知道或者不了解Binder是什么和Binder干了什么。我们这里只是简单的通过Aidl通信小小的窥视了一下它干了什么,至于它的工作原理以及底层实现,只能说太深奥了,要学的太多了。继续努力吧!

发布了29 篇原创文章 · 获赞 3 · 访问量 888

猜你喜欢

转载自blog.csdn.net/LVEfrist/article/details/102727319