Android进阶之AIDL的使用详解(一)

AIDL(Android 接口定义语言),可以使用它定义客户端与服务端进程间通信(IPC)的编程接口,在 Android 中,进程之间无法共享内存(用户空间),不同进程之间的通信一般使用 AIDL 来处理。

主要流程就是在 .aidl 文件中定义 AIDL 接口,并将其添加到应用工程的 src 目录下,创建完成之后 rebuild,Android SDK 工具会自动生成基于该 .aidl 文件的 IBinder 接口,具体的业务对象实现这个接口,这个具体的业务对象也是 IBinder 对象,当绑定服务的时候会根据实际情况返回具体的通信对象(本地还是代理),最后
将客户端绑定到该服务上,之后就可以调用 IBinder 中的方法来进行进程间通信(IPC),下面将从以下几个方面学习 AIDL 的使用:

  1. 创建.aildl 文件

  2. 具体的业务对象实现基于 .aidl 文件生成的接口

  3. 向客户端公开接口

  4. 客户端远程调用

  5. 验证 AIDL

1. 创建.aildl 文件

在 AIDL 中可以通过可带参数以及返回值的一个或多个方法来声明接口,参数和返回值可以是任意类型,AIDL 中支持的数据类型如下:

  1. java 的 8 种数据类型:byte、short、int、long、float、double、boolean、char

  2. 除此之外支持 String、charSequence、List、Map

  3. 自定义数据类型

如果业务方法中参数或返回值类型为 List 或 Map 时:

List 中的所有元素都必须是 AIDL 支持的数据类型、其他 AIDL 生成的接口或自己声明的可打包类型。可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。

Map 中的所有元素都必须是 AIDL 支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。

当然,AIDL 也支持自定义数据类型,会在下文中介绍。

首先,在工程的 src 目录下创建 .aidl 文件,具体如下图所示:

然后,在 .aidl 文件中添加具体的业务方法,文件内容如下:

1// IPersonAidlInterface.aidl
2package com.manu.aidldemo;
3// Declare any non-default types here with import statements
4interface IPersonAidlInterface {
5 //具体的业务
6 void setName(String name);
7 void setAge(int age);
8 String getInfo();
9
10 /**
11 * Demonstrates some basic types that you can use as parameters
12 * and return values in AIDL.
13 */

14 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
15 double aDouble, String aString)
;
16}

然后,重新 rebuild project , Android SDK 工具会在相应的目录生成对应的与 .aidl 文件同名的 .java 接口文件,具体目录如下:

2. 具体的业务对象实现基于 .aidl 文件生成的接口

上一步只是使用 AIDL 定义了基本的业务操作,rebuild 之后会生成与 .aidl 相同文件名的 .java 文件,生成的这个接口文件中有一个名称为 Stub 的一个子类,这个子类也是其父接口的抽象实现,主要用于生成 .aidl 文件中的所有方法,Stub 类声明具体如下:

1// Stub
2public static abstract class Stub extends android.os.Binder implements com.manu.aidldemo.IPersonAidlInterface
3

显然,Stub 实现了本地接口且继承了 Binder 对象,介于 Binder 对象在系统底层的支持下,Stub 对象就具有了远程传输数据的能力,在生成 Stub 对象的时候会调用 asInterface 方法,具体如下:

1// asInterface
2public static com.manu.aidldemo.IPersonAidlInterface asInterface(android.os.IBinder obj){
3
4 if ((obj==null)) {
5 return null;
6 }
7
8 // 检索 Binder 对象是否是本地接口的实现
9 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
10
11 if (((iin!=null)&&(iin instanceof com.manu.aidldemo.IPersonAidlInterface))) {
12 return ((com.manu.aidldemo.IPersonAidlInterface)iin);
13 }
14
15 return new com.manu.aidldemo.IPersonAidlInterface.Stub.Proxy(obj);
16}

asInterface 这个方法在 Stub 创建的时候会调用,主要功能就是检索 Binder 对象是否是本地接口的实现,根据 queryLocalInterface() 方法返回值判断是否使用代理对象,这个检索过程应该由系统底层支持,如果返回为 null,则创建 Stub 的代理对象,反之使用本地对象来传输数据,下面看一下 Binder 为什么具有远程通信的能力,因为 Stub 继承了 Binder 类,Binder 类具体如下:

1// Binder
2public class Binder implements IBinder {
3 //...
4}

下面是官网对 IBinder 接口的描述:

远程对象的基础接口,轻量级远程过程调用机制的核心部分,专为执行进程内和跨进程调用时的高性能而设计。该接口描述了与可远程对象交互的抽象协议。不要直接实现这个接口,而是从Binder扩展。

这里我们知道 Binder 实现了 IBinder 接口,也就是说 Binder 具备了远程通信的能力,当不同进程之间(远程)之间通信时,显然使用的是 Stub 的代理对象,这个代理类里面具体的业务处理逻辑,如下面这个方法,具体如下:

1//具体的业务
2@Override
3public void setName(java.lang.String name) throws android.os.RemoteException{
4 // 将数据序列化
5 android.os.Parcel _data = android.os.Parcel.obtain();
6 android.os.Parcel _reply = android.os.Parcel.obtain();
7 try {
8 _data.writeInterfaceToken(DESCRIPTOR);
9 _data.writeString(name);
10 // 这个方法会最终调用 onTransact 方法
11 mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
12 _reply.readException();
13 }
14 finally {
15 _reply.recycle();
16 _data.recycle();
17 }
18}

这里主要就是将数据序列化,然后在系统跨进程支持下最终调用 onTransact() 方法,下面是 onTransact() 方法,具体如下:

1@Override
2public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{
3 switch (code){
4 case INTERFACE_TRANSACTION:
5 {
6 reply.writeString(DESCRIPTOR);
7 return true;
8 }
9 case TRANSACTION_setName:
10 {
11 //...
12 // 最终调用了 Stub 里面的业务方法
13 this.setName(_arg0);
14 //...
15 }
16 }
17}

显然,这个方法在当系统回调给开发者的时候,传递回来的 code 是一个常量,在跨进程时,每个具体的服务(方法)都会对应一个编号,然后根据这个编号来执行相应的服务(业务),这里说到了最后要执行的具体业务,那么这个业务要体现在什么地方呢,从上面可知 Stub 是一个抽象类,那么它所提供的具体业务必然需要一个具体的实现类来完成,下面实现这个具体的业务类,具体如下:

1/**
2 * Created by jzman
3 * Powered by 2018/3/8 0008.
4 */

5public class IPersonImpl extends IPersonAidlInterface.Stub {
6 private String name;
7 private int age;
8
9 @Override
10 public void setName(String name) throws RemoteException {
11 this.name = name;
12 }
13
14 @Override
15 public void setAge(int age) throws RemoteException {
16 this.age = age;
17 }
18
19 @Override
20 public String getInfo() throws RemoteException {
21 return "My name is "+name+", age is "+age+"!";
22 }
23
24 @Override
25 public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
26
27 }
28}

这个类就是对外提供的具体业务类,同时其实例也是一个 Binder 对象。

3. 向客户端公开接口

创建一个 Service 以便对外提供具体的业务,具体如下:

1// Service
2public class PersonService extends Service {
3 public PersonService() {
4 }
5
6 @Override
7 public IBinder onBind(Intent intent) {
8 return new IPersonImpl();
9 }
10}

当外部调用 bindService() 方法绑定服务时,就会调用 onBind() 方法返回 IBinder 对象,这个 IBinder 对象也是具体的业务对象,如这里的 onBind() 方法返回的也是具体的业务对象,两者是统一的。此外,创建的 Service 要在 AndroidManifest.xml 文件中声明,具体如下:

1<service
2 android:name=".PersonService"
3 android:enabled="true"
4 android:exported="true"
5 android:process=":remote">

6service>

其中使用 process 关键字表示为该服务开启一个独立的进程,remote 可任意,表示进程名称,":"将会在主进程(进程名为包名)添加新名称作为新进程的名称,如 com.manu.study 将会变成 com.manu.study:remote。

4. 客户端远程调用

通过上面几步完成了服务的搭建,并将服务运行在独立进程中,下面主要就是客户端的具体调用了,具体实现参考如下:

1// Client
2public class MainActivity extends AppCompatActivity {
3 private static final String TAG = "MainActivity";
4 private IPersonAidlInterface iPersonAidlInterface;
5
6 @Override
7 protected void onCreate(Bundle savedInstanceState) {
8 super.onCreate(savedInstanceState);
9 setContentView(R.layout.activity_main);
10 }
11
12 public void bindServiceClick(View view) {
13 Log.i(TAG,"绑定服务...");
14 Intent intent = new Intent(this,PersonService.class);
15 // 绑定服务时自动创建服务
16 bindService(intent,conn, Context.BIND_AUTO_CREATE);
17 }
18
19 public void unbindServiceClick(View view) {
20 Log.i(TAG,"解绑服务...");
21 unbindService(conn);
22 }
23
24 public void callRemoteClick(View view) {
25 Log.i(TAG,"远程调用具体服务...");
26 try {
27 iPersonAidlInterface.setName("Tom");
28 iPersonAidlInterface.setAge(10);
29 String info = iPersonAidlInterface.getInfo();
30 System.out.println("这是远程调用的服务信息:"+info);
31 } catch (RemoteException e) {
32 e.printStackTrace();
33 }
34 }
35
36 private ServiceConnection conn = new ServiceConnection() {
37 @Override
38 public void onServiceConnected(ComponentName name, IBinder service) {
39 // 根据实际情况返回 IBinder 的本地对象或其代理对象
40 iPersonAidlInterface = IPersonAidlInterface.Stub.asInterface(service);
41 System.out.println("具体的业务对象:"+iPersonAidlInterface);
42 }
43
44 @Override
45 public void onServiceDisconnected(ComponentName name) {
46 // Service 意外中断时调用
47 }
48 };
49}

上述代码是客户端调用具体服务的过程。

5. 验证 AIDL

通过前面几步,服务端与客户端已经完成,下面来验证能否调用具体的业务,这里分两种情况:

1. 相同进程

创建 Service 的时候不要在 AndroidManifest.xml 文件中不要使用 process 开启独立进程即可,此时服务进程默认与客户端属于统一进程,结果如下:

2. 不同进程

创建 Service 的时候在 AndroidManifest.xml 文件中使用 process 开启独立进程即可,这个在上文中提到过,此时,服务进程与客户端进程位于不同进程,结果如下:

显然,如果服务与客户端处于不同进程,也就是常常说的进程间通信,具体是由 IBinder 对象的代理对象完成,反之,使用本地对象,也就是本地的 IBinder 对象,具体就是实现 AIDL 的业务类所生成的对象来完成。由于篇幅略长,所以如何在 AIDL 中使用自定义类型将在下篇文章中介绍。