AIDL2 源码分析
900 Words|Read in about 4 Min|本文总阅读量次
aidl一种android接口描述语言,本文主要是对.aidl文件自动生成的.java文件的具体源码进行分析,描述AIDL生成的java类细节。
AIDL实例分析
1.客户端的操作
-
获取aidl的对象
-
通过获取的对象调用方法(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.服务端的操作
-
实现aidl.stub类(继承或者内部匿名的方式)
-
回调中实现方法(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的接口。
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调用关系,下图所示
2.根据客户端和服务端的调用,如下图所示。
客户端和服务端之间为AIDL,AIDL的内部调用在红色边框内部,也是简洁明了。
实际上客户端和服务端的调用就是操作Parcel数据,这个是一种共享内存,即客户端和服务端可以抽象成虚线所示的调用关系。绿色部分为客户端进程,蓝色部分为服务端进程,红色部分为AIDL的流程部分。
3.ConnectService调用
另外,这里没有过多阐述连接Service的过程,这里简单通过时序图来表示了调用关系,具体深入详解ConnectService后续更新
猜你喜欢
参考
[1] qinjuning, Android中Parcel的分析以及使用, 2011.
[2] 杰杰_88, AIDL oneway 方法的隐患, 2020.