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;
}
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(mBookId);
parcel.writeString(mBookName);
}
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
public Book createFromParcel(Parcel parcel) {
Book book = new Book(parcel.readInt(), parcel.readString());
return book;
}
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 | public interface IBookManager extends IInterface { |
- 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 | public static abstract class Stub extends Binder implements |
- Proxy 类
- getBookList
- 这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型 Parcel 对象 _data 、输出型 Parcel 对象 _reply 和返回值对象 List;然后把该方法的参数信息写入 _data 中(如果有参数的话);接着调用 transact 方法来发起 RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果;最后返回 _reply 中的数据。
- addBook
- 这个方法运行在客户端,它的执行过程和 getBookList 是一样的,addBook 没有返回值,所以它不需要从 _reply 中取出返回值。
1 | private static class Proxy implements IBookManager { |
Binder 的工作机制
> 以上述为例,客户端调用 getBookList 方法时,在方法内部定义了 Parcel 对象,然后通过调用 transact 发起远程请求,服务器端接收到后调用 onTransact 方法,根据传递过来的 Code 进行相应的处理,将结果写入 Parcel 对象当中,处理完成之后在 客户端的 transact 将结果取出来并返回。
Binder 使用的一些注意点
Binder 一般和 Service 配合使用,作为 bindService 执行时的返回,最后在 ServiceConnect 当中获取返回的 Binder: 1
2
3
4
5
6
7
8
9
10
11private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mCalcAidl = ICalcAIDL.Stub.asInterface(iBinder);
}
public void onServiceDisconnected(ComponentName componentName) {
mCalcAidl = null;
}
};
在服务中使用时,定义的 AIDL 文件包名必须相同,不然将无法找到对应的 AIDL,从而导致无法进行通信,并且使用时需要通过 ICalcAIDL.Stub 实现在 AIDL 中定义的接口,从而为客户端提供服务。
1 | private final ICalcAIDL.Stub mBinder = new Stub() { |
总结
粗略的说,Client 通过 Binder 当中的 Proxy 进行了 IPC 的请求,而 Server 则通过 Stub 当中的 onTransact 对跨进程请求进行处理,之所以能进行跨进程请求的原因在于底层能够通过可序列化的数据,上述的 Book 类实现了 Parcelable 接口,而基本类型是被看作可序列化的,所以这些数据在底层能够进行传输,自然就可以通过 Binder 完成 IPC。
参考
- Android 开发艺术探索-IPC 机制
- Android aidl Binder框架浅析
- Binder学习指南