aidl一种android接口描述语言,本文主要是对.aidl文件自动生成的.java文件的具体源码进行分析,描述AIDL生成的java类细节。

AIDL实例分析

1.客户端的操作

  1. 获取aidl的对象

  2. 通过获取的对象调用方法(1,2,3…)

 1mContext?.bindService(aidlIntent, mAidlConnect, Context.BIND_AUTO_CREATE)
 2
 3private var mAidlConnect: ServiceConnection = object:ServiceConnection{
 4    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
 5        //1.获取aidl的对象
 6        mAidlClient = IDataUpdate.Stub.asInterface(service)
 7        initView()
 8    }
 9
10    override fun onServiceDisconnected(name: ComponentName?) {
11        Log.d(TAG, "->>>mAidl disConnect")
12        mAidlClient = null
13    }
14}
15
16private fun initView() {
17    //2.通过获取的对象调用方法(1,2,3...)
18    var ret = mAidlClient?.StartUp()
19    for (i in 0  .. 100)
20    {
21        mAidlClient?.DialogUpdate(i)
22    }
23}

2.服务端的操作

  1. 实现aidl.stub类(继承或者内部匿名的方式)

  2. 回调中实现方法(1,2,3…)

 1//1.实现aidl.stub类(继承或者内部匿名的方式)
 2private val dataUpdateBinder: IDataUpdate.Stub  = object : IDataUpdate.Stub(){
 3    //2.回调中实现方法(1,2,3...)
 4    override fun StartUp(): Int {
 5        return 0
 6    }
 7
 8    override fun DialogUpdate(progress: Int) {
 9        if (!DialogFlag) return
10        dataUpdate(progress)
11    }
12
13}

服务端和客户端之间是通过IDataUpdate.java来做到通信的

AIDL生成的java文件

首先列出这个java文件的组成,如下图所示,可以看到IDateUpdate.java中除了需要实现的两个方法DialogUpdate和StartUp之外,还有两个类,都实现了IDateUpdate.java的接口。

IDateUpdate.java组成 IDateUpdate.java组成

1.Default类

Default类里面就是一个空操作,这里不展开描述

1/** Default implementation for IDataUpdate. */
2public static class Default implements com.example.aidl.IDataUpdate
3{
4    @Override public int StartUp() throws android.os.RemoteException{return 0;}
5    @Override public void DialogUpdate(int progress) throws android.os.RemoteException{}
6    @Override public android.os.IBinder asBinder() {return null;}
7}

2.Stub类

这里最重要的是Stub类,实现了IDateUpdate.java的接口之外,还继承了Binder,并且可以从IDateUpdate.java整体的结构中看到内部还有一个Proxy.java,同样也实现了IDateUpdate.java的接口。

 1public static abstract class Stub extends android.os.Binder implements com.example.aidl.IDataUpdate
 2{
 3    private static final java.lang.String DESCRIPTOR = "com.example.aidl.IDataUpdate";
 4    static final int TRANSACTION_StartUp = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
 5    static final int TRANSACTION_DialogUpdate = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
 6    public Stub(){this.attachInterface(this, DESCRIPTOR);}
 7    public static com.example.aidl.IDataUpdate asInterface(android.os.IBinder obj){...}
 8    @Override public android.os.IBinder asBinder(){return this;}
 9    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{...}
10    //内部类,实现了IDataUpdate接口
11    private static class Proxy implements com.example.aidl.IDataUpdate{...}
12    public static boolean setDefaultImpl(com.example.aidl.IDataUpdate impl) {...}
13    public static com.example.aidl.IDataUpdate getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}
14}

AIDL源码流程

1.从客户端开始,首先获取aidl的对象

1)判断是否是跨进程

如果是就通过Proxy代理类处理,不然的话就无须Binder。

 1//获取aidl对象,这里的obj是从onServiceConnected传过来的IBinder对象
 2public static com.example.aidl.IDataUpdate asInterface(android.os.IBinder obj)
 3{
 4    if ((obj==null)) {
 5        return null;
 6    }
 7    //查看是否是跨进程,如果是本地就无须进行Binder
 8    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
 9    if (((iin!=null)&&(iin instanceof com.example.aidl.IDataUpdate))) {
10        return ((com.example.aidl.IDataUpdate)iin);
11    }
12    //属于跨进程,就需要通过Proxy类代理
13    return new com.example.aidl.IDataUpdate.Stub.Proxy(obj);
14}

2)queryLocalInterface

这里主要用来检查是否为跨进程,注意两点1.传入值descriptor 2.mOwner,这个是Binder的私有属性,后面服务端会阐述作用。

1//Binder.java#queryLocalInterface
2//这里注意两点1。传入值descriptor 2.mOwner,这个是Binder的私有属性
3public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
4    if (mDescriptor != null && mDescriptor.equals(descriptor)) {
5        return mOwner;
6    }
7    return null;
8}

3)完成检查之后,调用Proxy的构造

这个构造实际上相当于是Stub对象在内部Proxy类进行注入操作。

1private android.os.IBinder mRemote;
2Proxy(android.os.IBinder remote)
3{
4    mRemote = remote;
5}

最终返回的aidl的对象是Proxy中的mRemote

2.通过获取的对象调用方法(1,2,3…)

1)客户端用IBinder调用方法

因为实际上的对象就是Proxy类的IBinder,即跨进程的调用方法事件交给代理类。(即客户端调用,在Proxy实现)

代理类中有两个包,_data和 _reply。可以分别看做是行李包和纪念品包。行李包的特点是从客户端发起向服务端发送的数据,纪念品包是从服务端发起向客户端发送的数据。

 1@Override public int StartUp() throws android.os.RemoteException
 2{
 3    //android.os.Parcel.obtain这个用到了线程池的操作,跟Message的获取类似,属于享元模式
 4    //行李包
 5    android.os.Parcel _data = android.os.Parcel.obtain();
 6    //纪念品包
 7    android.os.Parcel _reply = android.os.Parcel.obtain();
 8    int _result;
 9    try {
10        _data.writeInterfaceToken(DESCRIPTOR);
11        boolean _status = mRemote.transact(Stub.TRANSACTION_StartUp, _data, _reply, 0);
12        if (!_status && getDefaultImpl() != null) {
13            return getDefaultImpl().StartUp();
14        }
15        _reply.readException();
16        _result = _reply.readInt();
17    }
18    finally {
19        //清空、回收parcel对象的内存
20        _reply.recycle();
21        _data.recycle();
22    }
23    return _result;
24}
25@Override public void DialogUpdate(int progress) throws android.os.RemoteException
26{
27    android.os.Parcel _data = android.os.Parcel.obtain();
28    android.os.Parcel _reply = android.os.Parcel.obtain();
29    try {
30        _data.writeInterfaceToken(DESCRIPTOR);
31        _data.writeInt(progress);
32        boolean _status = mRemote.transact(Stub.TRANSACTION_DialogUpdate, _data, _reply, 0);
33        if (!_status && getDefaultImpl() != null) {
34            getDefaultImpl().DialogUpdate(progress);
35            return;
36        }
37        _reply.readException();
38    }
39    finally {
40        _reply.recycle();
41        _data.recycle();
42    }
43}

然后调用到transact会挂起客户端线程,0是同步,1是异步,也可以用android.os.IBinder.FLAG_ONEWAY表示。默认AIDL为同步操作,需要等待_result的返回值,但是异步操作是不需要 _reply的。调用结束之后,每次都会清空、回收parcel对象的内存。

不要在服务端oneway接口中处理耗时操作,一旦用于高频调用,服务端又处理耗时,再偶尔碰上cpu负荷高,很可能会发生其他关键调用偶现失败的隐蔽问题,而且很难复现,隐患就一直在那儿了。具体原因分析请点击

2)transact方法

通过Proxy中的transact方法调用,最终调用到Binder.java里面的transact方法,具体的类似Parcel中data操作的setDataPosition的方法含义详见下表,不具体展开说明。

表1 Parcel一些常用的方法

方法名称 作用
obtain 获得一个新的parcel ,相当于new一个对象
dataSize 得到当前parcel对象的实际存储空间
dataCapacity 得到当前parcel对象的已分配的存储空间, >=dataSize()值 (以空间换时间)
dataPostion 获得当前parcel对象的偏移量(类似于文件流指针的偏移量)
setDataPosition 设置偏移量
recyle 清空、回收parcel对象的内存
writeInt 写入一个整数
writeFloat 写入一个浮点数
writeDouble 写入一个双精度数
writeString 写入一个字符串
writeException Parcel队头写入“无异常“
readException 在Parcel队头读取,若读取值为异常,则抛出该异常;否则,程序正常运行

可以看到transact方法会调用到onTransact方法,因为这个this是从Proxy传过来,而Proxy的操作的IBinder对象实际是Stub中的IBinder对象,即最终调用到的onTransact,实际上是在Stub中回调的onTransact的方法。

 1//Binder.java#transact
 2public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
 3                              int flags) throws RemoteException {
 4    if (false) Log.v("Binder", "Transact: " + code + " to " + this);
 5
 6    if (data != null) {
 7        data.setDataPosition(0);
 8    }
 9    boolean r = onTransact(code, data, reply, flags);
10    if (reply != null) {
11        reply.setDataPosition(0);
12    }
13    return r;
14}

3)onTransact

 1//IDataUpdate.java中的Stub类#onTransact
 2@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
 3{
 4    java.lang.String descriptor = DESCRIPTOR;
 5    switch (code)
 6    {
 7        case INTERFACE_TRANSACTION:
 8            {
 9                reply.writeString(descriptor);
10                return true;
11            }
12        case TRANSACTION_StartUp:
13            {
14                data.enforceInterface(descriptor);
15                int _result = this.StartUp();
16                reply.writeNoException();
17                reply.writeInt(_result);
18                return true;
19            }
20        case TRANSACTION_DialogUpdate:
21            {
22                data.enforceInterface(descriptor);
23                int _arg0;
24                _arg0 = data.readInt();
25                this.DialogUpdate(_arg0);
26                reply.writeNoException();
27                return true;
28            }
29        default:
30            {
31                return super.onTransact(code, data, reply, flags);
32            }
33    }
34}

可以看到通过Proxy对IBinder操作,传过来的是一系列的code,对应的code进行处理,通常会返回true,在Stub类里面找不到对应的code,就回到Binder.java中onTransact去查找对应的code。

而code里边的处理是和服务端紧密联系的,并且如果客户端需要有返回值这里就会会返回。

通过上面的代码可以得知实际上调用到了int _result = this.StartUp()和this.DialogUpdate(arg0),其中这里面的this就是Stub对象

3.服务端的操作

实现aidl.stub类 —-继承或者内部匿名的方式

这里的例子是服务端通过内部匿名类的形式实现,具体看上面的服务端实现操作。

1private val dataUpdateBinder: IDataUpdate.Stub  = object : IDataUpdate.Stub(){}

这个实际上做了两件事创建了一个匿名内部类,并且调用了IDataUpdate.Stub()的构造方法

1//IDataUpdate.java中的Stub类#Stub
2private static final java.lang.String DESCRIPTOR = "com.example.aidl.IDataUpdate";
3/** Construct the stub at attach it to the interface. */
4public Stub()
5{
6    this.attachInterface(this, DESCRIPTOR);
7}

可以看到实际上构造方法,传入了两个参数到attachInterface方法中,一个是Stub对象本身,另一个定义的AIDL类名。

1//Binder.java#attachInterface
2public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
3    mOwner = owner;
4    mDescriptor = descriptor;
5}

可以看到实际上这里传入的mOwner是Stub对象,mDescriptor是定义的AIDL类名。

结合上述queryLocalInterface中遗留下来的1.传入值descriptor 2.mOwner,

现在可以知道其实queryLocalInterface查询做了两件事:

1.优先得知mOwner是否为空,如果为空,则不是两个进程

2.比对AIDL类名是否相同(因此AIDL文件需要对应放在相同目录

4.回调中实现方法(1,2,3…)

这个对象正好是IDataUpdate.Stub对象,跟上面在Stub类中onTransact的处理为Stub对象吻合,即可以知道从Stub类中调用的onTransact方法,真正的实现是服务端。

 1private val dataUpdateBinder: IDataUpdate.Stub  = object : IDataUpdate.Stub(){
 2    override fun StartUp(): Int {
 3        //真正实现的方法1
 4        return 0
 5    }
 6
 7    override fun DialogUpdate(progress: Int) {
 8        //真正实现的方法2
 9        return
10
11    }
12}

总结

1.UML调用关系,下图所示

AIDL UML图 AIDL UML图

2.根据客户端和服务端的调用,如下图所示。

客户端和服务端之间为AIDL,AIDL的内部调用在红色边框内部,也是简洁明了。

实际上客户端和服务端的调用就是操作Parcel数据,这个是一种共享内存,即客户端和服务端可以抽象成虚线所示的调用关系。绿色部分为客户端进程,蓝色部分为服务端进程,红色部分为AIDL的流程部分。

AIDL客户端和服务端的调用图 AIDL客户端和服务端的调用图

3.ConnectService调用

另外,这里没有过多阐述连接Service的过程,这里简单通过时序图来表示了调用关系,具体深入详解ConnectService后续更新

ConnectService调用时序图 ConnectService调用时序图

猜你喜欢

AIDL的使用

参考

[1] qinjuning, Android中Parcel的分析以及使用, 2011.

[2] 杰杰_88, AIDL oneway 方法的隐患, 2020.

[3] kururunga, Android AIDL源码分析, 2017.

[4] 不会写代码的丝丽, AIDL源码分析, 2017.

[5] anlian523, AIDL oneway 以及in、out、inout参数的理解, 2019.