概念
全称:AIDL: Android Interface Definition Language ,它是 Android 接口定义语言。用它定义客户端与服务使用进程间通信 ( IPC ) 进行相互通信时都认可的编程接口。Android 中一个进程通常无法访问另一个进程的内存,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。
AIDL 接口的调用是直接函数调用(同步的),执行进程取决于调用来自本地进程还是远程进程中的线程:
- 来自本地进程的调用在发起调用的同一线程内执行
如果该线程是UI主线程,则该线程继续在AIDL接口中执行;如果该线程是其他线程,则其在其他中线程执行。但是这种情况根本不应该使用AIDL,而是应该通过实现本地Binder类创建接口 - 来自远程进程的调用,平台分派给当前应用自有进程内部维护的线程池
必须为来自未知线程的多次并发传入调用做好准备,即AIDL接口的实现必须是完全线程安全实现。
AIDL 接口首次发布后对其进行的任何更改都必须保持向后兼容性,以避免中断其他应用对服务的使用。因为必须将 .aidl 文件复制到其他应用,才能让这些应用访问服务的接口,因此必须保留对原始接口的支持。
注意:如下介绍都是针对跨进程
AIDL的用法。如果是在相同进程中,AIDL相当于直接调用,和本地扩展Binder用法一样,具体原因参看asInterface的返回值。
语法格式
每个 .aidl 文件都必须定义单个接口,并且需要包含接口声明和方法,不支持定义字段。
支持的数据类型
- 基本数据类型
byte, int , short, long, boolean, char, float, double, String, CharSequence等。 - 集合类型
List中的数据类型必须是基本类型或者Parcelable类型。可选择将List用作“通用”类(例如,List<String>)。另一端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口。Map支持的数据类型和List相同,但是不支持通用Map类(即不支持Map<String,Integer>这种格式)。另一端实际接收的具体类始终是HashMap,但生成的方法使用的只能是Map接口。
示例:void ListAndMapTypes(inout List<String> list, inout Map map); - 自定义
Parcelable类型
必须为这些类型加入一个import语句,即使这些类型是和接口相同的软件包中定义。
数据走向标记
在 AIDL 进程传递数据时,都有一个数据拆包和打包的过程,这是一个很耗系统内存的。AIDL 作为参数不需要指定,基本数据类型(int, Long, String...)默认且只能为 in,所有其他非基本数据类型都需要指定数据走向的方向标记。
1 | interface IMyIPCAidlInterface { |
举例客户端数据传递到服务端,走向标记解析:
- in
类似值传递,形参只拷贝了数据内容到服务器端,服务器端的修改不会影响到客户端的实参。 - out
传递空对象,但是服务端的修改会同步写入客户端实参,相当于只传递了参数类型。形参传递时服务器端并没有直接使用,而是重新 new 了一个空对象;客户端的参数是无法传递进服务端的,在服务端对空对象填充完数据后,会同步写入客户端的实参,即服务端的修改会同步到客户端。
- inout
类似引用传递,形参拷贝了数据内容到服务器端,但是服务器端的修改会重新写回客户端,所以客户端的实参跟着被修改了,也就是服务端的修改会同步影响客户端。
根据后文的源码分析,非基本数据类型不管是哪种走向标记,客户端和服务端在跨进程通信时,对象都不是直接传递的,而是在 Studb.Proxy 中通过 Parcel 重建了数据做中转,中转的过程中模拟了值传递或者引用传递的效果。示例:
注意:走向标记仅仅在跨进程时才有上面的含义。如果都是在相同进程中执行,不管如何标记都是引用传递,即相当于直接调用,并没有使用代理,具体参考
asInterface的返回值。
oneway 关键字
表示用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果。该关键字可以用来声明接口或者声明方法,如果接口声明中用到了 oneway 关键字,则该接口声明的所有方法都采用 oneway 方式 。通过这种方式,服务端在回调客户端的方法时,可以同时通知所有客户端,而不必等待客户端执行完毕。
通俗来讲oneway 修饰方法 F,在包含了 F 的这段程序会先执行完了,才执行 F 方法。如果没有 oneway 关键字定义,即正常流程,程序先等待 F 方法调用完才会继续执行。参看代码示例:
1 | Log.d(TAG, "handleMessage: MSG_UPDATE_DATA"); |
有没有 oneway 关键字定义的前后对比 Log :
1 | // oneway 关键字定义 updateData,不会阻塞原有程序 handleMessage,执行完后才调用 updateData |
oneway关键字最终是在Binder机制中解析的,具体为IPCThreadState.cpp::transact中判断是否阻塞执行,具体参考Android Binder 机制 。
自定义 Parcelable 数据在 IPC 中传递
实现 Parcelable 自定义类
在做进程间通信时,自定义的 Parcelabel 类除了完成默认的实现,还需要多增加两个方法(具体需求可以参见后面的源码分析):
- 空的构造函数
out走向时,需要重新new一个空对象,就是调用的该构造函数:
1 | public MyDataParcelable(){ |
- 从
Parcel中读取数据Parcelable数据clone时,需要通过该方法中转:
1 | public void readFromParcel(Parcel in){ |
创建同名 AIDL 文件申明自己
凡是自定义的 Parcelabel 类,在 AIDL 中使用时,必须要先定义自己,并用 parcelable 关键字做声明:
1 | // MyDataParcelable.aidl |
如果不定义,在使用过程中编译时会抛出异常:
couldn't find import for class ***.ipc.MyDataParcelable
AIDL 实现双向通信及示例
服务端创建 AIDL 文件
服务端创建 IMyIPCAidlInterface.aidl 文件,供客户端调用:
1 | // IMyIPCAidlInterface.aidl |
服务端
- 创建
IMyIPCAidlInterface.Stub实例,并实现接口函数private IMyIPCAidlInterface.Stub mBinder = new IMyIPCAidlInterface.Stub() {...} onBind返回该实例
返回IMyIPCAidlInterface.Stub的实例:
1 |
|
- 创建回调列表以及注册和取消
1 | private final RemoteCallbackList<IMyAidlInterfaceCallback> mCallbacks |
- 回调客户端方法
这里可以通过oneway关键字定义回调是否阻塞。
1 | // Broadcast to all clients the new value. |
客户端创建 AIDL 文件用于回调
客户端创建 IMyIPCAidlInterfaceCallback.aidl 文件,供服务端调用:
1 | // IMyAidlInterfaceCallback.aidl |
客户端
创建
IMyIPCAidlInterface实例private IMyIPCAidlInterface mAIDLService = null;绑定服务并初始化
必须使用IMyIPCAidlInterface.Stub.asInterface(IBinder);来初始化实例:
1 | private boolean mAIDLIsBound = false; |
创建
IMyAidlInterfaceCallback实例private final IMyAidlInterfaceCallback mCallback = new IMyAidlInterfaceCallback.Stub(){...}Callback注册和取消注册
1 | mAIDLService.registerCallback(mCallback); |
源码分析
编译器会根据 AIDL 自动生成对应的 Java 文件,根据服务端的 IMyIPCAidlInterface.aidl 文件生成的代码做源码分析。
文件路径及基本内容
app/build/generated/source/aidl/debug/***/ipc/IMyIPCAidlInterface.java
文件的类结构:
1 | /* |
其中,DESCRIPTOR 是 Binder 的唯一表标识,一般用类名表示。
asInterface 的返回值
将服务端的 Binder 对象转成客户端的所需的 AIDL 对象:
1 | public static ***.ipc.IMyIPCAidlInterface asInterface(android.os.IBinder obj) { |
从上面的源码可以看出,如果客户端和服务端:
- 在相同进程下
返回Stub对象本身。因为此时根本不需要跨进称通信。那么直接调用Stub对象的接口就可以了,返回的实现就是服务端的Stub实现,也就是客户端直接调用了服务端的代码。 - 跨进程
返回Stub.Proxy对象。该对象持有着远程的Binder引用,因为现在需要跨进程通信,所以如果调用Stub.Proxy的接口的话,那么它们都将是IPC调用,它会通过调用transact方法去与服务端通信。
in 走向标记分析
in 模式下,对象从客户端传递到服务端后,不会被服务端修改:
1 |
|
out 走向标记分析
out 模式下,对象无法从客户端传递到服务端,Stub.Proxy 在中转时新建了一个空对象传递给服务端。服务端针对该空对象填充数据后,Stub.Proxy 在中转时将空对象填充的数据,重新写回客户端对象,所以服务器端的修改会同步到客户端:
1 | case TRANSACTION_updateBookOut: { |
inout 走向标记分析
inout 走向类似引用传递,对象从客户端传递到服务端,服务端的修改同步会影响到客户端 ,综合了 in 和 out 的功能:
1 | case TRANSACTION_updateBookInOut: { |
AIDL 作为参数不需要指定走向
IMyAidlInterfaceCallback 也是一个 AIDL,它作为参数时不需要指定走向,参数传递类似对象的引用传递(IMyAidlInterfaceCallback 直接写入 Parcel,并通过 Parcel 来传递),并不涉及到对象的转存。
1 | case TRANSACTION_registerCallback: { |
示例及 Log 分析
示例代码
1 | // 客户端代码 |
Log 分析
1 | // Activity 绑定服务 |
结论
- 如果是在相同进程中,只需要使用本地扩展
Binder - 如果跨进程通信并不考虑并发,可以使用
Messenger实现通信,但是该方式只支持异步通信 - 如果跨进程通信需要并发以及同步调用,则必须使用
AIDL来通信
传输大小限制
默认跨进程通信,传输大小为 1M:The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.
链接:https://developer.android.com/reference/android/os/TransactionTooLargeException.html
参考文档
- https://developer.android.com/guide/components/aidl.html
- http://blog.csdn.net/luoyanglizi/article/details/51980630
- http://blog.csdn.net/jiwangkailai02/article/details/48098087
- http://blog.csdn.net/scnuxisan225/article/details/49970217
- https://my.oschina.net/zhoulc/blog/199199
- http://blog.csdn.net/luoyanglizi/article/details/51958091
- https://stackoverflow.com/questions/5507990/how-to-return-hashmap-in-an-aidl-file
- http://www.dre.vanderbilt.edu/~schmidt/cs282/PDFs/8-Services-and-IPC-parts-14-15-and-16.pdf