AIDL实现不同进程间的通信
- 引言:在互联网经济时代,支付宝可谓是普及到了大街小巷。就某个游戏应用来说,当我们需要用支付宝充值时,会发现程序自动去调用了支付宝提供的相关服务。如何做到呢?就要靠进程间通信来发挥作用了。Google工程师考虑到了这方面需求,为我们提供了专门的实现方法,接下来就去看看如何利用AIDL实现进程间的通信。
1. AIDL介绍
我们知道,Android中的每一个应用,都由自己的dvm进行单独管理。也就是说,Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。
为了使其他的应用程序也可以访问本应用程序提供的服务,Android使用了一种接口定义语言,即AIDL(Android Interface Definition Language,AIDL)来公开服务的接口,从而达成进程间通信的目的。
接下来,就让我们通过一个小例子,来完成AIDL的相关功能。
2. AIDL适用场景
2.1 场景分析
假设有这么一个场景:我们的两个进程(此处假设为服务端进程及客户端进程)都需要针对某个类(假设有一个User类)进行修改,然而Android是不允许进程间直接进行数据共享的,那应当如何做到呢?
2.2 实现思路
针对这个问题,首先我们知道四大组件中除服务之外的三个,都可以完成进程间的数据访问操作。那么,其实我们的Service也可以实现这个功能。今天我们就采用AIDL的高效方式来完成这两个不同进程间的数据通信。
怎么样才能实现一个完整的AIDL相关传输呢?主要有以下的几个步骤:
【1】 包结构的搭建;
【2】 创建要传输的实例化对象;
【3】 创建AIDL相关文件;
【4】 在服务端进程创建服务,并对外提供相应接口;
【5】 在客户端绑定服务,获取服务接口对象并调用方法。
好了,思路就介绍到这里,接下来就进入到具体的开发中。
3. AIDL实现进程间通信
3.1 服务端进程相关
3.1.1 包结构搭建
包结构如下图所示:

首先,我们需要建立一个与
java
同级的文件夹aidl
(可将java
文件夹Copy过来改名字),接着在我们的bean
目录下对应建立我们将要传递的对象,并建立对象所对应的.aidl
文件,最后我们需要在aidl
的包下建立我们服务接口对应的.aidl
文件。这样,我们的AIDL结构就搭建完成了。下面进行详细说明:
3.1.2 对象创建
由于我们需要传递的是一个对象,并不是一个基本的数据类型,因此必须进行序列化,这里建议采用Parcelable
完成相关的序列化操作,这是android比较高效的序列化方法。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
55package com.idx.aidl.bean;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by hayden on 18-6-19.
*/
public class User implements Parcelable {
private String account;
private int password;
public User(String account, int password) {
this.account = account;
this.password = password;
}
protected User(Parcel in) {
account = in.readString();
password = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(account);
dest.writeInt(password);
}
public String toString() {
return "User{" +
"account='" + account + '\'' +
", password=" + password +
'}';
}
}
3.1.3 aidl文件的创建
首先是User.aidl:1
2
3
4
5// User.aidl
package com.idx.aidl.bean;
//要和我们定义的User类在同一个包(bean)里
parcelable User;
接着是我们服务对外提供方法接口的.aidl文件:这里需要注意,我们必须手动将User的全路径import
进来,否则系统无法找到此对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14 // IMyAidlInterface.aidl
package com.idx.aidl;
import com.idx.aidl.bean.User;
// Declare any non-default types here with import statements
interface IService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void addUser(in User user);
User getUser();
}
在上述服务接口的搭建中,我们定义了addUser(in User user)
方法,可以看到,相对我们以前的写法多了一个in
,需要说明的是,这个参数是不能省略的,不然系统会报错。它代表了我们将通过此方法写入User
对象。
到了这里,我们关于AIDL的搭建工作就完成了,接下来将我们的项目Build -- ReBuild Project
,我们就会发现系统自动为我们生成了AIDL的底层代码。如何查看它呢?在我们app的build目录下

如果我们点进去查看的话,会发现系统为我们生成了一个抽象类
Stub
,它继承了Binder并实现了我们定义的接口,那么后面我们在调用IService相关功能的时候,只需要去拿到它的实例化对象就行了。接下来,就进入到我们的服务代码中。
3.1.4 服务的创建
1 | //保存传递过来的对象 |
服务端的代码并不难,我们通过IService.Stub
来获取到了我们对外接口的实体对象,通过它我们就可以建立其进程间的通信,因为它就是数据联系的纽带。
到了这里,我们还需要做两件事:
一、我们的服务虽然在服务端创建了,但是需要为它设置对应的action,这样我们才能在客户端进程中去开启对应的这个服务;
AndroidManifest.xml
中服务相关配置: 1
2
3
4
5
6<service android:name=".MyService">
<intent-filter>
//在这里要为我们的服务配置一个自定义action
<action android:name="com.idx.demo.service"/>
</intent-filter>
</service>
二、由于我们的aidl
文件夹处于和java
文件夹同级的层次,因此要去我们模块的build.gradle
下进行相关配置,从而可以保证系统能够找到我们对应的实体类User
。
build.gradle
相关配置:1
2
3
4
5sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
到了这里,服务端进程的工作就做完了,我们在Activity中调用服务接口传递一个对象,看看能否获取到此对象,从而实现进程内服务的调用:

可以看到,我们在Activity中获取到这个
IService
对象后,确实可以通过它来操作服务中的相关数据。那么接下来,就进入到客户端进程的相关开发中。
3.2 客户端进程相关
客户端进程的代码编写就相对简单一些,由于我们已经在服务端定义了main
包下关于aidl
的相关配置,只需要将aidl
包连同其子类一并Copy到我们客户端进程main
包的下面,然后去Rebuild
一下,就实现了IService接口的导入,剩下的就是主Activity的代码: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 /**
* 尝试连接服务
*/
private void attemptToConnectService() {
Intent intent = new Intent();
//设置服务的action
intent.setAction("com.idx.demo.service");
//设置服务器定义的包名
intent.setPackage("com.idx.aidl");
//开启服务
bindService(intent,conn,BIND_AUTO_CREATE);
}
private ServiceConnection conn = new ServiceConnection() {
IService iService;
public void onServiceConnected(ComponentName name, IBinder service) {
//转为对应接口的实例对象
iService = IService.Stub.asInterface(service);
try {
User user = iService.getUser();
Toast.makeText(getApplicationContext(),"拿到的数据是:"+user.toString(),Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName name) {
}
};
从上面的代码我们可以看到,如果要获取服务端进程的Service
, 我们除了要设置在服务端已经定义好的服务的Action
之外,还需要将服务端进程的完整包名作为参数设置进去,只有这样我们才能成功通过此客户端进程去启动服务端的Service。接着调用stub
的asInterface
方法,将传递进来的IBinder实例对象转换为我们对应的IService对象,这样,就可以去获取到对应的数据。
接下来,我们在服务端进程中设置好数据,再开启客户端进程,看看能否在客户端进程中获取到我们设置的数据:

可以看到,数据在客户端进程已经可以拿到了。本篇解析到此结束。
————学如逆水行舟,不进则退,技术更是如此。