概念
全称: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