sq_Hayden's blog

进程间通信之AIDL的实现

2018/06/19 Share

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 包结构搭建

  包结构如下图所示:
   

AIDL

  首先,我们需要建立一个与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
55
package 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>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}

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

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

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(account);
dest.writeInt(password);
}

@Override
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目录下
   

AIDL

  如果我们点进去查看的话,会发现系统为我们生成了一个抽象类Stub,它继承了Binder并实现了我们定义的接口,那么后面我们在调用IService相关功能的时候,只需要去拿到它的实例化对象就行了。接下来,就进入到我们的服务代码中。

3.1.4 服务的创建

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
//保存传递过来的对象
private User user;

@Override
public IBinder onBind(Intent intent) {
//这里返回我们的实体对象
return iService;
}

@Override
public void onCreate() {
super.onCreate();
}

//用于对外返回设置进来的User
private User getServiceUser(){
return user;
}

//这就是我们的服务接口实体类了
private final IService.Stub iService = new IService.Stub() {
@Override
public void addUser(User userData) throws RemoteException {
//保存别人添加的对象
user = userData;
//弹个提示
Toast.makeText(getApplicationContext(),"添加成功",Toast.LENGTH_SHORT).show();
}

@Override
public User getUser() throws RemoteException {
Log.i(TAG, "getUser: 这里走了");
//获取添加的对象
return getServiceUser();
}
};

  服务端的代码并不难,我们通过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
5
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}

  到了这里,服务端进程的工作就做完了,我们在Activity中调用服务接口传递一个对象,看看能否获取到此对象,从而实现进程内服务的调用:

AIDL

  可以看到,我们在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;

@Override
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();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

  从上面的代码我们可以看到,如果要获取服务端进程的Service, 我们除了要设置在服务端已经定义好的服务的Action之外,还需要将服务端进程的完整包名作为参数设置进去,只有这样我们才能成功通过此客户端进程去启动服务端的Service。接着调用stubasInterface方法,将传递进来的IBinder实例对象转换为我们对应的IService对象,这样,就可以去获取到对应的数据。
  接下来,我们在服务端进程中设置好数据,再开启客户端进程,看看能否在客户端进程中获取到我们设置的数据:
  

AIDL

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

CATALOG
  1. 1. AIDL实现不同进程间的通信
    1. 1.1. 1. AIDL介绍
    2. 1.2. 2. AIDL适用场景
      1. 1.2.1. 2.1 场景分析
      2. 1.2.2. 2.2 实现思路
    3. 1.3. 3. AIDL实现进程间通信
      1. 1.3.1. 3.1 服务端进程相关
        1. 1.3.1.1. 3.1.1 包结构搭建
        2. 1.3.1.2. 3.1.2 对象创建
        3. 1.3.1.3. 3.1.3 aidl文件的创建
        4. 1.3.1.4. 3.1.4 服务的创建
      2. 1.3.2. 3.2 客户端进程相关