Android中的IPC之Binder机制

Android中的IPC机制即跨进程通信技术(Inter-Process-Comunicate),主要技术是 Android 的序列化机制和Binder。 Android 跨进程通信常见的几种方式;Bundle、文件共享、AIDL、Messenger、ContentProVider和Socket等。其中AIDL、Messenger、ContentProvider底层属于基于 Android Binder 机制实现的。

Android 多进程模式

关于开启多进程模式注意,android:process=":remote"android:precess="com.xxx.xxx.remote"指定Android 多进程方式的区别,进程名以”:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程,而进程名不以”:”开头的进程属于全局进程,其他应用通过 ShareUID 方式(签名也需相同)可以和它跑在同一个进程中。Android系统为每个应用分配一个唯一的 UUID,具有相同的 UUID 的应用才能共享数据。

多进程模式下会造成如下几方面的问题:

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效
  • SharedPrefence 的可靠性下降(SharedPrefence 本质上是读写 xml 文件进行存储,并发操作会导致混乱)
  • Application 多次创建

运行在同一个进程中的组件是属于同一个虚拟机和同一个 Application 的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和 Application 的。

IPC基础概念介绍

IPC基本概念主要包含三方面内容:Serializable 和Parcelable 接口以及 Binder。Serializable 和Parcelable 接口可以完成对象的序列化过程。通过序列化保证对象的持久化,可以使用 Intent 、Bundle 以及网络传递对象。

Serializable序列化

Serializable 实现序列化的过程主要通过下面的表示实现:

1
private static final long serialVersionUID = 8711368828010083044L

序列化标识并不是必须的,系统会自动生成这个标识,但是当实现 Serializable 序列化接口的对象实体类结构发生变化,序列化标识也会重新赋值,这将对与反序列化过程有影响。

1
2
3
4
5
6
7
8
9
10
//序列化过程
User user = new User("0","jake",true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)int.readObject();
in.close();

需要注意的是 user 和 newUser 的内容相同,但不是同一个对象。关于 serialVersionUID 这个标识,只有序列化之前的对象的 serialVersionUID 值和序列化之后 serialVersionUID 值一致,才可以正常反序列化。实体类中成员数量和类型发生变化, serialVersionUID 值都会改变。一般来说我们手动指定 serialVersionUID 值,这样反序列化过程不会受到影响。如果不手动指定 serialVersionUID ,反序列化是当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类结的 hash 值并把它赋值给 serialVersionUID,这个时候当前类的serialVersionUID 就和序列化后的数据中的 serialVersionUID 不一致,于是反序列化失败。当我们手动指定了它以后,就可以很大程度上避免反序列化过程的失败,比如删除了某些成员变量或者增加了某些成员变量,数据仍然可以正常反序列化。不过如果类结构发生非常规性改变,比如修改类名,变量类型或者名称,即便指定了serialVersionUID,依然会反序列化失败。

Parcelable序列化

Parcelable 是 Android 平台上提供的序列化方案,相对于 Serialable 大量的 IO 操作,拥有更好的性能。

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
public class Book implements Parcelable {
private int bookId;
private String bookName;
public Book() {}

public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}

public int getBookId() {
return bookId;
}

public void setBookId(int bookId) {
this.bookId = bookId;
}

public String getBookName() {
return bookName;
}

public void setBookName(String bookName) {
this.bookName = bookName;
}

protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}

//当设置参数标签为out和inout时会要求重写这个方法
public void readFromParcel(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}


public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}

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

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

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

Android进程通信之Binder机制

Messenger-进程间通信的信使

Messenger 根据 Handler 生成实例构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Messenger.java
/**
* Create a new Messenger pointing to the given Handler. Any Message
* objects sent through this Messenger will appear in the Handler as if
* {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
* been called directly.
*
* @param target The Handler that will receive sent messages.
*/
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
/**
* Send a Message to this Messenger's Handler.
*
* @param message The Message to send. Usually retrieved through
* {@link Message#obtain() Message.obtain()}.
*
* @throws RemoteException Throws DeadObjectException if the target
* Handler no longer exists.
*/
public void send(Message message) throws RemoteException {
mTarget.send(message);
}

通过 Handler 对象创建 Messenger,通过 Messenger#send(Message) 方法就可以跨进程传递信息。

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
//MainActivity.java
private Messenger client = new Messenger(new MessengerHandler());
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

MainActivity.this.service = new Messenger(service);
Message msg = Message.obtain(null, Contanstants.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "hello, this is client");
msg.setData(bundle);
//指定回复信使
msg.replyTo = client;
try {
MainActivity.this.service.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

MainActivity 通过调用bindservice() 方法,获取IBinder 对象。看一下 Messenger 关于 IBinder 参数的构造方法:

1
2
3
4
5
6
7
8
9
/**
* Create a Messenger from a raw IBinder, which had previously been
* retrieved with {@link #getBinder}.
*
* @param target The IBinder this Messenger should communicate with.
*/
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}

上面是 clien 端向 service 端发送信息,再看一下关于 service 端处理 client 端的数据,并回复 client 端。上述代码我们在 Message 传递信息中指定了回复信使,看一下 service 端代码:

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
//MessengerService.java
private Messenger mMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == Contanstants.MSG_FROM_CLIENT) {
Bundle bundle = msg.getData();
Log.d(TAG, bundle.getString("msg"));
Messenger clientMessenger = msg.replyTo;
Message replyMsg = Message.obtain(null, Contanstants.MSG_FROM_SERVER);
bundle.putString("msg", "this is server");
replyMsg.setData(bundle);
try {
clientMessenger.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
super.handleMessage(msg);
}
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}

AIDL(android interface define launage)

关于基于 Binder 机制的 AIDL 以及 Messenger 概述:AIDL 文件在编译过程中会在 build 文件中生成对应的java文件,所以我们也可以通过写java文件来实现进程间通信。这里简单介绍一样 AIDL 文件对应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
public interface IBookManager extends android.os.IInterface {
//...
public static abstract class Stub extends android.os.Binder
implements com.markzl.android.androidipc.entity.IBookManager {

private static final java.lang.String DESCRIPTOR = "com.markzl.android.androidipc.entity.IBookManager";

/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an com.markzl.android.androidipc.entity.IBookManager interface,
* generating a proxy if needed.
* client端会调用该方法根据service端传回的IBinder对象生成IBookManager对象
*/
public static com.markzl.android.androidipc.entity.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.markzl.android.androidipc.entity.IBookManager))) {
return ((com.markzl.android.androidipc.entity.IBookManager) iin);
}
return new com.markzl.android.androidipc.entity.IBookManager.Stub.Proxy(obj);
}

/**
* service端会将当前的binder通过@link{Service#OnBinder(Intent intent)}传递给client
*/
@Override
public android.os.IBinder asBinder() {
return this;
}
//...
private static class Proxy implements com.markzl.android.androidipc.entity.IBookManager {
//...
}
}
public void addBookIn(com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException;
public void addBookOut(com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException;
public void addBookInOut(com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException;
}

可以看到 client 端调用的IBookManager.Stub#asInterface()方法中,会先通过 binder 对象查询当前进程中的IBookManager 对象是否存在,不存在就走 Proxy 代理类。关于 Proxy 中如何处理跨进程通信,下面会结合AIDL定向标签来介绍。

关于 AIDL 中定向标签 in,out,inout

通过分析定向标签,我们同样可以学习到 AIDL 的基本原理。

1
2
3
4
5
6
7
8
9
10
// IBookManager.aidl
package com.markzl.android.androidipc.entity;
import com.markzl.android.androidipc.entity.Book;
import com.markzl.android.androidipc.IOnNewBookAddedListener;

interface IBookManager {
void addBookIn(in Book book);
void addBookOut(out Book book);
void addBookInOut(inout Book book);
}

生成的 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
public interface IBookManager extends android.os.IInterface {
//...
public static abstract class Stub extends android.os.Binder
implements com.markzl.android.androidipc.entity.IBookManager {
//...

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
//根据方法对应的code码处理相应数据
//...
case TRANSACTION_addBookIn:
//...
break;
case TRANSACTION_addBookOut:
//...
break;
case TRANSACTION_addBookInOut:
//...
break;
}
}

private static class Proxy implements com.markzl.android.androidipc.entity.IBookManager {
//...
static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
}
public void addBookIn(com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException;
public void addBookOut(com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException;
public void addBookInOut(com.markzl.android.androidipc.entity.Book book) throws android.os.RemoteException;
}

in 标签

in 标签 client 端向 service 端写入数据,我们看一下生成的对应的 IBookManager.java 文件,其中用于跨进程处理数据的 IBookManager.Stub 内部私有 Proxy 类:

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
//IBookManager.java 
private static class Proxy implements com.markzl.android.androidipc.entity.IBookManager {
private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
mRemote = remote;
}

@Override
public android.os.IBinder asBinder() {
return mRemote;
}

@Override
public void addBookIn(com.markzl.android.androidipc.entity.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_addBookIn, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

可以看到 Proxy 中类将我们传入的实体序列化后,交给 mRemote#transact 方法处理,从 Proxy 类中可以看出,mRemote 是一个 IBinder 对象,在调用 Proxy 的构造方法时传入的是 Binder 的对象。 transact 方法是在IBinder 接口类中定义的,其实现类 Binder#transact 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);

if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}

可以看到最终调用的方法是onTransact(code, data, reply, flags),接下来我们看一下 onTransact 中方法如何处理序列化后的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
//...
switch (code) {
//...
case TRANSACTION_addBookIn: {
data.enforceInterface(descriptor);
com.markzl.android.androidipc.entity.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.markzl.android.androidipc.entity.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBookIn(_arg0);
reply.writeNoException();
return true;
}
}
}

onTransact() 方法中将序列化的数据重新反序列化成一个新的Book对象,然后调用service端的addBookIn()方法添加Book对象。注意这个过程是生成一个新的Book对象,具体为什么要注意生成新对象后面会讲到。这里总结一下in标签的作用向service传递数据但没有处理service的reply结果。

out标签

同样首先看Proxy类中生成的addBookOut()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void addBookOut(com.markzl.android.androidipc.entity.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);
mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
book.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}

可以看到这里我们并没有处理 Book 对象,_data 始终没有写入 Book 对象,而且调用transact() 方法之后的_reply.readInt() 不等于0的时候,会重新从 _reply 中读取 book 实体。我们再来看一下调用的 onTransact() 方法中如何处理out标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case TRANSACTION_addBookOut: {
data.enforceInterface(descriptor);
com.markzl.android.androidipc.entity.Book _arg0;
_arg0 = new com.markzl.android.androidipc.entity.Book();
this.addBookOut(_arg0);
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}

out定向标签中是重新生成了一个新的 Book 对象,没有写入任何值。然后调用了addBook(Book book)方法,由此可知 service 并没有真正添加 client 传来的 Book 对象。

inout标签

先看 Proxy#addBookInOut()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void addBookInOut(com.markzl.android.androidipc.entity.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_addBookInOut, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
book.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}

_data 传入 book 序列化数据时和in标签同样的处理, _reply 的处理和 out 标签时保持一致。接着我们看一下 addBookInOut() 方法标签对应的 onTransact() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
case TRANSACTION_addBookInOut: {
data.enforceInterface(descriptor);
com.markzl.android.androidipc.entity.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.markzl.android.androidipc.entity.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBookInOut(_arg0);
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}

调用IBookManager#Stub实例对象通过 addBookInOut 方法添加由data反序列化后得到的Book对象。inout标签在某种意义上算得上是in和out标签的“并集”,目前并没有看出out和inout标签中的 _reply 值存在的意义。_reply在处理有返回值的方法使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Proxy#getBookList()
@Override
public java.util.List<com.markzl.android.androidipc.entity.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.markzl.android.androidipc.entity.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.markzl.android.androidipc.entity.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

onTransact()中对于_reply赋值如下:

1
2
3
4
5
6
7
8
//IBookManager.Stub#onTransact()
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<com.markzl.android.androidipc.entity.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}

关于AIDL各种方法生成的详细内容都可以在build文件夹下生成对应java文件中看到。

service端数据改动时如何主动响应client端

service数据有改动如何主动响应client端?这种情况类似View的setOnClickListener设置监听事件方法,当View被单击调用OnClickListener#onClick方法来做出相应处理,即事件回调。在之前的IBookManager添加如下接口:

1
2
3
4
5
6
//...
interface IBookManager {
//...
void setOnBookAddedListener(OnBookAddedListener listener);
void removeOnBookAddedListener(OnBookAddedListener listener);
}

创建接口类OnBookAddedListener:

1
2
3
4
//OnBookAddedListener.aidl
interface OnBookAddedListener{
void onNewBookAdded();
}

然后在service中实现如下:

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
public class BookService extends Service {

private List<Book> mBookList = new ArrayList<>();
private List<OnBookAddedListener> mListenerList = new ArrayList<>();

IBookManager bookManager = new IBookManager.Stub() {
//...
@Override
public void setOnBookAddedListener(OnBookAddedListener listener) throws RemoteException {
mListenerList.add(listener);
}

@Override
public void removeOnBookAddedListener(OnBookAddedListener listener) throws RemoteException {
mListenerList.remove(listener);
}
};

@Nullable
@Override
public IBinder onBind(Intent intent) {
//开启线程不断添加新书。然后遍历mListenerList,
//调用OnBookAddedListener#onNewBookAdded()方法通知client端
new Thread(new AddBookWorker()).start();
return bookManager.asBinder();
}
//...
}

在client端与ServiceConnection#onServiceConnected()注册监听方法:

1
2
3
4
5
6
7
8
9
10
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookManager = IBookManager.Stub.asInterface(service);
try {
//listener中打印日志
bookManager.registerOnNewBookAddListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}

bindService()方法之后,当service端线程不断添加新书,我们可以正常打印新书添加日志信息,但是当我们调用removeOnBookAddedListener()方法时,打印日志信息并没有像我们期望的那样停止。这是因为client端进程中的OnBookAddedListener对象和service端的OnBookAddedListener对象不是同一个对象,正如我们在3.3.1末尾提到的一样。那么我们该如何在serice端和client操作同一个对象。这里系统为我们提供了RemoteCallbackList类,原理是通过键值对的形式保存OnBookAddedListener对象,使得我们添加和移除的是同一个对象。关于RemoteCallbackList的介绍:

1
2
remoteCallbackList.register(listener);
remoteCallbackList.unregister(listener);

其中RemoteCallbackList#register() 简单的概述如下(详情请阅读源码):

1
2
3
4
IBinder binder = listener.asBinder();
//...
Callback cb = new Callback(listener, cookie);
mCallbacks.put(binder, cb);

可以看出利用的是两个进程使用的是同一个Binder对象来作为key,通过client 端和service端相同key(binder)删除相应的listener。

关于Binder线程池的使用

// 待续……

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×