AIDL使用及原理浅析

参考:

前言

对于AIDL, 我不是很熟悉, 因为在工作中没有用到过.但是AIDL确实是Android跨进程通信中最常见的方式, 所以学习一下是十分有必要的.

AIDL简介

AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以在 Android 设备上两个进程之间进行进程间通信 (interprocess communication, IPC) 的代码。如果在一个进程中(例如 Activity)要调用另一个进程中(例如 Service)对象的操作,就可以使用 AIDL 生成可序列化的参数,来完成进程间通信。
简言之,AIDL 能够实现进程间通信,其内部是通过 Binder 机制来实现的。

AIDL的使用

AIDL文件

首先建立一个AIDL文件IBookManager.aidl

这个文件我们先不去管, 待会儿再来修改.
定义一个Book类, 实现Parcelable接口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Book implements android.os.Parcelable {
public int bookId;
public String bookName;

public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}

public Book() {
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.bookId);
dest.writeString(this.bookName);
}

protected Book(Parcel in) {
this.bookId = in.readInt();
this.bookName = in.readString();
}

public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}

@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}

AIDL支持的数据类型:

  • 基本数据类型
  • String, CharSequence
  • Parcelable
  • ArrayList
  • HashMap

虽然可以使用 ArrayList 与 HashMap, 但是其类型也只能用被AIDL支持的数据类型.
自定义的Parcelable对象和AIDL对象必须要显式的import进来
所以最终IBookManager的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// IBookManager.aidl
package me.mundane.testaidl.aidl;

// Declare any non-default types here with import statements
// 显式的导入
import me.mundane.testaidl.aidl.Book;

interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);

void addBook(in Book book);

List<Book> getBookList();
}

另外, 如果AIDL文件中用到了自定义的Parcelable对象, 必须新建一个和它同名的AIDL文件, 并在其中声明它为Parcelable类型

1
2
3
4
5
// IBookManager.aidl
package me.mundane.testaidl.aidl;

// Declare any non-default types here with import statements
parcelable Book;

除此之外, AIDL中除了基本数据类型, 其他类型的参数必须标上方向: in表示输入型参数, out表示输出型参数, inout表示输入输出型参数
为了方便AIDL的开发, 建议把所有和AIDL相关的类和文件全部放入一个包中, 方便整个复制
看一下我的分包结构

现在我们Make Project, Android Studdio自动为我们生成Binder类

该类中有个重要的内部类Stub, 继承了Binder类, 并实现了IBookManager接口

服务端

为了实现进程间的通信, 我们需要创建一个独立进程的service, 来模拟进程间的通信, service充当服务端
“:remote”表示在当前的进程名前面附加上当前的包名, 并且是当前应用的私有进程

1
2
3
4
5
6
7
8
9
<service
android:process=":remote"
android:name=".aidl.BookManagerService"
>
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="me.mundane.testaidl.aidl.BookManagerService"/>
</intent-filter>
</service>

在service中定义一个我上面说的那个重要的内部类IBookManager.Stub的对象, 并定义具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private IBookManager.Stub mBinder = new Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {

}

@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
final int bookCount = mBookList.size();
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(BookManagerService.this,
String.format("添加了一本新书, 现在有%d本", bookCount), Toast.LENGTH_SHORT).show();
}
});
Log.d(TAG, String.format("添加了一本新书, 现在有%d本", bookCount));
Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
}

@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
};

当Activity调用bindService()方法时, 会回调Service中的onBind()方法, 在onBind()方法中将上面定义的mBinder对象返回

1
2
3
4
5
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
return mBinder;
}

这样Activity就能得到Stub实现对象, 这个对象是个Binder对象, 可以实现进程间通信.

客户端

Activity充当服务端, 首先需要绑定service

1
2
3
4
5
6
7
public void bindService(View view) {
Intent intent = new Intent();
intent.setAction("me.mundane.testaidl.aidl.BookManagerService");
intent.setPackage(getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}

注意这个mServiceConnection是我们自定义的

1
2
3
4
5
6
7
8
9
10
11
12
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIBookManager = IBookManager.Stub.asInterface(service);
Toast.makeText(MainActivity.this, "绑定成功", Toast.LENGTH_SHORT).show();
}

@Override
public void onServiceDisconnected(ComponentName name) {
mIBookManager = null;
}
};

可以看到在onServiceConnected()方法里返回了我们得到了一个IBookManager对象, 这其实就是service.onBind()方法中返回的Binder对象, 利用这个Binder对象我们既可以为所欲为了, 比如addBook()

1
2
3
4
5
6
7
8
9
public void addBook(View view) {
if (mIBookManager != null) {
try {
mIBookManager.addBook(new Book(18, "漫画书"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

效果图

而且可以看到, 确实起了两个进程, 实现的是进程间通信

两个应用之间的通信

下面来演示一下真正的两个应用之间的通信

Server

首先新建一个aidlserver的module

创建AIDL文件, 定义一个login()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// IMyAidlInterface.aidl
package me.mundane.aidlserver;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);

void login(String userName, String password);
}

Make Module, 和上面一样, Android Studio会自动生成一个IMyAidlInterface的.java文件

新建一个Service, 在onBind()的时候返回一个继承了IMyAidlInterface.Stub的binder对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class AIDLService extends Service {
private static final String TAG = "AIDLService";
public AIDLService() {
}

@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}

public interface OnLoginListener {
void login(String username, String password);
}

private OnLoginListener mOnLoginListener;


public void setOnLoginListener(OnLoginListener listener) {
mOnLoginListener = listener;
Log.d(TAG, "mOnLoginListener = " + mOnLoginListener);
}

class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {

}

@Override
public void login(String userName, String password) throws RemoteException {
Log.d(TAG, "mOnLoginListener = " + mOnLoginListener);
if (mOnLoginListener != null) {
mOnLoginListener.login(userName, password);
}
}

public AIDLService getService() {
return AIDLService.this;
}


}
}

在AndroidManifest为Service设置默认的category和action, 使得能够通过隐式启动来启动这个Service

1
2
3
4
5
6
7
8
<service
android:name=".AIDLService"
>
<intent-filter>
<action android:name="me.mundane.aidlserver"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>

再看一下我们在MainActivity中的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MainActivity extends AppCompatActivity implements OnLoginListener {

private TextView mTv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
Intent intent = new Intent(this, AIDLService.class);
bindService(intent, mAIDLConnection, Service.BIND_AUTO_CREATE);
}

private ServiceConnection mAIDLConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyBinder binder = (MyBinder) service;
AIDLService aidlService = binder.getService();
aidlService.setOnLoginListener(MainActivity.this);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

private Handler mHandler = new Handler();

@Override
public void login(final String username, final String password) {

mHandler.post(new Runnable() {
@Override
public void run() {
mTv.setText(username + ", " + password);
Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
}
});
}
}

Client

接在再建一个Client的module

把服务端 aidl 整个文件夹拷贝到客户端 src/main 目录下, 同样的, Make Project一下.
接着再完善一下MainActivity中逻辑, 首先通过bindService(), 在onServiceConnected()的时候获取一个binder对象, 在点击登录按钮的时候将用户名和密码传递过去.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class MainActivity extends AppCompatActivity {

private IMyAidlInterface mIMyAidlInterface;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
// 服务端AndroidManifest.xml文件该Service所配置的action
intent.setAction("me.mundane.aidlserver");
// Service所在的包名
intent.setPackage("me.mundane.aidlserver");
bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);
}

public void login(View view) {
if (mIMyAidlInterface != null) {
try {
mIMyAidlInterface.login("mundane", "123456");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

class ConnectCallBack implements ServiceConnection {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {
mIMyAidlInterface = null;
}
}

}

看一下效果, 可以看到在Client端传递了用户名和密码, 在Server端获取了用户名和密码, 实现了两个应用之间的通信, 很显然是真正的跨进程通信

AIDL的原理浅析

下面以第一个Book的例子浅析一下AIDL的原理
首先看服务端的这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
return mBinder;
}

private IBookManager.Stub mBinder = new Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {

}

@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
final int bookCount = mBookList.size();
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(BookManagerService.this,
String.format("添加了一本新书, 现在有%d本", bookCount), Toast.LENGTH_SHORT).show();
}
});
Log.d(TAG, String.format("添加了一本新书, 现在有%d本", bookCount));
Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
}

@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
};

我们从源码解析的角度, 看一下这个Stub的构造方法

1
2
3
4
5
6
7
8
public static abstract class Stub extends android.os.Binder implements IBookManager {
private static final String DESCRIPTOR = "me.mundane.testaidl.aidl.IBookManager";

/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
...

可以看到这个内部类Stub是一个抽象类, 跟踪this.attachInterface(this, DESCRIPTOR);

1
2
3
4
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}

可以看到只是将它自己本身和DESCRIPTOR这个字符串标识作为Binder中的全局变量保存了起来.DESRIPTOR作为Binder 的唯一标识,一般用当前 Binder 的全类名表示。

然后我们看客户端的这段代码

1
2
3
4
5
6
7
8
9
10
11
12
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIBookManager = IBookManager.Stub.asInterface(service);
Toast.makeText(MainActivity.this, "绑定成功", Toast.LENGTH_SHORT).show();
}

@Override
public void onServiceDisconnected(ComponentName name) {
mIBookManager = null;
}
};

跟踪IBookManager.Stub.asInterface(service);这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Cast an IBinder object into an me.mundane.testaidl.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBookManager))) {
return ((IBookManager) iin);
}
return new Proxy(obj);
}

注释是说将IBinder接口对象强转为IBookManager对象, 如果需要的话, 生成一个代理.
首先会根据标识符去 IBinder 的本地去查找是否有该对象,也就是调用 obj.queryLocalInterface(DESCRIPTOR) 方法,继续源码中 Binder.java

1
2
3
4
5
6
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}

意思就是如果本地存在这个标识符的 IInterface 对象,那就直接返回之前构造方法中初始化的 mOwner 对象,否则返回 null.因为我们这里涉及到了跨进程通信,虽然服务端在初始化mBinder进行了attachInterface(this, DESCRIPTOR)(可以再看看上面的源码), 但那时另一个进程的, 所以这里会直接返回 null。接着就return一个 new Proxy(obj);, 我们继续跟踪这个Proxy类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    private static class Proxy implements IBookManager {
private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
mRemote = remote;
}

@Override
public android.os.IBinder asBinder() {
return mRemote;
}

public String getInterfaceDescriptor() {
return DESCRIPTOR;
}

/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean) ? (1) : (0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}

@Override
public void addBook(Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}

@Override
public java.util.List<Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}

static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}

在客户端中, 我们调用的是IBookManager的addBook()方法, 也就是这个Proxy对象的addBook()方法, 观察这个addBook()方法.
这里就涉及到了一个重要的类 Parcel,Parcel 天生具备跨进程传输数据能力.
把需要传递的数据写入 Parcel 中,然后到达目标进程后,将 Parcel 中的数据读出即可,所以可以将 Parcel 称为数据传输载体。
这里的_data就是一个Parcel对象, 我们将Book写入Parcel, 然后调用mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);

这个mRemote其实就是之前传进来的也就是Service返回的一个IBookManager.Stub对象, 我们还是再跟踪一下这个transact()方法吧, 在Binder类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);

if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}

可以看到onTransact()方法会被调用, 这里其实是这样
client 端:BpBinder.transact() 来发送事务请求;
server 端:BBinder.onTransact() 会接收到相应事务。
所以服务端的onTransact()方法会被调用, 其实就是IBookManager.Stub.onTransact()会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_basicTypes: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0 != data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
Book _arg0;
if ((0 != data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

case TRANSACTION_addBook的时候, 调用addBook()方法, 这样自然就完成了服务端中收到响应之后的操作.返回true表示这个事务是成功的.
当 onTransact 返回 true,调用成功,从 reply 对象中取出返回值,返回给客户端调用方。

总结

高度概括 AIDL 的用法,就是服务端里有一个 Service,给与之绑定 (bindService) 的特定客户端进程提供 Binder 对象。客户端通过 AIDL 接口的静态方法asInterface 将 Binder 对象转化成 AIDL 接口的代理对象,通过这个代理对象就可以发起远程调用请求了。
用这张图来表述再清楚不过了.

值得一提的是, 在这里ADDL是系统自动生成的, 实际上我们手写AIDL也是完全可以的, 只要完全理解Binder的原理, 只不过使用系统生成更加方便快速而已.
下面总结一下几种常见的IPC方式

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件的进程间通信
文件共享 简单易用 不适合高并发场景, 并且无法做到进程间的即时通信 无并发访问情形, 交换简单的数据, 实时性不高的场景
AIDL 功能强大, 支持一对多并发通信, 支持实时通信 使用稍复杂, 需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般, 支持一对多串行通信, 支持实时通信 不能很好处理高并发情形, 不支持RPC, 数据通过Message进行传输, 因此只能传输Bundle支持的数据类型 低并发的一对多即时通信, 无RPC需求, 或者无需要返回结果的RPC需求
ContentProvider 在数据源访问方便功能强大, 支持一对多并发数据共享, 可通过Call方法扩展其他操作 可以理解为受约束的AIDL, 主要提供数据源的CURD操作 一对多的进程间的数据共享
Socket 功能强大, 可以通过网络传输字节流, 支持一对多并发实时通信 实现细节有点繁琐, 不支持直接的RPC 网络数据交换

关于Binder, 如果想了解更多一些, 我推荐weishu的这一篇Binder 学习指南

demo地址

https://github.com/mundane799699/AndroidProjects/tree/master/AIDLdemo

0%