官方说明直达
何时使用AIDL
在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的primitives,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。
简单地说,在android上跨进程无法直接交互数据信息,需要经过一系列转换,让android底层来实现传输。
注:只有在跨进程且服务中处理多线程才需要用到AIDL;如果不需要跨进程只是IPC,用Binder就可以;如果不需要多线程用message就好了。
| 方式 | 条件 | |
|---|---|---|
| AIDL | 需要IPC | 跨进程,多线程 |
| Binder | 只有IPC | 跨进程,多线程 |
| Messager | 只有IPC | 单进程没有多线程 |
基本语法&简单实例
按照google官方的介绍,要使用AIDL需要三个步骤:
- 创建 .aidl 文件
- 实现接口
- 向客户端公开该接口
1.创建 .aidl 文件
新建一个项目AidlTestRemote
AndroidStudio中的目录结构
-main
|_aidl
|_java

// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** 此处新建的是一个service接口 */
interface IRemoteService {
/** 请求这个服务的进程ID,用它来搞事情.
* int getPid();
*/
/** 这里可以使用一些基本类型作为参数的抽象方法
* 并通过AIDL返回
*
* void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
*/
int add(int num1 , int num2);
}
2.服务端实现接口
在写好.aidl文件后,在 androidstudio 上 make project 项目,如下图均可以


点击后androidstudio会在如下目录自动生成与aidl同名的java文件。

This file is auto-generated. DO NOT MODIFY.折叠代码可以看到, IRemoteService.java 中实际是由一个Stub子类和之前定义的add方法组成。若要具体实现IRemoteService(此示例中为add(int num1, int num2)方法),则继承已生成的Binder接口并实现从.aidl文件继承的方法。



3.向客户端公开该接口
上述完成的服务端的建立,简单的说,就是先用aidl建一个通道,声明好客户端可以发送什么,服务端可以接收并返回什么。服务端通过service来接收客户端通过aidl发送来的数据,经过处理后,再通过aidl返回给客户端。实现了双向的通讯。
既然服务端用service来处理客户端的数据,自然客户端要绑定上服务端的service。
为方便起见此时在项目中,新建一个 Module 名为aidltestclient。
将服务端的 IRemoteService.aidl 复制过来,要注意目录结构完全一致


1.绑定到服务端的 RemoteCalculator service,调用它的 add(int num1, int num2)方法
2.调用 add(int num1, int num2) 方法需要客服端传入两个int参数,传参的操作需要通过IRemoteService.aidl实现
3.客户端完成操作后要解绑service(易忽视)
4.绑定到 RemoteCalculator service
在onCreate方法中执行bindServie()方法:
private void bindServie() {
Intent intent = new Intent();
//android新版本上需要显示发起intent,ComponentName需要传入pkg name,cls name。注意cls name需要加上包名
intent.setComponent(new ComponentName("com.wind.fitz.aidltestremote","com.wind.fitz.aidltestremote.RemoteCalculator"));
//(Intent service, ServiceConnection conn,int flags)
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
bindService的参数说明:intent不解释;ServiceConnection 用来接收the service object when it is created and be told if it dies and restarts。简单的说,ServiceConnection 用来接收绑定到的service的状态并作出响应,不可为空,看下面conn的实现就很清楚;flags则是绑定服务时的可选操作,设置BIND_AUTO_CREATE可以在绑定时自动创建,也可设0.
因为 ServiceConnection 不可为空,所以:
IRemoteService iRemoteService;
……
private ServiceConnection conn = new ServiceConnection() {
//绑定上服务
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iRemoteService = IRemoteService.Stub.asInterface(service);
}
//服务断开
@Override
public void onServiceDisconnected(ComponentName name) {
iRemoteService = null;
}
};
在之前有说到,RemoteCalculator service 被绑定上时,会返回一个 mBinder
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
那么在 onServiceConnected(ComponentName name, IBinder service) 中的 IBinder service 实质上就是 RemoteCalculator service 中的 mBinder ,那么在 RemoteCalculator service 中 private Binder mBinder = new IRemoteService.Stub(){},
所以这么一操作,conn 中的 IBinder service 就是远程服务的 service 。
此时接收一下就好了 iRemoteService = IRemoteService.Stub.asInterface(service);(为何如此接收后面分析)
至此客户端就拿到了远程服务端的 service 即 iRemoteService。
客户端想要的结果就可以这么来获得 int result = iRemoteService.add(num1,num2);
通过 IPC 传递对象
官方说明:通过 IPC 接口把某个类从一个进程发送到另一个进程是可以实现的。 不过,您必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统可通过它将对象分解成可编组到各进程的原语。
传递自定类型是可以的,但必须支持 Parcelable 接口。
- 自定义类实现 Parcelable 接口。
- 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
- 自定义类添加一个名为 CREATOR 的静态字段,这个字段是一个实现 Parcelable.Creator 接口的对象。
- 最后,创建一个声明可打包类的 .aidl 文件。
1.创建自定义类
新建一个 IMyBook 类继承 Parcelable
包含name,price,year,author四个属性。
继承 Parcelable 接口需要实现以下三个方法
//注意拆包顺序要与打包顺序一致
protected IMyBook(Parcel in) {
this.name = in.readString();
this.price = in.readInt();
this.year = in.readInt();
this.author = in.readString();
}
public static final Creator<IMyBook> CREATOR = new Creator<IMyBook>() {
//拆包
@Override
public IMyBook createFromParcel(Parcel in) {
//获取的in写入新book对象
return new IMyBook(in);//返回的新IMyBook对象到上面拆包
}
//默认
@Override
public IMyBook[] newArray(int size) {
return new IMyBook[size];
}
};
//默认
@Override
public int describeContents() {
return 0;
}
//打包
@Override
public void writeToParcel(Parcel dest, int flags) {
//将book属性打包
dest.writeString(name);
dest.writeInt(price);
dest.writeInt(year);
dest.writeString(author);
}

IMyBook 类也需要建议对应的aidl文件


2.建立服务端
新建 IRemote.aidl 注意导包,接口里写一个向list添加对象的抽象方法。

make project,生成 IMyBook.java 和 IRemote.java 后。建立 service 实现 IRemote 接口

3.客户端绑定服务
客户端将aidl文件复制到同级目录,同时 IMyBook 类也要复制到对应包下


绑定到服务


注意事项
客户端在调用远程方法时,要确保在调用前绑定到远程服务!!
如下错误写法:
在点击事件内绑定服务,会导致服务未绑定完成就开始调用 add 方法。


基本数据类
默认情况下,AIDL 支持下列数据类型:
- Java八种基本数据类型(int、char、boolean、double、float、byte、long、string) 但不支持short
- String、CharSequence
- List和Map
- Parcelable
- List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
- Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
您必须为以上未列出的每个附加类型加入一个 import 语句,即使这些类型是在与您的接口相同的软件包中定义。
AIDL原理
先放图

看下图,IRemote.java 是make project后AS自动生成的。看他的结构实际就是一个 Stub 和 add 方法。
add方法就是开发者定义在IRemote.aidl中的方法。可以看到它返回一个list,抛出RemoteException。
public java.util.List<com.wind.fitz.ipcaidl.IMyBook> add(com.wind.fitz.ipcaidl.IMyBook book) throws android.os.RemoteException;
这个 Stub 子类它继承自 Binder 并实现IRemote(自身)的接口。
public static abstract class Stub extends android.os.Binder implements com.wind.fitz.ipcaidl.IRemote

/** Construct the stub at attach it to the interface. */在连接到接口时构建存根
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
在客户端 ServiceConnection 中通过如下方式来获得远程服务:
iRemote = IRemote.Stub.asInterface(service);
那么 **.Stub.asInterface() 实际上是返回一个 Proxy
/**
* Cast an IBinder object into an com.wind.fitz.ipcaidl.IRemote interface,
* generating a proxy if needed.
*/
public static com.wind.fitz.ipcaidl.IRemote asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.wind.fitz.ipcaidl.IRemote))) {
return ((com.wind.fitz.ipcaidl.IRemote)iin);
}
return new com.wind.fitz.ipcaidl.IRemote.Stub.Proxy(obj); //a
}
那么去看 Proxy ,上面的 a 实际走到了 b ,返回一个 mRemote (Binder对象)即 IRemote 的代理给了客户端的 iRemote。
private static class Proxy implements com.wind.fitz.ipcaidl.IRemote
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote; //b
}
那么再看客户端调用服务的方法:
iRemote.add(new IMyBook("aaa",100,2018,"bb"));
已经知道客户端的 iRemote 实际上是 IRemote.java 中的 mRemote。
iRemote.add 调用的是 Proxy 重写的 add 方法
@Override public java.util.List<com.wind.fitz.ipcaidl.IMyBook> add(com.wind.fitz.ipcaidl.IMyBook book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.wind.fitz.ipcaidl.IMyBook> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); //c
_reply.readException();
_result = _reply.createTypedArrayList(com.wind.fitz.ipcaidl.IMyBook.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
在重写的 add 方法中,是一系列的序列化操作,通过 mRemote.transact 发送给底层(上述流程在Proxy内)。 之后 Stub onTransact 接收到 Proxy 发送来的数据。再联系到服务端。 不严谨的解释:
client -- [ proxy - stub ] -- server