Android 进阶之 Binder 浅解

什么是 Binder

Binder 是 Android 中一种跨进程通信(IPC)方式。 #### Binder 的原理 Binder通信采用C/S架构,从组件视角来说,包含 Client、Server、ServiceManager 以及 binder 驱动,其中 ServiceManager 用于管理系统中的各种服务。架构图如下所示:

  • 此处的 Service Manager 是指 Native 层的 ServiceManager(C++)
  • 注册服务(addService):Server 进程要先注册 Service 到 ServiceManager。该过程:Server 是客户端,ServiceManager 是服务端。
  • 获取服务(getService):Client 进程使用某个 Service 前,须先向 ServiceManager 中获取相应的 Service。该过程:Client 是客户端,ServiceManager 是服务端。
  • 使用服务:Client 根据得到的 Service 信息建立与 Service 所在的 Server 进程通信的通路,然后就可以直接与 Service 交互。该过程:Client 是客户端,Server 是服务端。

Binder 的结构

在学习 Binder 的结构的时候,可以利用 AIDL 去生成 Binder,这里我们首先需要创建一个实现了 Parcelable 接口的 Book 类,然后在 main 文件夹下创建 AIDL 文件夹,分别创建 Book.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
//Book.java
package com.rookieyang.aidltest;

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

/**
* Created by firstdream on 2017/9/4.
*/

public class Book implements Parcelable {

private int mBookId;
private String mBookName;

public Book(int mBookId, String mBookName) {
this.mBookId = mBookId;
this.mBookName = mBookName;
}

@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(mBookId);
parcel.writeString(mBookName);
}

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

public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel parcel) {
Book book = new Book(parcel.readInt(), parcel.readString());
return book;
}

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

// Book.aidl
package com.rookieyang.aidltest;

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

parcelable Book;

// IBookManager.aidl
package com.rookieyang.aidltest;

//需要导入 Book 类
import com.rookieyang.aidltest.Book;

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

interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book> getBookList();
void addBook(in Book book);
}

在经过上述的过程之后,点击编译即可生成 Binder 文件,以 IBookManager.aidl 生成的 IBookManager.java 为例, IBookManager.java 中定义了一个继承自 IInterface 的 IBookManager 接口,在 IBookManager 内包含了继承自 Binder 实现了 IBookManager 接口的内部类 Stub 和 实现了 IBookManager 接口的 Stub 的内部类 Proxy。整体结构如下图所示: 下面针对 IBookManager.java 进行具体的分析:

  • IBookManager 接口,这个接口与在 IBookManager.aidl 中一致
    • getBookList()
    • addBook(Book book)
1
2
3
4
5
6
7
8
9
10
public interface IBookManager extends IInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public List<Book> getBookList()
throws RemoteException;

public void addBook(Book book) throws RemoteException;
}
  • Stub 类
    • DESCRIPTOR
    • Binder 的唯一标识,一般用当前 Binder 的类名表示
    • TRANSACTION_getBookList 和 TRANSACTION_addBook
    • 两个id用于标识在transact过程中客户端所请求的到底是哪个方法
    • asInterface(IBinder obj)
    • 用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。
    • asBinder
    • 此方法用于返回当前 Binder 对象。
    • onTransact
    • 这个方法运行在服务端中的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
    • 服务端通过 code 可以确定客户端所请求的目标方法是什么,接着从 data 中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向 reply 中写入返回值(如果目标方法有返回值的话),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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public static abstract class Stub extends Binder implements
IBookManager {

private static final String DESCRIPTOR = "com.rookieyang.binderdemo.IBookManager";

/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
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 IBookManager.Stub.Proxy(obj);
}

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

@Override
public 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 _arg0;
if ((0 != data.readInt())) {
//反序列化
_arg0 = Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
}
  • Proxy 类
    • getBookList
    • 这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型 Parcel 对象 _data 、输出型 Parcel 对象 _reply 和返回值对象 List;然后把该方法的参数信息写入 _data 中(如果有参数的话);接着调用 transact 方法来发起 RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果;最后返回 _reply 中的数据。
    • addBook
    • 这个方法运行在客户端,它的执行过程和 getBookList 是一样的,addBook 没有返回值,所以它不需要从 _reply 中取出返回值。
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
private static class Proxy implements IBookManager {

private IBinder mRemote;

Proxy(IBinder remote) {
mRemote = remote;
}

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

public String getInterfaceDescriptor() {
return DESCRIPTOR;
}

/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public List<Book> getBookList()
throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
java.util.List<Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.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(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

Binder 的工作机制

> 以上述为例,客户端调用 getBookList 方法时,在方法内部定义了 Parcel 对象,然后通过调用 transact 发起远程请求,服务器端接收到后调用 onTransact 方法,根据传递过来的 Code 进行相应的处理,将结果写入 Parcel 对象当中,处理完成之后在 客户端的 transact 将结果取出来并返回。

Binder 使用的一些注意点

Binder 一般和 Service 配合使用,作为 bindService 执行时的返回,最后在 ServiceConnect 当中获取返回的 Binder:

1
2
3
4
5
6
7
8
9
10
11
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mCalcAidl = ICalcAIDL.Stub.asInterface(iBinder);
}

@Override
public void onServiceDisconnected(ComponentName componentName) {
mCalcAidl = null;
}
};
> 不同进程时返回的 iBinder 是 ICalcAIDL.Stub.Proxy,而相同进程中则是 ICalcAIDL.Stub。是返回 Stub 还是返回 Proxy 在底层已经做了判断,不是在 onServiceConnected 方法中进行的判断

在服务中使用时,定义的 AIDL 文件包名必须相同,不然将无法找到对应的 AIDL,从而导致无法进行通信,并且使用时需要通过 ICalcAIDL.Stub 实现在 AIDL 中定义的接口,从而为客户端提供服务。

1
2
3
4
5
6
7
8
9
10
11
private final ICalcAIDL.Stub mBinder = new Stub() {
@Override
public int add(int x, int y) throws RemoteException {
return x + y;
}

@Override
public int min(int x, int y) throws RemoteException {
return x - y;
}
};

总结

粗略的说,Client 通过 Binder 当中的 Proxy 进行了 IPC 的请求,而 Server 则通过 Stub 当中的 onTransact 对跨进程请求进行处理,之所以能进行跨进程请求的原因在于底层能够通过可序列化的数据,上述的 Book 类实现了 Parcelable 接口,而基本类型是被看作可序列化的,所以这些数据在底层能够进行传输,自然就可以通过 Binder 完成 IPC。


参考

  1. Android 开发艺术探索-IPC 机制
  2. Android aidl Binder框架浅析
  3. Binder学习指南