IPC机制系列六(1) Binder

Binder

       直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

       Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenger的底层其实是AIDL,所以这里选择用AIDL来分析Binder的工作机制。为了分析Binder的工作机制,我们需要新建一个AIDL示例,SDK会自动为我们生产AIDL所对应的Binder类,然后我们就可以分析Binder的工作过程。新建Java包AIDL,然后新建三个文件Book.java、Book.aidl和IBookManager.aidl,代码如下所示:

       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
37
38
39
40
41
42
43
44
package com.example.wy521angel.bindertest;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {

public int bookId;
public String bookName;

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


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

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

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

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];
}
};
}

       Book.aidl

1
2
3
4
5
6
// Book.aidl
package com.example.wy521angel.bindertest;

// Declare any non-default types here with import statements

parcelable Book;

       IBookManager.aidl

1
2
3
4
5
6
7
8
9
10
// IBookManager.aidl
package com.example.wy521angel.bindertest;

// Declare any non-default types here with import statements
import com.example.wy521angel.bindertest.Book;
interface IBookManager {

List<Book>getBookList();
void addBook(in Book book);
}

       上面三个文件中,Book.java是一个表示图书信息的类,它实现了Parcelable接口。Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是我们定义的一个接口,里面有两个方法,getBookList和addBook,其中getBookList是从服务器中获取图书列表,而addBook用于往图书列表中添加一本书。我们可以看到,addBook方法的参数是Book,,参数前面有一个修饰符:in,且这个修饰符是必须的,否则会报错。事实上这样的修饰符有in、out、inout,它们含义如下:

  • in:参数由客户端设置,或者理解成客户端传入参数值。
  • out:参数由服务端设置,或者理解成由服务端返回值。
  • inout:客户端服务端都可以设置,或者理解成可以双向通信。

       尽管Book类已经和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类,这就是AIDL的特殊之处。下面我们看一下系统为IBookManager.aidl生产的Binder类。在Android Studio 的app->build->generated->source->aidl->debug->包名下,有一个IBookManager.java的类,这就是我们要找的类,我们通过这个生成Binder来分析Binder的工作原理,代码如下:

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
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/wy521angel/Desktop/BinderTest/app/src/main/aidl/com/example/wy521angel/aidl/IBookManager.aidl
*/
package com.example.wy521angel.aidl;

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.wy521angel.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.wy521angel.aidl.IBookManager";

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

/**
* Cast an IBinder object into an com.example.wy521angel.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.wy521angel.aidl.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.wy521angel.aidl.IBookManager))) {
return ((com.example.wy521angel.aidl.IBookManager) iin);
}
return new com.example.wy521angel.aidl.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.wy521angel.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.wy521angel.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.wy521angel.aidl.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.wy521angel.aidl.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;
}

@Override
public java.util.List<com.example.wy521angel.aidl.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.wy521angel.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.wy521angel.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

@Override
public void addBook(com.example.wy521angel.aidl.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);
}

public java.util.List<com.example.wy521angel.aidl.Book> getBookList() throws android.os.RemoteException;

public void addBook(com.example.wy521angel.aidl.Book book) throws android.os.RemoteException;
}

       可以看到根据IBookManager.aidl系统为我们生成了上述代码这个java类,它继承了IIntenrface接口,同时它自己也还是一个接口,所有可以在Binder中传输的接口都需要继承IIntenrface接口。通过这个类,我们可以清楚地了解到Binder的工作机制。这个类的结构其实很简单,首先,它声明了两个方法getBookList和addBook,显然这就是我们在IBookManager.aidl中所声明的方法,同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这么来看,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍针对这两个类的每个方法的含义。

DESCRIPTOR

       Binder的唯一标识,一般用当前Binder的类名表示,比如本例子中的“com.example.wy521angel.aidl.IBookManager”

asInterface(android.os.IBinder obj)

       用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

asBinder

       此方法用于返回当前Binder对象。

onTransact

       这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为public Boolean onTransact (int code,android.os.Parcel data,android.os.Parcel reply,int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需要的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。

Proxy#getBookList

       这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。

Proxy#addBook

       这个方法运行在客户端,它的执行过程和getBookList是一样的,addBook没有返回值,所以它不需要从_reply中取出返回值。

       有两点还是需要额外说明一下:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。为了更好地说明Binder,下面给出一个Binder的工作机制图:

Binder的工作机制图

       从上述分析过程来看,我们完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码。系统根据AIDL文件生成Java文件的格式是固定的,我们可以抛开AIDL文件直接写一个Binder出来,接下来我们就介绍如何手动写一个Binder。还是上面的例子,但是这次我们不提供AIDL文件。参考上面系统自动生成的IBookManager.java这个类的代码,可以发现这个类是有规律的,根据它的特点,我们完全可以自己写一个和它一模一样的类出来,然后这个不借助AIDL文件的Binder就完成了。可以发现这个类主要由两部分组成,首先它本身是一个Binder的接口(继承了IInterface),其次它的内部有个Stub类,这个类就是个Binder。我们写一个Binder的服务端,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final IBookManager.Stub mBinder = new IBookManager.Stub(){

@Override
public List<Book> getBookList() throws RemoteException {
synchronized (mBookList){
return mBookList;
}
}

@Override
public void addBook(Book book) throws RemoteException {
synchronized (mBookList){
if(!mBookList.contains(book)){
mBookList.add(book);
}
}
}
};

       首先我们会实现一个创建了Stub对象并在内部实现IBookManager的接口方法,然后在Service的onBind中返回这个Stub对象。因此,从这一点来看,我们完全可以把Stub类提取出来直接作为一个独立的Binder类来实现,这样IBookManager中就只剩接口本身了,通过这种分离的方式可以让它的结构变得清晰点。

       根据上面的思想,手动实现一个Binder可以通过如下步骤来完成:

  • 声明一个AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中只有一个asBinder方法。这个接口的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IBookManager extends IInterface {

static final String DESCRIPTOR = "com.example.wy521angel.mybinder.IBookManager";

static final int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSACTION + 0;

static final int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1;

public List<Book> getBookList() throws RemoteException;

public void addBook(Book book) throws RemoteException;

}

       可以看到,在接口中声明了一个Binder描述符和另外两个id,这两个id分别表示的是getBookList和addBook方法,这段代码原本也是系统生成的,我们仿照系统生成的规则去手动书写这部分代码。如果我们有三个方法,应该怎么做呢?很显然,我们要再声明一个id,然后按照固定模式声明这个新方法即可。

  • 实现Stub类和Stub类中的Proxy代理类,这段代码我们可以自己写,但是写出来后会发现和系统自动生成的代码是一样的,因此这个Stub类我们只需要参考系统生成的代码即可,只是结构上需要做一下调整,调整后的代码如下所示:
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
public class BookManagerImpl extends Binder implements IBookManager {

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

/**
* Cast an IBinder object into an IBookManager interface,
* generating a proxy if needed.
*/
public static IBookManager asInterface(IBinder obj){
if (obj == null){
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if((iin != null) && iin instanceof IBookManager){
return (IBookManager) iin;
}
return new BookManagerImpl.Proxy(obj);
}

@Override
public List<Book> getBookList() throws RemoteException {
//待实现
return null;
}

@Override
public void addBook(Book book) throws RemoteException {
//待实现
}

@Override
public IBinder asBinder() {
return this;
}

@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case INTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:{
data.enforceInterface(DESCRIPTOR);
List<Book> result = this.getBookList();}
reply.writeNoException();
reply.writeTypedList(result);
return true;
}
case TRANSACTION_addBook:{
data.enforceInterface(DESCRIPTOR);
Book book;
if(0!=data.readInt()){
book = Book.CREATOR.createFromParcel(data);
}else {
book = null;
}
this.addBook(book);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements IBookManager{

private IBinder mRemote;

Proxy(IBinder remote){
mRemote = remote;
}

@Override
public List<Book> getBookList() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
List<Book>result;
try {
data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getBookList,data,reply,0);
reply.readException();
result = reply.createTypedArrayList(Book.CREATOR);
}finally {
reply.recycle();
data.recycle();
}
return result;
}

@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if(book != null){
data.writeInt(1);
book.writeToParcel(data,0);
}else {
data.writeInt(0);
}
mRemote.transact(TRANSACTION_addBook,data,reply,0);
reply.readException();
}finally {
reply.recycle();
data.recycle();
}

}

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

public String getInterfaceDescriptor(){
return DESCRIPTOR;
}
}
}

       通过将上述代码和系统生成的代码对比,可以发现简直是一模一样的。我们在实际开发中完全可以通过AIDL文件让系统去自动生成,手动去写的意义在于可以让我们更加理解Binder的工作原理,同时也提供了一种不通过AIDL文件来实现Binder的新方式。也就是说,AIDL文件并不是实现Binder的必需品。如果是我们手写的Binder,那么在服务端只需要创建一个BookManagerImpl的对象并在Service的onBind方法中返回即可。最后,是否手动实现Binder没有本质区别,二者的工作原理完全一样,AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具,仅此而已。

       接下来,我们介绍Binder的两个很重要的方法linkToDeath和unlinkToDeath。我们知道,Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。为了解决这个问题,Binder中提供了两个配对的方法linkToDeath和unlinkToDeath,通过 linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。那么到底如何给Binder设置死亡代理?

       首先,声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务:

1
2
3
4
5
6
7
8
9
10
11
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
//TODO: 这里重新绑定远程service
}
};

       其次,在客户端绑定远程服务成功之后,给binder设置死亡代理;

1
2
mService= IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

       其次linkToDeath的第二个参数是个标记位,我们直接设置为0即可。经过上面两个步骤,给Binder设置了死亡代理,当Binder死亡之后我们就可以收到通知了。另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

参考资料:
《Android 开发艺术探索》任玉刚 第2章 2.3.3 Binder
废墟的树 Android进程间通信之—-Aidl传递对象

Fork me on GitHub