BruceFan's Blog

Stay hungry, stay foolish

0%

Android跨进程通信——远程调用过程和AIDL

Android设计理念强调组件化,组件之间的依赖性很小。我们发送一个intent请求可以做到:

  • 启动另一个应用的Activity
  • 启动某一个进程的Service
  • 可以注册一个广播,只要有这个事件发生你都可以收到
  • 可以查询一个Content Provider获得你想要的数据

这其实都需要跨进程通信(IPC)的支持,只是Android将其封装的如此简单,应用开发者甚至完全不用关注它是不是和我在一个进程里。Android中的IPC是通过Binder实现的,本文主要介绍跨进程调用的过程和AIDL,下一篇介绍Binder实现机制。

Binder并不是android最早开始使用,它发源于Be和Palm之前的OpenBinder,由Dianne Hackborn领导开发。Hackborn现在就在google,是android framework的工程师,我们可以从https://lkml.org/lkml/2009/6/25/3 看一下,Hackborn如何描述binder。一句话总结:
In the Android platform, the binder is used for nearly everything that
happens across processes in the core platform.

Android将Binder封装的几乎不可见,层次结构如下图:

最底层的是Android的ashmen(Anonymous shared memory)机制,它负责辅助实现内存的分配,以及跨进程所需要的内存共享。
AIDL(android interface definition language)对Binder的使用进行了封装,可以让开发者方便的进行方法的远程调用,后面会详细介绍。
Intent是最高一层的抽象,方便开发者进行常用的跨进程调用。
使用Intent跨进程启动一个Activity或者Service是Android中非常基础的内容,这里就不再介绍了。
这里讲一下实现远程的方法调用。在Android中对方法的远程调用无处不在,随便打开framework/base中的包,都会发现很多AIDL文件。AIDL是Android为了方便开发者进行远程方法调用,定义的一种语言。使用AIDL完成一个远程方法调用只需要三个步骤:

  • 用AIDL定义需要被调用方法接口;
  • 实现这些方法;
  • 调用这些方法。

一个远程调用的例子

这个例子由两个APP组成,一个服务端,一个客户端,客户端向服务端远程(跨进程)请求数据。

服务端程序实现

这里我是用Android Studio进行开发的,用的不太熟练,这里也记录一下。

服务端的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
42
43
44
45
46
47
48
49
50
51
public class Student implements Parcelable {
private int age;
private String name;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public static final Parcelable.Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student[] newArray(int size) {
return new Student[size];
}

@Override
public Student createFromParcel(Parcel source) {
return new Student(source);
}
};

public Student() {
}

public Student(Parcel pl) {
age = pl.readInt();
name = pl.readString();
}

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

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

要传递的数据类型不是基础类型,需要对其进行包装,成为Parcelable的实例。

Student其实是一个JavaBean,JavaBean是使用Java语言开发的一个可重用组件。JavaBean必须实现可序列化接口,支持这个接口的对象可以存储和重建它们。JavaBean所有属性必须有对应的getter和setter方法。
AIDL支持的数据类型:String、Char、基本数据类型、List、Map、Parcelable

接着新建两个AIDL文件,新建方法是:右键java目录下的包->New->AIDL->AIDL File。这样Android Studio会自动创建一个与java目录同级的aidl目录,aidl目录下有与java目录中相同的包,如上图所示。
新建一个Student.aidl文件:

1
2
package com.example.aidlserver;
parcelable Student;

这个文件是为了接口文件能找到Student类对象。
再新建一个服务的接口IMyService.aidl

1
2
3
4
5
6
7
8
package com.example.aidlserver;
import com.example.aidlserver.Student;

interface IMyService
{
Map getMap(in String test_class, in Student student);
Student getStudent();
}

看起来和Java没有什么区别,定义了两个接口方法。在Android Studio的菜单栏点击Build->Make Project后,会在上图的build目录中生成对应的.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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/fan/Computer/Android/workspace/AIDLServer/app/src/main/aidl/com/example/aidlserver/IMyService.aidl
*/
package com.example.aidlserver;
public interface IMyService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.aidlserver.IMyService
{
private static final java.lang.String DESCRIPTOR = "com.example.aidlserver.IMyService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidlserver.IMyService interface,
* generating a proxy if needed.
*/
public static com.example.aidlserver.IMyService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.aidlserver.IMyService))) {
return ((com.example.aidlserver.IMyService)iin);
}
return new com.example.aidlserver.IMyService.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_getMap:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
com.example.aidlserver.Student _arg1;
if ((0!=data.readInt())) {
_arg1 = com.example.aidlserver.Student.CREATOR.createFromParcel(data);
}
else {
_arg1 = null;
}
java.util.Map _result = this.getMap(_arg0, _arg1);
reply.writeNoException();
reply.writeMap(_result);
return true;
}
case TRANSACTION_getStudent:
{
data.enforceInterface(DESCRIPTOR);
com.example.aidlserver.Student _result = this.getStudent();
reply.writeNoException();
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.aidlserver.IMyService
{
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 java.util.Map getMap(java.lang.String test_class, com.example.aidlserver.Student student) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.Map _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(test_class);
if ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_getMap, _data, _reply, 0);
_reply.readException();
java.lang.ClassLoader cl = (java.lang.ClassLoader)this.getClass().getClassLoader();
_result = _reply.readHashMap(cl);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public com.example.aidlserver.Student getStudent() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.example.aidlserver.Student _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getStudent, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
_result = com.example.aidlserver.Student.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getMap = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.Map getMap(java.lang.String test_class, com.example.aidlserver.Student student) throws android.os.RemoteException;
public com.example.aidlserver.Student getStudent() throws android.os.RemoteException;
}

首先这个接口继承了android.os.IInterface,它是所有由AIDL文件生成类的基类。接口中有一个内部类Stub,它继承自Binder并实现了这个生成的Java接口IMyService。但是它并没有实现我们定义的接口方法。而这些接口方法其实就是留给我们去实现的,新建一个MyService类:

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
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyServiceimpl();
}

public class MyServiceimpl extends IMyService.Stub {
@Override
public Student getStudent() throws RemoteException {
Student st = new Student();
st.setAge(18);
st.setName("terry");
return st;
}

@Override
public Map getMap(String testClass, Student student)
throws RemoteException {
Map<String, Object> map = new HashMap<String, Object>();
map.put("class", "五年级");
map.put("age", student.getAge());
map.put("name", student.getName());
return map;
}
}
}

继续看接口类,在Stub中实现了一个很重要的方法asInterface(android.os.IBinder obj)。该方法会查询是否有一个IMySerivce的实例,其实是查询是不是在同一个应用中调用它,那就不用实行远程调用,直接本地调用即可。如果不是本地接口,这时会返回一个Proxy对象。Proxy类是Stub的一个内部类,同样实现了IMyService接口。但它却实现了这些接口方法。这就意味着如果要进行远程调用,必须获取一个Proxy类的实例,方法是通过Stub类的asInterface()获得,具体实现一会可以在客户端中看到。

客户端程序实现

客户端程序的目录结构如下:

首先需要将服务端的aidl文件夹拷贝过来,Student类也要拷贝过来,而且包名不能变。接着编写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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MainActivity extends Activity implements View.OnClickListener {
Button btn1, btn2;

private IMyService myService = null;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myService = IMyService.Stub.asInterface(service); // 获取Proxy类的实例
btn2.setEnabled(true);
}

@Override
public void onServiceDisconnected(ComponentName name) {
myService = null;
btn2.setEnabled(false);
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = (Button) findViewById(R.id.Button01);
btn2 = (Button) findViewById(R.id.Button02);
btn2.setEnabled(false);

btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.Button01:
bindService(new Intent("com.example.aidlserver.IMyService"),
serviceConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.Button02:
StringBuilder sb = new StringBuilder();
try {
sb.append("学生名称为:" + myService.getStudent().getName() + "\n");
sb.append("年龄为:" + myService.getStudent().getAge() + "\n");
sb.append("map 对象内容为如下:"
+ myService.getMap("中国", myService.getStudent())
.toString());
} catch (RemoteException e) {
e.printStackTrace();
}

new AlertDialog.Builder(MainActivity.this).setTitle("调用外部服务")
.setMessage(sb.toString()).setPositiveButton(
android.R.string.ok, null).show();
break;
default:
break;
}
}
}

可以看到在serviceConnection成员变量的声明中,onServiceConnected()调用asInterface()获取了Proxy类的远程实例,Proxy就是远端传递过来的Binder对象的本地的代理。
ServiceConnection对象是Button01点击事件中为了绑定Service而调用的bindService方法的参数。bindService时,会调用ActivityThread的方法,并会传递一个Binder引用,而ActivityThread会回调ServiceConnection中的onServiceConnected方法,并将这个Binder对象传入,也就是asInterface的参数service。整个流程结束就获得了远程实例,保存到全局变量myService中,为调用远程方法做准备。
最后一步就是调用远程方法了,即Button02的点击事件。到这里就完成了开发工作,先启动服务端,再启动客户端:

继续分析

这里还需要回头分析一下数据传送和方法调用的一些过程。Proxy类中实现了getMap和getStudent方法,客户端可以通过Proxy调用它们,这个调用会被Proxy对象转换成可以用Parcel包装的基础数据类型,参数也被序列化写入一个数据包。一个int型code会被指派给transact,这个code用来标识方法名,因为Binder此时只允许传递int类型。这就需要客户端和远程服务端做好约定。

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 java.util.Map getMap(java.lang.String test_class, com.example.aidlserver.Student student) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.Map _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(test_class);
if ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_getMap, _data, _reply, 0);
_reply.readException();
java.lang.ClassLoader cl = (java.lang.ClassLoader)this.getClass().getClassLoader();
_result = _reply.readHashMap(cl);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public com.example.aidlserver.Student getStudent() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.example.aidlserver.Student _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getStudent, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
_result = com.example.aidlserver.Student.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

方法首先通过obtain方法获取两个Parcel对象。调用writeInterfaceToken方法用来标识,以便服务端能够识别。然后写入参数,注意这个写入顺序和取出顺序必须是一致的。然后对传给Proxy的Binder对象调用了transact方法。Parcel对象通过jni接口传递到Binder的C++空间,最终传递到Binder驱动。Binder驱动会让客户端进程休眠,并且将传过来的Parcel数据从客户端进程映射到服务端进程。然后反向的传递,从binder驱动传递到C++中间层,再通过JNI传递到java层。此时服务端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
@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_getMap:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
com.example.aidlserver.Student _arg1;
if ((0!=data.readInt())) {
_arg1 = com.example.aidlserver.Student.CREATOR.createFromParcel(data);
}
else {
_arg1 = null;
}
java.util.Map _result = this.getMap(_arg0, _arg1);
reply.writeNoException();
reply.writeMap(_result);
return true;
}
case TRANSACTION_getStudent:
{
data.enforceInterface(DESCRIPTOR);
com.example.aidlserver.Student _result = this.getStudent();
reply.writeNoException();
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

首先通过对code的判断,执行对应方法的内容,对数据按顺序解包,读出参数。最终调用服务端Service的方法,并将返回值写入Parcel,传递给Binder驱动。Binder驱动重新唤醒客户端进程并把返回值传递给Proxy对象,并最后被解包并作为Proxy方法的返回值。
从这一个流程下来,我们可以知道aidl主要就帮助我们完成了包装数据和解包的过程,并调用了transact过程。
reference
http://blog.csdn.net/notice520/article/details/8135600
http://www.cnblogs.com/TerryBlog/archive/2010/08/24/1807605.html