AIDL
2016-04-16 16:55
591 查看
基础
为服务端定义的与客户端进行通信的接口,客户端按照服务端定义的AIDL接口文档进行传参数,即可获取服务端操作后的返回结果,从而实现客户端与服务端通信的功能。服务端:定义AIDL文档的一方;客户端:调用AIDL文档的一方,请求的一方。
步骤
服务端
1,新建aidl文件夹,它与java目录同级(可直接右键->NEW->FOLDER->AIDL folder)。并在该文件中建立aidl文件(后缀名为aidl的文件),也可直接右键->New->AIDL->AIDLFile。
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对象,才能保证客户端拿到的数据是正确的。
相关文章推荐
- Ibatis中的动态SQL:isNotNull,isPropertyAvailable,isNotEmpty用法
- main函数中两个参数的用法
- POJ1273 Drainage Ditches
- ACM--单调栈--Bad Hair Day--POJ--3250--水
- Again Stone Game (通过SG函数找规律)
- 《LeetBook》leetcode题解(11):Container With Most Water[M] ——用两个指针在数组内移动
- 食物链(带权并查集)(OpenJ_Bailian 1182)
- 11. Container With Most Water
- vim 插件之vim-trailing-whitespace
- 人工智能\机器学习\统计学\数据挖掘之间有什么区别?
- 信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logge
- HDU 1021 Fibonacci Again(斐波那契数列+mod规律)
- tools:context=".MainActivity"
- Code Forces 22B Bargaining Table
- Code Forces 22B Bargaining Table
- USACO Training Section 3.2 & 洛谷P2730
- Educational Codeforces Round 8 F. Bear and Fair Set【最大流】
- [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:31:compile (default-co
- 1014. Waiting in Line
- leetcode 70. Climbing Stairs