Android的进程间通信(三) 之 AIDL 通信 之AIDL使用方式与源码解析

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(默认)的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值