AIDL

来源:互联网 发布:剑三花太捏脸数据 编辑:程序博客网 时间:2024/06/05 11:05

基础

        为服务端定义的与客户端进行通信的接口,客户端按照服务端定义的AIDL接口文档进行传参数,即可获取服务端操作后的返回结果,从而实现客户端与服务端通信的功能。

        服务端:定义AIDL文档的一方;客户端:调用AIDL文档的一方,请求的一方。

步骤

服务端

        1,新建aidl文件夹,它与java目录同级(可直接右键->NEW->FOLDER->AIDL folder)。并在该文件中建立aidl文件(后缀名为aidl的文件),也可直接右键->New->AIDL->AIDL File。

        2,AIDL语法与java类似。新建完aidl文件后重新build一个module,就会在/build/generated/source/aidl/debug下生成相应的java文件。Java文件中有一内部类Stub。

        3,服务端定义一个Service,并定义一个第2步生成的Stub的对象。并将该变量当作onBind()的返回值。如下:

    public IBinder onBind(Intent intent) {        return iBinder;    }    private IBinder iBinder = new IMyAidlInterface.Stub() {        @Override        public int sum(int a, int b) throws RemoteException {            Log.e("TAG", "远方的朋友:a= " + a + ",b = " + b);            return a + b;        }    };

其中IMyAidlInterface是aidl的文件名。

客户端

        1,拿到服务端的aidl文件,并放到相同的目录下。build自己的module,生成相应的java文件。
        2,客户端使用bindService,绑定服务端在第3步中定义的Service。如:
        Intent intent = new Intent().setComponent(new ComponentName("com.example.myapp", "com.example.myapp.service.RemoteService"));        bindService(intent,conn, Context.BIND_AUTO_CREATE);

        3,初始化一个ServiceConnection对象,并将其当作bindService的第二个参数。如下:

    private ServiceConnection conn = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {            Log.e(TAG,componentName.getPackageName()+","+componentName.getClassName());            bridge = IMyAidlInterface.Stub.asInterface(iBinder);//bridge为IMyAidlInterface        }        @Override        public void onServiceDisconnected(ComponentName componentName) {            bridge = null;        }    };

        在初始化ServiceConnection对象时,必须实现onServiceConnected(),并在该方法中调用YourAidlInterface.Stub.asInterface(service),该方法返回一个服务端定义的AIDL接口的对象。使用该对象就可以调用接口中定义的方法。

        并且要在onServiceDisconnected()中将得到的对象置为null。

        4,拿到第3步中得到的AIDL接口实例,调用其中方法。如下:

 try {            int sum = bridge.sum(20, 30);            Log.e(TAG,"sum = "+sum);        } catch (RemoteException e) {            e.printStackTrace();        }

         经过上述步骤就可以完成基本数据类型的通信。

传递对象

服务端

        通过AIDL传递的对象,必须实现Parcelable接口。

        1,定义一个实现Parcelable接口的bean,假设类名为Person。

        2,新建一个与上述bean同名的aidl文件,以第一步为例,新建的aidl文件为Person.aidl。并且Person.aidl所处的包名必须与Person.java相同,否则第三步时会出错。Person.aidl的内容很简单:

package com.example.myapp.bean;parcelable Person;

        首先定义包名,其次使用parcelable关键字定义类名。

        3,定义AIDL文件,并使用import导入相应的类。如:

package com.example.myapp;import com.example.myapp.bean.Person;interface IMyAidlInterface { int sum(int a, long b); Person getPerson();}

        这里导入的Person是aidl目录中的——aidl无法使用java定义的bean。在Service中,新建IBinder对象时,方法的返回值是import导入的Person,如果说Person.aidl与Person.java的全名不同,那么在Service中就会报错。

客户端

        将服务端给的aidl文件放与相同的包中,并且需要将服务端给的Person.java文件放入到相同的包中。使用如下:
           int sum = bridge.sum(20, 30);            Person person = bridge.getPerson();
其中Person的包名为:com.example.myapp.bean,它与在服务端定义的Person.java的包名是相同的。

总结

        与基本数据类型的不同地方在于:
        1,服务端需新加一个与要交换的类同名的aidl文件,并且该aidl文件与java文件包名相同。
        2,在定义aidl的接口时,需通过import导入上步中的bean.aidl。
        3,客户端需拿到服务端给的aidl文件和做为传输的bean.java。并将bean.java的包名设置的与服务端相同。

注意

        1,aidl的方法可以支持除short外的所有基本数据类型,即方法的参数可以为基本数据类型中除short外的所有类型。之所以不支持short类型,是因为Parcel没有办法对short进行序列化,也就没办法通过aidl将short类型在客户端与服务端进行传递。
        2,可支持String,CharSequence以及Parcelable。也可支持List,Map类型,但List,Map中所存储的元素必须是上述所支持的类型。并且客户端和服务端获取的List,Map对象只能是ArrayList和HashMap。如下:

        从中可以看出,传递的List对象先通过Parcel#writeStringList写入到_data中,服务端返回的List对象通过Parcel#createStringArrayList()转换成结果,而在该方法返回的就是一个ArrayList。所以,客户端拿到的List对象只能是ArrayList。
        服务端同客户端一样,只不过先通过Parcel#createStringArrayList()拿到数据,再通过Parcel#writeStringList()写出数据。
        要注意:这里并不是说客户端和服务端写出的List和Map只能是ArrayList和HashMap,它们可以是任何类型的List和Map,只是在读取的时候只能获取到ArrayList和HashMap
        3,除了基本数据类型,String,CharSequence,ArrayList以及HashMap外,其余都需要在AIDL中import进来,那怕它import的文件与AIDL处在同一个包中。
        4,AIDL只支持方法,不支持定义的静态变量。
        5,同一个对象多次传递到对方时,对方获取到的依旧是多个不同的对象。如,一个Person对象,先调用add(Person)传递到服务端,再调用remove(Person)传递到服务端,服务端将拿到是两个不同的Person对象。这很正常,因为服务端拿到的数据是通过Person.CREATOR.createFromParcel()进行创建的,每一次都是新new一个Person。这就导致了add能成功,但remove不成功(因为服务端拿到的person已经不是add的那个person了)。
        6,虽然接收的是不同对象,但这些对象有一个共同点:它们的底层使用的是同一个Binder对象。但一般的Parcelable对象无法拿到Binder,所以一般得通过其中的其中一个字段来标识是否相同。
        7,一方调用另一方方法,则另一方的方法运行在线程中,而本方的方法却需要自己指定,否则就运行在UI线程
        8,服务端可能会被杀死,杀死时会回调ServiceConnection#onServiceDisconnected()——该方法运行在UI线程中。也可以通过IBinder#linkToDeath()注册服务端死亡时的回调,该回调运行在子线程中。

过程分析

总览

        假设AIDL的文件名为IMyAidlInterface。在build完项目后,会生成一个同名的IMyAidlInterface.java文件。该文件是一个接口类。该接口中,除了自己在AIDL文件中定义的方法外,还有一个抽象的内部类Stub。如下:

        注:上面代码块中的getPerson()方法应该含有一个Person对象作为参数。在保存这幅截图和下面用的aidl定义有所改变
        就这是在服务端和客户端使用的IMyAidlInterface.Stub()的来源。
        而Stub内部,除了asInterface(),onTransact()等方法以及一些常量外,还有一个私有的内部类Proxy。如下:

        首先看TRANSACTION_**代表的常量,从**的内容可以发现,**就是我们定义在AIDL文件中的方法名,而TRANSACTION_**就是一个IBinder类中的常量(IBinder.FIRST_CALL_TRANSACTION = 1)加上另一个int值,可以把TRANSACTION_**看作**方法的id。它的作用也主要是用来区别在onTransact()中该调用哪个方法的。

asInterface()

        在客户端,使用最多的就是asInterface()方法,并且参数为服务端返回的一个IBinder对象。如下:

        其中obj为ServiceConnection#onServiceConnected()中的第二个参数。
        如果进程间通信,在ServiceConnection#onServiceConnected()中输出asInterface()的返回值可以发现,其类型为IMyAidlInterface$Stub$Proxy。此时调用AIDL接口中的方法,就是调用Proxy中对应的方法。而且客户端ServiceConnection#onServiceConnected()中的IBinder对象与服务端Service#onBind()中返回的并不是同一个,这很正常,客户端与服务端是两个进程,不可能使用的是同一个对象。
        如果客户端与服务端处于同一进程,那么onServiceConnected()与Service#onBind()中的IBinder对象是同一个。

Proxy

        首先会将它的成员变量mRemote赋值成ServiceConnection#onServiceConnected()中的第二个参数。这步在Proxy的构造方法中进行的,代码略。主要分析其中的两个自定义方法,并以getPerson(Person)方法为例进行说明,代码如下:

        1,调用bean的writeToParcel()方法,将客户端bean中的属性值写入到_data中。如果是基本数据类型,就调用Parcel#writeInt()等方法,将参数写入到_data中(参见sum()方法)。如果参数为List,则调用Parcel#writeTypedList()。
        2,使用mRemote调用transact()方法,并将要调用的方法的id传进去,同时将带有客户端请求参数的_data也传到服务端。此时通过调用transact()向服务端发起请求
        3,调用transact()方法拿到结果(结果存储在transact()中的_reply参数中),再调用Person#CREATOR中的createFromParcel()方法,将服务端返回的结果还原成一个Person对象,然后将该对象返回。这就实现了对象从服务端到客户端的传递。
        3.1,如果服务端返回的是一个List集合,则会调用Parcel#createTypedArrayList()进行还原,而该方法中返回的List对象为ArrayList类型的。因此AIDL中,拿到的List集合为ArrayList。同理,Map集合为HashMap。
        第一步和第三步也说明了为什么通过AIDL传递的对象需要实现Parcelable接口,以及其中writeToParcel()与createFromParcel()的作用:前者将对象拆分,后者将数据还原成相应的对象
        到目前为止,所以的代码执行都是在客户端进行的,唯一与服务端交互的地方就是mRemote.transact()。

onTransact()

        客户端调用了mRemote.transact(),经过系统的一系列操作,最终会回调到服务端的onTransact(),并将通过transact传递的参数带到onTransact()方法中。其代码如下:

        在最后一个case中,首先通过data.readInt()获取一个值,在Proxy#getPerson(Person)中首先也写入了一个1,所以这里的判断是成立的。
        另外,服务端在往回写数据时,也首先写入了一个1(代码reply.writeInt(1)),而Proxy#getPerson(Person)在mRemote.transact()后读取数据时,也首先读取了一个int值。两者是相呼应的。这里之所以额外写一个int值,主要是为了标识读取和写入是否成功。
        在服务端获取返回数据时,使用的是this.getPerson()。这里的this指的就是Service#onBind()中的返回值。
        上述即整个AIDL的工作流程。通过上面的流程可以发现,AIDL的底层依旧使用的是IBinder进行(跨进程的主要操作就是IBinder#transact()),其余的都是一些操作前的准备工作或处理返回结果,而这些步骤完全可以自己实现,不需要依赖于开发工具生成的代码。因此,AIDL是为了让开发工具生成一套使用Binder的代码的工具而已。

观察者模式

        上述所有代码都是客户端主动调用服务端的方法,从而获取自己需要的数据。如果服务端想要主动的通知客户端,就需要用到观察者模式。客户端向服务端注册一个对象,服务端在需要的时候调用该对象中的某个方法。
        一般的情况下,客户端能注册,也一样可以解绑。解绑就是服务端从一个集合中将客户端注册的对象给删除。由上面可以知道,同一个对象两次调用时服务端拿到的并不相同,但底层的Binder对象却是相同的。因此,可以建立一个Map,key为Binder,value为客户端注册的对象。同时,系统提供了一个RemoteCallbackList工具类,使用该类就可以省去自己建立Map。如下:
 private RemoteCallbackList<IRemoteObserver> mList = new RemoteCallbackList<>();    private IBinder iBinder = new IMyAidlInterface.Stub() {        @Override        public List<Person> add(Person p) throws RemoteException {            int i = mList.beginBroadcast();//遍历所有的观察者,并挨个通知            for (int x = 0; x < i; x++) {                IRemoteObserver o = mList.getBroadcastItem(x);                o.notityWhenChanged();//为IRemoteObserver中定义的接口方法            }            mList.finishBroadcast();            return ps;        }        @Override        public void unregister(IRemoteObserver ob) throws RemoteException {            mList.unregister(ob);        }                @Override        public void register(IRemoteObserver ob) throws RemoteException {            mList.register(ob);        }    }
        对RemoteCallbackList类来说,以beginBroadcast()开始,以finishBroadcast()结束。

遗留问题

        1,mRemote.transact()的工作过程,即为什么IBinder#transact()可以将一些参数从一个进程带到另一个进程。
        2,mRemote.transact()是如何找到Service#onBind()返回的IBinder对象的。只有找到正确的IBinder对象,才能保证客户端拿到的数据是正确的。
0 0