AIDL使用方式与源码解析
继上一篇博客,这一篇我们将要介绍AIDL的使用方式和源码解析。先来看官网对于AIDL的定义:
Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。
简单来说,AIDL是一种接口语言,服务端和客户端通过AIDL定义接口后,编译器会自动生成相应的java代码,使二者可以进行IPC通信。
如何使用?
使用的步骤也很简单。
第一步:新建AIDL文件。
第二步:在这个文件里,定义接口(与java接口很类似,语法也很简单)
interface IAIDLService {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
String getPrintf();
void saveRect(in Bundle bundle);
}
然后点击Android studio 的build。
第三步:在服务端实例化xxx.Stub()对象(xxx是接口的名称),并实现里面的抽象方法,这些方法都是在定义AIDL接口时定义的接口。
private IAIDLService.Stub binder = new IAIDLService.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public String getPrintf() throws RemoteException {
return "AIDL content";
}
@Override
public void saveRect(Bundle bundle) throws RemoteException {
bundle.setClassLoader(getClass().getClassLoader());
Rect rect = bundle.getParcelable("rect");
Log.e("TAG","saveRect:" + rect.toString());
}
};
然后,在服务端的onBind方法把这个实例化对象返回。
第四步:在客户端拿到IBinder接口后,用xxx.Stub.asInterface()转化一下后,调用转化之后的对应方法,AIDL会自动调到服务端的对应的方法。
IAIDLService iaidlService = IAIDLService.Stub.asInterface(service);
iaidlService.getPirntf();
心细的同学估计已经发现了,类似Stub、asInterface这样的字眼,我们在上一篇分析Messenger的时候已经见过了。事实上,Messenger是定义了一个有send(Message message)方法的 AIDL接口,在客户端调用send方法后,会来到服务端的send方法,通过send方法接收到Message后进行Handle分发。也就是说:Messenger是写死了一个send接口的AIDL。所以,AIDL更加灵活,可以随意定义我们想要定义的接口。
源码分析
AIDL的底层实现是Binder机制,下一篇文章会重点分析Binder机制的源码(绝大部分是C++层)。这篇文章,我们着重于AIDL的java层源码分析。也是因为有java层封装一层,我们在进行进程间通信才如此简单。
我们从客户端开始。
从上面的分析可以得知,当客户端拿到IBinder接口后,要进行如下操作:
IAIDLService iaidlService = IAIDLService.Stub.asInterface(service);
我们看一下这里的代码实现。
public static com.bzl.a929demo.service.aidl.IAIDLService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.bzl.a929demo.service.aidl.IAIDLService))) {
return ((com.bzl.a929demo.service.aidl.IAIDLService)iin);
}
return new com.bzl.a929demo.service.aidl.IAIDLService.Stub.Proxy(obj);
}
可以看到,这里做了一个判断,如果是同一个进程,则直接强转。如果不同一个进程,则把IBinder接口传入Stub.Proxy,Proxy也只是把IBinder对象存到remote中,然后把Proxy返回。
所以,IAIDLService iaidlService 其实 是 Stub.Proxy。
然后,在随意进行一个方法的调用。例如:
IAIDLService iaidlService = IAIDLService.Stub.asInterface(service);
String printf = iaidlService.getPrintf();
其实是来到了Stub.Proxy.getPrintf()。看一下这里的实现。
@Override
public java.lang.String getPrintf() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getPrintf, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPrintf();
}
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到这里做了一下数据封装,然后通过mRemote.transact方法把数据传输给服务端,然后等待服务端的回复。
mRemote的实现是BinderProxy,而BinderProxy.transact方法中调用native方法,也就到涉及到了C++层。Binder通信的C++层代码我们下一篇再分析,这里就记住mRemote.transact是通过Binder驱动把数据发送给服务端即可。
mRemote.transact发送的数据,会来到服务端的onTransact方法。
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_basicTypes: {
......
return true;
}
case TRANSACTION_getPrintf: {
data.enforceInterface(descriptor);
java.lang.String _result = this.getPrintf();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_saveRect: {
......
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
可以看到这个方法做了数据头的case,我们调用的getPrintf方法,来到TRANSACTION_getPrintf这个case。这个case调用了getPrintf这个抽象方法,然后把返回值用Binder驱动又写了回去。
而getPrintf这个抽象方法就是我们在服务端实例化Stub时所实现的。
所以,AIDL的整个java层的源码流程,大致如下所示:
好了,到了这里我们了解完AIDL的java层源码,在分析的过程中,我们跳过了mRemote.transact这里的c++层代码分析。
因为我们在了解mRemote.transact背后的C++层代码之前,我们要先知道Binder机制到底是什么?以及Binder内核层的代码还有大名鼎鼎的ServiceManager分别是什么样子。
-------- 2021年7月11日补充
AIDL 在传递对象时,有数据流向的概念。
void rect(out Rect1 test);
void rect1(inout Rect1 test);
void rect2(in Rect1 test);
以上的三个方法
**第一个方法的数据流向是从服务端传到客户端。**也就是说,当你在客户端传过去的数据,服务端是接受不到的,但是服务端改变你传过去的数据,你的对象里面的内容会发生改变。
**第二个方法的数据流向是双向。**这种的数据流向和java就完全一模一样,你数据传进去,服务端是可以接收到的,服务端改变,客户端这边也会变化。虽然和java一模一样,但是实现方式完全不一样,java是改变内存,AIDL是重新读了一遍。
**第三个方法的数据流向是从客户端到服务端。**也就是说,你传对象过去,服务端能收到,但是服务端改变你的这个对象,你不会随着改变。
**AIDL 在实现异步调用时,有一个关键字,叫做oneway。**它可以修饰在方法。如下:
oneway void test2(in int[] test);
修饰了这个关键字,这个方法变成了异步。也就是说,当你服务端这个方法需要执行30秒,客户端是不会阻塞的,因为AIDL修饰了out 和 inout时,数据流向可以从服务端流向客户端,而数据流向的实现原理本质上通过java层重新又读了一遍结果。所以,当你修饰了这个关键字,异步和重新读是冲突的,所以oneway只能用于in(默认)的情况。