AIDL初识

在《Android源码设计模式》一书中看到这么一段话:Android的Binder机制是一个庞大的体系模块,平时能接触到的最难懂的莫过与View的运行机制,但Binder比它还要复杂百倍,而且View的底层通信也依赖于Binder。对于开发者来说,这么底层的东西没必要太过于接触,只要学会如何使用它即可。

之前也是一直想搞清楚Binder是怎么实现的,但一直停留在整体架构和流程上的粗浅了解,很难深入下去。当看了这段话我也就不那么执着了,毕竟自己功力也确实还达不到,先学会怎么用,在逐渐加深了解更适合我,那“用”的是什么呢?

下面我们会简单介绍,Binder主要包括四大模块,对于底层的Binder驱动和ServiceManager来说我们没有必要自己去实现,这一部分逻辑最复杂,而且这部分逻辑Android已经为我们封装的很完美了,我们只需要自己实现Binder Client和Binder Server部分就可以了。

其中对于开发者来说,Binder Server必须由自己来实现,但编写这么一个Binder Server的难度也很大,Android就充分考虑到这点,提供了一个简单的方式来生成Android Server端,也就是AIDL(Android Interface Description Language)。Android提供了AIDL工具,可以将AIDL文件编译成Java文件。

Binder简介

核心概念

BInder主要涉及四大模块,这四者之间的关系类似于网络访问。

Binder通信架构

  • Binder驱动:路由器
  • ServiceManager:DNS
  • Binder Client:客户端
  • Binder Server:服务器

通信步骤

1、ServiceManager建立,负责管理所有的进程号

2、各个Server在启动后,在ServiceManager中进行注册

3、Client要与某个Server通信,向ServiceManager进行查询,ServiceManager会返回给Client该Server的相关信息

那Binder具体是如何跨进程通信的呢?先上张图:

假设我们的Client想要调用Server端的,返回值类型为Object的add()方法,这其实就是一个跨进程通信,我们来看下过程是怎么样的:

在Server启动之初,Server会在SM(ServiceManager)中进行注册,并告诉它我这里都有哪些方法,于是SM会将这些方法存放在建立好的一张表中。

接着Client端会向SM发来请求,请求调用Server端的Object类型的add()方法。我们知道,进程间进行通信,它们的数据都是在内核空间里,所以SM并不会返回给Client一个真正的Server端的Object对象,因为这是无法操作的。

实际上返回的是一个代理对象,而这个代理对象里面包含add()方法,但这个add()方法是个空方法,这个代理方法唯一的作用就是当Client端调用add()方法的时候,它会返回给Binder驱动,Binder驱动收到这个代理方法就会明白,它真正要访问的是Server端的add()方法,于是Binder驱动就会通知Server端,调用它的add()方法,将结果返回给SM,SM再返回给Client端。

AIDL

介绍

AIDL是“Android Interface Definition Language”的缩写,翻译过来就是“Android接口定义语言”。从它的名字中我们可以提取这么两个关键词:接口定义语言

关于”定义“

所有的AIDL文件大致可以分为两类:一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,而且定义的都是接口,这就是为什么它叫做“Android接口定义语言”。

关于”语言“

既然是一语言,主要需要学习的就是它的”语法“,AIDL语法包括七大部分:

  • import:用于导入一个类,其与Java语言的不同之处在于,即便导入的类与当前AIDL文件在同一目录,也需要显示导入。
  • package:用于声明该AIDL生成的Java类文件所在的包名。
  • interface:用于声明一个AIDL的服务接口。
  • oneway:用于修饰interface和方法,表示CLient不需要等待Server端服务方法返回,oneway interface表示该interface中所有方法都是oneway,通常oneway方法的返回类型都为void。
  • 数据类型:AIDL列出的支持的类型可以作为参数或返回值使用。其支持的数据类型主要有:
    • Java基础数据类型及其数组:int、long、char、boolean、byte
    • String和CharSequence及其数组
    • android.os.Ibinde
    • List和Map
  • 方向标记:用于标记参数的输入/输出类型,方向标记有in、out、inout三种:
    • in表示参数由Client端设置并传递到Server端服务方法,服务端方法需要根据该参数做出处理。
    • out表示参数由Client端传递到Server端服务方法,服务方法将设置该参数并将其返回Client端。
    • inout表示Client端和Server端服务方法操作同一个参数。通常情况下,由Client提供一个参数值,server端服务方法修改该值并返回。
  • parcelable:用于声明一个在AIDL中使用的自定义类。

开发步骤

创建“.aidl”文件

鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File,创建后Android Studio会自动把这个.aidl文件放到一个aidl的目录下,文件初始内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// IMyAidl.aidl
package com.cn.im;
// Declare any non-default types here with import statements
interface IMyAidl {
/**
* 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);
}

basicTypes方法中给我们展示了AIDL支持的基本数据类型。

向新建的aidl文件书写代码

1
2
3
4
5
6
7
8
9
10
11
12
// IMyAidl.aidl
package com.cn.im;
// Declare any non-default types here with import statements
interface IMyAidl {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
int add(in int value1, in int value2);
}

代码写完之后,clean或rebuild一下工程会自动生成对应的.java文件,该Java文件的目录如下:

代码如下:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.cn.im;
// Declare any non-default types here with import statements
public interface IMyAidl extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.cn.im.IMyAidl
{
private static final java.lang.String DESCRIPTOR = "com.cn.im.IMyAidl";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.cn.im.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.cn.im.IMyAidl asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.cn.im.IMyAidl))) {
return ((com.cn.im.IMyAidl)iin);
}
return new com.cn.im.IMyAidl.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@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_add:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.cn.im.IMyAidl
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int add(int value1, int value2) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(value1);
_data.writeInt(value2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int value1, int value2) throws android.os.RemoteException;
}

主要内容包括:

移植相关文件

使用AIDL进行客户端和服务端的通信有一个条件需要满足,那就是服务器端的各个AIDL文件必须要被拷贝到客户端的相同包名下,不然会不成功,所以,需要将 aidl 包复制到另一端的 main 目录。

这一步还有别的坑需要注意,不过因为我们这里的客户端和服务端都写在同一个项目中,所以这一步不需要做,其中的一些问题先暂且不去考虑,等在实践中遇到了再去解决。

编写服务端代码

要编写的服务端代码,就是实现AIDL中定义的方法接口的具体逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyAidlService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myAidl;
}
//这个方法进行一些初始化的操作
@Override
public void onCreate() {
super.onCreate();
}
private final IMyAidl.Stub myAidl = new IMyAidl.Stub(){
@Override
public int add(int value1, int value2) throws RemoteException {
return value1 + value2;
}
};
}

整体的代码结构很清晰,大致可以分为三块:

第一块是初始化:在 onCreate() 方法里面我进行了一些数据的初始化操作。

第二块是重写 IMyAidl.Stub 中的方法:在这里面提供AIDL里面定义的方法接口的具体实现逻辑。

第三块是重写 onBind() 方法:在里面返回写好的 IMyAidl.Stub实例 。

在清单文件中进行注册

1
2
3
4
5
6
7
8
<service
android:name=".MyAidlService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.cn.test"/>
</intent-filter>
</service>

到这里我们的服务端代码就编写完毕了。

编写客户端代码

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
public class MainActivity extends AppCompatActivity {
private IMyAidl myAidlService;
private MyServiceConncetion connection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initService();
try {
int result = myAidlService.add(1,2);
Log.e("Print:","result="+result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onStop() {
super.onStop();
releaseService();
}
class MyServiceConncetion implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlService = IMyAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAidlService = null;
}
}
/*
* 这个方法使Activity(客户端)连接到服务(service)
*/
private void initService() {
connection = new MyServiceConncetion();
Intent i = new Intent();
i.setAction("com.cn.test");
i.setPackage(this.getPackageName());
bindService(i, connection, Context.BIND_AUTO_CREATE);
}
/*
* 这个方法使Activity(客户端)从服务(service)断开
*/
private void releaseService() {
unbindService(connection);
connection = null;
}
}

代码逻辑同样也很清晰,首先建立连接,然后在 ServiceConnection 里面获取 IMyAidl接口对象,接着通过它来调用服务端的方法。

此时,我们的代码就全部编写完毕了,但运行却报错:Attempt to invoke interface method 'int com.cn.test.IMyAidl.add(int, int)' on a null object reference,反复看代码逻辑并没有问题,在网上提出这个问题的人也很少,所以这个问题暂时还没有解决,只能靠自己深入学习,找出问题了。如果大家有遇到过这个问题的,欢迎在我的简书下留言,不胜感激。