一、概述
AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板。
在Android中,默认每个应用(application)执行在它自己的进程中,无法直接调用到其他应用的资源,这也符合“沙箱”(SandBox)的理念。所谓沙箱原理,一般来说用在移动电话业务中,简单地说旨在部分地或全部地隔离应用程序。
Android沙箱技术:
Android“沙箱”的本质是为了实现不同应用程序和进程之间的互相隔离,即在默认情况 下,应用程序没有权限访问系统资源或其它应用程序的资源。
每个APP和系统进程都被分配唯一并且固定的User Id(用户身份标识),这个uid与内核层进程的uid对应。
每个APP在各自独立的Dalvik虚拟机中运行,拥有独立的地址空间和资源。
运行于Dalvik虚拟机中的进程必须依托内核层Linux进程而存在,因此Android使用Dalvik虚拟机和Linux的文件访问控制来实现沙箱机制,任何应用程序如果想要访问系统资源或者其它应用程序的资源必须在自己的manifest文件中进行声明权限或者共享uid。
因此,在Android中,当一个应用被执行时,有一些操作是被限制的,比如访问内存,访问传感器,等等。这样做可以最大化地保护系统,免得应用程序“为所欲为”。
设计AIDL这门语言的目的就是为了实现进程间通信。在Android系统中,每个进程都运行在一块独立的内存中,在其中完成自己的各项活动,与其他进程都分隔开来。可是有时候我们又有应用间进行互动的需求,比较传递数据或者任务委托等,AIDL就是为了满足这种需求而诞生的。通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求
但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用——这种时候就需要使用 AIDL 了。
通常,暴露方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互
二、原理
服务端:因为要实现Binder,必须在服务器端创建一个Binder对象,如何创建呢?就是newAIDL接口中的Stub内部类,代码示例如:
Binder mBinder=new IBookManager.Stub(){接口方法实现}
其中IBookManager
是系统根据我们自己定义的IBookManager.AIDL
所生成的类。
Binder驱动:在AIDL
中,Binder驱动其实就是Service。
客户端:要实现客户端跨进程和服务端通信,必须获得服务端的Binder
对象在binder驱动层对应的mRemote引用,如何获得呢?首先绑定远程服务,绑定成功后的ServiceConnection
中的IBinder service
其实就是mRemote引用
,但是因为是使用AIDL
方式,所以需要在客户端中调用IBookManager.Stub.asInterface(android.os.IBinder obj)
方法将服务器返回的Binder对象转换成AIDL
接口,然后就可以通过这个接口去调用服务器的远程方法了。
根据原理,我们得出AIDL
的使用流程,其实很简单,大致就是在服务端创建一个Service
,然后创建一个Binder
对象,最后在客户端得到这个Binder
对象。
AIDL
使用流程:
先建立AIDL
,如果在你建立的AIDL
接口中,有自定义的类,那么,也需要建立这个类的AIDL,并且名字要完全相同。同时在使用的时候,一定要显示的导入这个类。接下来的流程就是跟Binder的一样了。
服务器端:创建Binder
对象,并且实现接口中的方法。
客户端:绑定service,得到Binder
对象在驱动层对应的mRemote引用。
四、重点
- AIDL的本质其实就是系统为我们提供了一种快速实现Binder的工具,我们完全可以不用AIDL,自己去写代码实现Binder,但是当你写出来的时候会发现其实和AIDL自动生成的代码一模一样。我们接下来来分析一下原理,因为AIDL的实现其实就是快速实现Binder,所以原理自然离不开Binder。
- 文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。
- 服务端和客户端的路径、文件名、内容一样,这样才能保证协议一致。
- 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下——在 Java 中,这种情况是不需要导包的。比如,现在我们编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.aoaoyi.aidldemo 包下 ,现在我们需要在 .aidl 文件里使用 Book 对象,那么我们就必须在 .aidl 文件里面写上 import com.aoaoyi.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一个包下。
默认支持的数据类型包括:- Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
- String 类型。
- CharSequence类型。
- List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。
- Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。
- 定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。还有,请注意,请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的 - 两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。 - RemoteCallbackList:可以实现一对多回调
需要注意的是这个类的beginBroadcast()和finishBroadcast()一定要配对使用,否则会出现异常java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast,特别是在使用for循环的时候。 - 当使用客户端调用服务器的方法的时候,被调用的方法运行在服务器的Binder线程池中,同时客户端会被挂起,如果此时服务端方法执行耗时的话,就会导致客户端线程长时间阻塞,如果客户端线程是UI线程的话,就会导致客户端ANR,注意的是onServiceConnected(ComponentName name, IBinder service)和onServiceDisconnected(ComponentName name)都运行在UI线程,所以不能在这里调用服务端耗时的方法。同理,对于服务端调用客户端的方法的情况,比如服务端调用客户端的listener中的方法的时候也是一样。即服务端挂起,方法运行在客户端的Binder线程池中。
- 重新绑定service方式:当服务端因为某种异常原因停止,我们需要重新启动服务端,这里有两种方式,因为AIDL的底层是Binder,所以可以使用Binder的linkToDeath和unlinkToDeath方法。还有一种方式是在onServiceDisconnected(ComponentName name)重新绑定。这两个区别就是第二种方式可以访问UI,第一种不行,因为像之前说的,onServiceDisconnected(ComponentName name)是运行在UI线程里的。而第一种方式使用的时候需要设置一个IBinder.DeathRecipient接口用于接收服务端binder因为特殊原因消失的通知,当收到通知的时候就会回调binderDied()方法,我们在这里unlinkToDeath并且重新绑定service。而这个binderDied()方法是运行在客户端的Binder线程池中的。
- asInterface(android.os.IBinder obj)
用于将服务器的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么返回服务器的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。 - onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)
这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由服务端 的onTransact方法来处理。这个方法有四个参数,分别是code ,data,reply,flags.code是确定客户端请求的方法是哪个,data是目标方法所需的参数,reply是服务器端执行完后的返回值。如果这个方法返回false,那么客户端的请求会失败。 - Proxy#getBookList
这里的getBookList方法就是在自定义的AIDL文件中定义的方法,这个方法运行在客户端,当客户端远程调用此方法的时候,内部实现是这样的:首先在代理类中创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中,接着mRemote调用transact方法来发起RPC(远程过程调用)请求, 同时当前线程挂起,然后服务端的onTransact方法会被调用,直到RPC返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果并返回(如果有返回值的话),之前创建的参数其实就是onTransact()方法需要的参数
文章评论