一.简介
IPC (Inter-Process Communication),意为进程间的通信。一个操作系统不同的进程,有自己的进程内存空间,其中的数据不共享,因此进程间的通信就需要采用一定的机制,传统的进程通信的方式有 Socket,管道,内存共享,消息队列等,在 Linux 系统中同样存在这些方式。Android 系统是基于 Linux 系统的,但是 Android 系统却采用了另一种进程通信机制 Binder ,主要有以下两点原因:
- 性能,传统的通信的方式,要么开销大且效率里,比如 Socket ,或者数据需要进行多次拷贝,性能较低。
- 安全,在 Android 系统中,获取系统操作很简单比如连接无线网络,访问文件等,如果不对进程进行标识,一些程序就很容易进行频繁地进行底层操作。传统 IPC 没有任何安全措施,不能识别用户进程,比如 Socket ,用户完全可以指定自己的端口。
而使用 Binder 可以做到只对数据进行一次拷贝,并在每次通信的时候问每个用户进程添加标识。因此相对传统的 IPC 机制性能和安全性都相对较高。
二.基础概念
1.Parcel/Serializable/Parcelable
Serializable :[sɪərɪrlaɪ’zəbl]
1.Parcel 是一个容器,可以包含数据或者是对象引用,支持序列化以及跨进程之后进行反序列化,里面有很多 native 方法,在底层实现中都是用于将数据写入内存,和从内存中。
2.Serializable 和 Parcelable 都是实现序列化的接口,Parcelable 是在内存中直接进行读写,中间结合需要 Parcel 类去做一些低层的工作 (C/C++ 实现)而 Serializable 是通过使用 IO 流的形式将数据读写入在硬盘上, Parcelable 的性能要强于 Serializable, 但是 Parcelable 无法将数据进行持久化。
2.IBinder/Binder
1.IBinder 是 Binder 的接口,。这个接口定义了与远程对象交互的协议。不仅用于远程调用,也用于进程内调用
2.Binder 实现了 Binder 接口,这是 Binder 机制的通信的媒介,进程 A 调用进程 B 的方法,具体就是由这个 Binder 实现。
3.Binder 驱动
(1).运行空间(内核空间/用户空间)
在 Android 系统中负责调用系统一切资源的是 Linux 的内核空间,而用户程序运行的空间是用户空间,每个进程有属于自己的用户空间,当用户空间需要调用系统资源的时候,只能通过内核空间提供的 System call ,系统调用,去访问内核空间。
(2).Binder 驱动
传统的 IPC 机制是 Linux 内核支持的,但是 Binder 并不是 Linux 支持的,但是 Linux 可以动态添加内核模块,这个模块在内核中有自己的内核空间,因此 Android 就在系统中添加一个模块,这个模块就叫 Binder 驱动。Binder 驱动会自动完成进程 A 和 进程 B 的 Binder 对象的转换;而相应的方法参数就会在用户空间通过 mmap ,ioctl ,open 等 方法就可以调用 Binder 驱动里的方法,然后这样两个不同进程的用户空间就可以通过这个 Binder 驱动的内核空间进行通信。
4.Binder 框架
1.Client,客户进程,这是相对的说法,只要是发起进程请求的一方都可以为客户进程
2.Server,服务进程,这也是相对的说法,被请求执行某种服务的进程都可以称为服务端进程
3.ServiceManager,SM,对服务进程的管理,每个服务进程都要在这里注册所拥有的跨进程的 Binder, 并保存对应的 Binder 的引用,Client 可以通过名字在这里查询想要的服务的名字,最后 SM 就会返回该 Server 的一个 Binder 的引用。SM 也是一个进程,但是每个需要和 SM 通信的进程都知道 SM 的引用,这是提前就固定的。
4.Binder 驱动,在具体的通信过程中做中转,比如需要传递的数据,Binder 对象在两个进程间的转换等.
5.Java 层的 Binder
1.AIDL
AIDL (Android 接口定义语言),用于跨进程通信,通过定义 ADIL ,AS 会自动生成 Binder 类。比如,定义一个如下的接口1
2
3
4
5
6
7
8import com.example.asus.testdemo.Book;//即使在一个包下也要 import
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
aidl 支持的数据类型有如下
- 基本数据类型
- String 和 CharSequence
- List,只支持 ArrayList,每个元素都支持 AIDL
- Map,只支持 Map,每个key,value,都支持 AIDL
- Parcelable,所有实现 Parcelable 接口
- AIDL,所有定义为 AIDL 的接口,比如上面的 IBookManager ,也可以作为一个接口对象
生成的 aidl 文件为: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
141public interface IBookManager extends android.os.IInterface {
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.asus.testdemo.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.asus.testdemo.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.asus.testdemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.asus.testdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.asus.testdemo.IBookManager))) {
return ((com.example.asus.testdemo.IBookManager) iin);
}
return new com.example.asus.testdemo.IBookManager.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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.asus.testdemo.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.asus.testdemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.asus.testdemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.asus.testdemo.IBookManager {
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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public java.util.List<com.example.asus.testdemo.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.asus.testdemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.asus.testdemo.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.example.asus.testdemo.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public java.util.List<com.example.asus.testdemo.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.asus.testdemo.Book book) throws android.os.RemoteException;
}
这个接口的内部结构就是有一个 Stub 的内部类,这 Stub 内部类还有一个内部类 Proxy。先看 Stub 类,Binder 的 java 层采用的是代理模式,其中 Stub 类代表是 Binder 的本地对象,而 Proxy 则是 Binder 在另一个进程的代理对象。先看 Stub
1 | public static abstract class Stub extends android.os.Binder implements com.example.asus.testdemo.IBookManager { |
- DESCRIPTOR,Binder 的唯一标识
- asInterface,用于 Binder 到转换,如果客户服务端都在同一进程,就会返回一个 Binder 对象本身,如果是在不同进程就返回其代理 Proxy.
- asBinder,返回当前的Binder 对象
- onTransact,这个方法在服务端的线程池中,通过标识判断客户端调用的是什么方法,data 就是方法的参数。
这是一个抽象类,因此具体的是实现就在 Service 中,通过onBinder 方法返回具体的实现。
接着看 Proxy
1 | private static class Proxy implements com.example.asus.testdemo.IBookManager { |
客户进程得到的对象通常为这个类的实例,_data 就是参数,_reply 就是相应的返回值,而在这里封装了具体的数据写入的底层实现,而这对客户进程是透明的。
三.工作流程
1.大致流程
1.当有一个应用进程提供一个可跨进程访问的的服务的时候,就会通过默认的通信地址向 ServiceManager 注册服务相应的线程就进入等待,这时 ServiceManager 就会通过 ioctl 方法让 Binder 驱动在内核空间保存相应的信息,然后 ServiceManager 就会唤醒线程通知应用进程注册成功。
2.客户进程查询服务,SM 会通过和 Binder 驱动交互,查询已注册的服务,查询到就返回相应的对象,这时就会返回一个代理对象。
3.通过调用代理对象的方法这时客户进程的这个线程进入等待状态,按之前的 Proxy 的方法的实现可以知道,在客户进程只是对一些数据进行包装,然后写入Binder 驱动和 SM 的共享内存,这是一次拷贝也是唯一的一次, 而服务进程的用户空间的地址和这个内存已经做了映射,所以 服务进程可以直接获取内存中的数据, 对于Binder Proxy 对象,就又转化为 Binder 的 Stub 的具体实现,等执行完后就返回,这同样也是一次通信,最后客户的进程的线程就会被唤醒。
2.跨进程的接口
同样的通过定义一个接口,让客户进程实现相应的方法,可以做到在服务进程回调所有客户进程的方法。
1 | interface IOnNewBookListenerArrivedListener { |
1 | interface IBookManager { |
在服务进程有一个用于对所有接口保存的类,RemoteCallbackList 是系统用于跨进程接口通信的,内部有一个 HasMap ,key 是 IBinder ,value 是 listener, 因此可以根据 Binder 找到客户端,因为每个客户端的 Proxy 都是不一样的。1
private RemoteCallbackList<IOnNewBookListenerArrivedListener> mListeners = new RemoteCallbackList<>();
在重写的方法中1
2
3
4
5
6
7
8
9
10@Override
public void registerListener(IOnNewBookListenerArrivedListener listener) throws RemoteException {
mListeners.register(listener);
}
@Override
public void unregisterListener(IOnNewBookListenerArrivedListener listener) throws RemoteException {
mListeners.unregister(listener);
}
回调的具体使用可以看以下例子1
2
3
4
5
6
7private void onNewBookArrived(Book book) throws RemoteException{
for (int i=0;i<mListeners.beginBroadcast();i++){
IOnNewBookListenerArrivedListener listenerArrivedListener = mListeners.getBroadcastItem(i);
listenerArrivedListener.onNewBookArrvied(book);
}
mListeners.finishBroadcast();
}
在客户端只要这样使用就可以
1 | private IOnNewBookListenerArrivedListener mListener = new IOnNewBookListenerArrivedListener.Stub() { |
四.其他方式
1.Intent/Bundle
Intent 和 Bundle 都实现了 Parcelable 接口,因为 Intent 常常用于组件的通信,因此在跨进程中,这也只是适合于组件间的跨进程通信,低层也是 Binder 机制。
2.文件共享
两个应用进程使用同一个共享文件并结合序列化可实现对数据的传递,但是在 Linux 系统中对文件的读写并没有处理并发情况,因此在高并发访问的情况可能会产生一些问题。
3.Messenger
和 Messege 共同使用,但是服务对 Messege 的处理时串行的,因此也不适合高并发情况,且不能获取返回的结果,不适合 Remote Procedure Call (RPC 跨进程调用)。这是对 AIDL 封装后的是使用,也就是内部是 Binder 机制。
4.ContentProvider
用于一对多的进程间的数据共享,支持并发操作,因为内部的数据库实际上是可以做到同步的,在底层仍然是 Binder 机制
5.Socket
常用于不同网络的进程间的通信,因此不适合 RPC 。