AIDL使用规范及调用过程解析(Android Q)

AIDL使用介绍


AIDL的全称是Android Interface definition language,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口,用处当然就是用来进程间的通信和方法调用了。其实AIDL是Binder的一个上层实现,它简化了Binder的使用,在编译时,由编译器帮助我们完成了大量工作(例如,生成AIDL文件所对应的.java类)。

先介绍一下AIDL进程间通信的流程。

AIDL通信流程简介

1. AIDL接口的创建

AIDL文件中,并不是所有的数据类型都是可以使用的,它支持的数据类型有:

  • 基本数据类型(int,long,char,boolean,double等)
  • String和CharSequence
  • List:只支持ArrayList,而且list中的元素也必须是 AIDL 支持的类型
  • Map:只支持HashMap,里面的key和value也必须是AIDL支持的类型
  • Parceable:所有实现了 Parceable 接口的对象
  • AIDL:所有的 AIDL 接口本身也可以在 AIDL 文件中使用,所以 IBinder 类型也是支持的

2. 服务端

服务端首先要创建一个 Service 用来监听客户端的请求,然后将在对应AIDL文件中声明的接口实现,并且通过onbind函数返回相应 IBinder 对象即可。

3. 客户端

客户端所要做的事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的 IBinder 对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

AIDL文件及参数说明

自定义类型

通常,AIDL默认支持基础类型参数及返回值的数据传递。如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

例如,我们定义了一个名为RequestData的数据类型,要想在AIDL中使用它,除了它自己的RequestData.java文件之外,我们还必须定义一个名为RequestData.aidl的文件(包名必须和RequestData.java中的相同),内容如下:

package com.testaidl;
parcelable RequestData;

参数标志

AIDL中除了基本数据类型,其他类型的参数必须标上方向:in,out或者inout, in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。

我们要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的。

接口及同步

AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。

客户端调用服务端方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,所以如果知道服务端的一个方法是耗时的,就要避免在客户端的UI线程中去调用该远程方法。

但是有一种方法可以发起非阻塞式的远程调用:就是在声明AIDL时,加上oneway关键字。

相比平常自定义的 aidl,多了 oneway 的关键字,声明和不声明 oneway 关键字的在于生成 Java 类中一个参数:
在这里插入图片描述在这里插入图片描述
不声明 oneway 时,mRemote.transact 传入的最后一个参数是 0;声明 oneway 时,mRemote.transact 传入的最后一个参数是 android.os.IBinder.FLAG_ONEWAY 。

FLAG_ONEWAY 的作用就是让客户端能够非阻塞的调用远程方法。

系统Service的调用示例


我们以系统服务的调用来分析AIDL的使用过程。

系统服务的调用过程

首先通过 ServiceManager.getService()方法获取一个IBinder对象,但是这个IBinder对象不能直接调用,必须要通过asInterface方法转成对应的IInterface对象才可以使用,如果在同一个进程中(当然,这是跨进程通信,这种情况很少),就会直接返回通过attachInterface方法设置的IInterface对象(上面代码所述),但是如果不是在同一个进程中,就会先通过IBinder对象创建一个Proxy对象(服务的本地代理对象),然后在Proxy对象中通过调用IBinder对象的transact方法调用到服务进程的Service中,实现了跨进程的通信。

以AMS为例,我们来看具体实现。

AMS的服务调用关键实现

AIDL 文件生成的类中会自动生成两个类,Proxy 类和 Stub 类,对应的是 ActivityManagerProxy 和 ActivityManagerService 类。

我们使用ActivityManager实现AMS通信时,在其内部都是调用ActivityManagerNative来实现的。我们来看相关类的继承关系:

我们在应用层调用,都会调用到ActivityManagerNative.getDefault()方法,我们来看源码:

    static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }

        return new ActivityManagerProxy(obj);
    }
    static public IActivityManager getDefault() {
        return gDefault.get();
    }
    
        private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
逻辑解析:
  1. getDefault返回一个Singleton对象。
  2. ServiceManager.getService(“activity”)返回AMS的IBinder对象,该对象需要转换后使用。
  3. asInterface()方法把AMS的IBinder对象转换为可供使用的对象。

asInterface()方法调用结果会有2种情况,第一种是,如果调用者和被调用服务在同一个进程,则进行本地通信,直接返回当前对象即可;第二种是,如果调用者和被调用服务是在不同进程,则会通过服务的BinderProxy实现跨进程调用。我们分别来分析。

同一个进程

queryLocalInterface 函数如果不为null,则在同一进程中通信。

queryLocalInterface 函数:

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

我们来看mOwner是谁?

public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}
public ActivityManagerNative() {
    attachInterface(this, descriptor);
}

mOwner是在ActivityManagerNative创建的时候赋值的,就是一个ActivityManagerNative对象。AMS的初始化是在SystemServer进程中,所以ActivityManagerNative构造函数是在SystemServer进程中调用的。

不同进程

如果queryLocalInterface 方法返回的是 null,就会创建一个ActivityManagerProxy对象(参数是IBinder对象),该对象是服务端AMS的代理对象,可跨进程通信。

return new ActivityManagerProxy(obj);

我们来看ActivityManagerProxy的部分实现:

class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote)
    {
        mRemote = remote;
    }

    public IBinder asBinder()
    {
        return mRemote;
    }

    public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        ...
        mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
        reply.readException();
        ...
    }
}

可以看到,ActivityManagerProxy 的作用只是作为代理而已,通过它,最终会调远程(Service端)mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0)来实现具体的功能。

到了这里,我们明白,如果是远程调用,即Client和Server不在同一个进程中,queryLocalInterface必然返回null,这样就会new一个ActivityManagerProxy对象来进行远程调用。

原创文章 20 获赞 2 访问量 5100

猜你喜欢

转载自blog.csdn.net/u011578734/article/details/105955563
Q A
q