大家好,好久不见,距离上篇文章已经有35天之久了,因为身体不舒服害了一场病,不过现在已经好多了;另外这个月是考试月,当然得花多点时间复习功课了;再者这段时间依旧在看书,同时也在研究Android源码,准备了不少干货想与大家一起分享。7月刚到,该放假的也都差不多放假了,该实习的也已经在实习了,而我。。。还是准备秋招吧!多看书多打码多提升自己的眼界。
前言
今天想要和大家一起分享的是Android中的Binder机制,讲真这绝对是Android中很深奥的一个点,如果能够彻底弄懂它,这对初级程序员来说绝对会是一件具有里程碑意义的事件,当然我也研究了许久,终于琢磨出点所以然,所以就拿出来和大家一起分享分享。另外这篇文章将会通过一个小实例来讲解Binder,大家可以点击这里,也欢迎大家fork和star。话不多说让我们开始吧!
IPC
为了弄懂IPC的来龙去脉,我将从以下三个方面为大家来讲解,希望对大家理解IPC会有帮助
什么是IPC
IPC是Inter Process Communication
的缩写,其意思就是进程间的通信,也就是两个进程之间的通信过程。我们都知道在Android系统中,每个应用都运行在一个进程上,具有自己的DVM实例,而且进程之间是相互隔离的,也就是说各个进程之间的数据是互相独立,互不影响的,而如果一个进程崩溃了,也不会影响到另一个进程。
采取这样的设计是有一定道理的,例如这样的前提下将互相不影响的系统功能分拆到不同的进程里面去,有助于提升系统的稳定性,毕竟我们都不想自己的应用进程崩溃会导致整个手机系统的崩溃。
进程之间隔离是不错的选择,可是如果进程之间想要互相通信,进行数据交互的时候那该怎么办呢?例如我们在自己的应用中想要访问手机通讯录中的联系人,很显然这是两个不同的进程,如果Android没有提供一种进程之间交流的机制,那么这种功能将无法实现。
不过由于Android系统使用的是Linux内核,而在Linux系统中进程之间的交互是有一套机制的,所以Android也借鉴了其中的一些机制,从而形成了Android的IPC机制。
上面只是粗略的讲解了IPC是啥,关于它的使用和原理我将一一为大家呈上。
为什么要用IPC
上一点中我举了访问手机通讯录的例子。但你可能觉得我不需要用到这种功能,那么我就不用管IPC啦!其实不然,IPC在我们的应用开发过程中随处可见,下面我将举一个例子来说明他的重要性。
我们在MainActivity中修改一个静态变量,接着在另一个进程的SecondActivity中去访问该变量,看看能否读取已经修改过的变量。
1、新建一个Student类,并声明一个静态变量
1 | public class Student { |
2、在MainActivity的onCreate方法中修改name的值,并打印log1
2
3
4
5
6
7
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Student.name = "JACK";
Log.d("MainActivity:Sname=", Student.name);
}
3、将SecondActivity设置为新进程,并在其onCreate方法中访问name1
2
3
4
5<!-- 在清单文件中通过android:process属性为SecondActivity指定特定的进程:com.bob.aidltest:second -->
<activity
android:name=".SecondActivity"
android:process=":second">
</activity>
1 | public class SecondActivity extends AppCompatActivity { |
4、通过log,可以看到在MainActivity中修改了name的值,但是在SecondActivity中却无法读取修改后的值
通过以上的实验,大家应该明白了一点:在不同的进程之间访问同一个静态变量是行不通的。其原因是:每一个进程都分配有一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机上访问同一个对象会产生多个副本。例如我们在MainActivity中访问的name的值只会影响当前进程,而对其他进程不会造成影响,所以在SecondActivity中访问name时依旧只能访问自己进程中的副本。
Android中解决IPC的方法
上面也讲到,为了解决这些跨进程的问题,Android沿用了一些Linux的进程管理机制,使得进程之间能够进行交互,下面我将列出一些常见的IPC方式,需要指出的是本文主要讲解Binder机制,所以会注重讲解AIDL,其他方式请读者自行查阅相关资料。
名称 | 特点 | 使用场景 |
---|---|---|
Bundle | 只能传输实现了Serializable或者Parcelable接口或者一些Android支持的特殊对象 | 适合用于四大组件之间的进程交互 |
文件 | 不能做到进程间的即时通信,并且不适合用于高并发的场景 | 适合用于SharedPreference以及IO操作 |
ContentProvider | 可以访问较多的数据,支持一对多的高并发访问,因为ContentProvider已经自动做好了关于并发的机制 | 适合用于一对多的数据共享并且需要对数据进行频繁的CRUD操作 |
Socket | 通过网络传输字节流,支持一对多的实时通信,但是实现起来比较复杂 | 适合用于网络数据共享 |
Messenger | 底层原理是AIDL,只是对其做了封装,但是不能很好的处理高并发的场景,并且传输的数据只能支持Bundle类型 | 低并发的一对多的即时通信 |
AIDL | 功能强大,使用Binder机制(接下来会讲解),支持一对多的高并发实时通信,但是需要处理好线程同步 | 一对多并且有远程进程通信的场景 |
Binder
终于来到这篇文章的重头戏了,上面讲到Android解决IPC的方法中有一种是AIDL,它使用的原理就是Binder,只有理解了Binder,我们才算是理解了Android跨进程通信的原理。在这里我会带大家看看Android中有哪一些重要的地方使用到了Binder,接着我们会通过一个实例来了解如何使用Binder,最后我们会分析Binder的源码来理解他的工作流程。
Binder在Android中的运用
说起Binder在Android的使用场景,可以说是无处不在,我列出一些最常见的场景:
- 四大组件的生命周期都是使用Binder机制进行管理的
- View的工作原理也使用了Binder
- WindowManager的工作机制同样使用了Binder
以上三个方面只是最常见的场景,但是却几乎包括了我们开发的整个流程。我们开发的应用都离不开四大组件,而四大组件也正是依靠Binder机制运行的;对于我们最常见的View,他是如何显示的,View又是如何响应我们的动作的,这其中也用到了Binder(关于这些内容我会在后续的文章中为大家分析)。可以说了解Binder对于我们的开发是很有帮助的,那接下来我们就来看看我们该如何使用Binder进行进程间的通信吧!
如何使用Binder
现在我们需要实现这样的功能:客户端与服务端位于不同的进程,客户端需要向服务端添加学生,同时客户端还可以向服务端发起查询学生列表的请求。
1、创建Student.java,Student.aidl,IStudentManager.aidl
- Student.java
1 | package com.bob.aidltest.aidl; |
- Student.aidl
1 | // Student1.aidl |
- IStudentManager.aidl
1 | // IStudentManager.aidl |
创建完毕之后手动编译项目(Build-->ReBuild Project
),接着就会在app/build/generated/source/aidl/debug/com/bob/aidltest/aidl/IStudentManager.java
中看到自动生成的IStudentManager
接口,如下图:
2、分析IStudentManager.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
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
136public interface IStudentManager extends android.os.IInterface
{
/** 内部类Stub,继承自Binder并且实现了IStudentManager接口,因此他也是一个Binder对象,这个内部类是需要在服务端手动实现的,并且会通过onBind方法返回给客户端 */
public static abstract class Stub extends android.os.Binder implements com.bob.aidltest.aidl.IStudentManager
{
private static final java.lang.String DESCRIPTOR = "com.bob.aidltest.aidl.IStudentManager";
/** 构造方法 */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* 将服务端的Binder对象转换为客户端的所需的AIDL接口类型的对象,客户端拿到这个对象就可以通过这个对象远程访问服务端的方法
*/
public static com.bob.aidltest.aidl.IStudentManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.bob.aidltest.aidl.IStudentManager))) {
return ((com.bob.aidltest.aidl.IStudentManager)iin);
}
return new com.bob.aidltest.aidl.IStudentManager.Stub.Proxy(obj);
}
public android.os.IBinder asBinder()
{
return this;
}
/**
* 运行在服务端进程的Binder线程池中;当客户端进程发起远程请求时,远程请求会要求系统底层执行回调该方法
* @param code 客户端进程请求方法标识符。服务端进程会根据该标识确定所请求的目标方法
* @param data 目标方法的参数,他是客户端进程传进来的,当我们调用addStudent(Student student)方法时,参数就是Student对象
* @param reply 目标方法执行后的结果,将会返回给客户端,例如当我们调用getStudentList,返回的就是一个Student的列表
*/
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_getStudentList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.bob.aidltest.aidl.Student> _result = this.getStudentList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addStudent:
{
data.enforceInterface(DESCRIPTOR);
com.bob.aidltest.aidl.Student _arg0;
if ((0!=data.readInt())) {
_arg0 = com.bob.aidltest.aidl.Student.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addStudent(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
/**
* 代理的内部类,他实现了IStudentManager接口,这个代理类就是服务端返回给客户端的AIDL接口对象,客户端可以通过这个代理类访问服务端的方法
*/
private static class Proxy implements com.bob.aidltest.aidl.IStudentManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
public java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.bob.aidltest.aidl.Student> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.bob.aidltest.aidl.Student.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public void addStudent(com.bob.aidltest.aidl.Student student) 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 ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getStudentList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException;
public void addStudent(com.bob.aidltest.aidl.Student student) throws android.os.RemoteException;
}
可能看了上面的注释大家还是一头雾水,那就先看看这个类的结构图吧:
有关这个类的细节我们待会讲,现在只需要知道我们需要在服务端手动实现Proxy类并实现其中的方法。
创建StudentManagerService.java,并为其指定进程
1 | /** |
在清单文件中指定服务的进程
1 | <service |
可以看到这个服务类跟普通的服务类相差并不大,唯一的区别在于它创建了一个IStudentManager.Stub的匿名内部类并且实现了其中的方法,在onBind方法中将这个IBinder对象返回给客户端。这里需要说明一下:Binder是实现了IBinder接口的,所以他同时也是一个IBinder对象。
在客户端愉快的绑定Service吧!
1 | public class MainActivity extends AppCompatActivity { |
可以看到我们在客户端只需要绑定远程的服务端,服务端就会返回一个IBinder对象,接着我们需要调用IStudentManager.Stub.asInterface()方法,将这个IBinder对象转换为我们客户端可用的AIDL接口对象,拿到这个对象之后我们就可以远程调用服务端的方法了。是不是很容易?
但是需要注意的一点是为了模拟耗时操作,我们在服务端的getStudentList的方法中使用休眠以模拟耗时操作,所以客户端在调用该方法时不能直接在主线程中调用,而是应该开启一个子线程,在子线程中调用这个耗时的操作。
看看效果
首先我们获取学生列表,接着连续添加4个学生,再次查看学生列表,最终的结果如下图,可以看到我们已经实现了两个进程之间的交互,接下来我们将分析Binder的原理。
Binder的原理
进程的机制
首先我们需要了解进程之间为什么不能直接进行通信,以下是两个进程的示意图:
从上面的图我们可以得到以下几点:
- 一个进程空间分为:用户态和内核态,即把进程内用户和内核隔离开来
- 进程之间,由于Android系统为每个进程分配了一个独立的虚拟机,用户空间和内核空间的数据不可交互
- Binder作为进程间的介质,充当了中介,使得进程间的内核态可以通过Binder进行数据交互
IPC交互示意图
图中总共有四个元素,分别是充当客户端的Activity,服务端的StudentManagerService,充当服务管理者的IStudentManager以及充当访问介质的Binder驱动。他们的职责如下:
- StudentManagerService: 服务提供者,这里面会有许多我们常用的服务,在本例中提供的服务就是添加学生以及获取学生列表。而在系统中则包括有ActivityService 、 WindowMananger等服务,这些系统服务提供的功能,对四大组件以及Window的工作提供的保障。
- Activity: 服务调用者,一般就是我们的应用,在这里我们通过调用StudentManagerService的服务来完成工作。
- IStudentManager: 他是负责管理服务的,在其内部通过map集合来存储Service与Binder的映射关系,这样客户端在向其请求服务的时候就能够返回特定的Binder。
- Binder驱动: 他是IStudentManager连接各种Service的桥梁,同时也是客户端与服务端交流的桥梁。
总结起来说,应用程序(Activity
)首先向IStudentManager
发送请求StudentManagerService
的服务,IStudentManager
查看已经注册在里面的服务的列表,找到相应的服务后,通过Binder驱动
将其中的Binder
对象返回给客户端,从而完成对服务的请求。
源码分析
我们主要分析的就是IStudentManager
这个类,从上面得到讲解我们已经知道它包含了两个类:Stub和Proxy。先来看看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
34
35
36
37
38
39
40
41//Proxy.java
public java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.bob.aidltest.aidl.Student> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.bob.aidltest.aidl.Student.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public void addStudent(com.bob.aidltest.aidl.Student student) 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 ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
上面截取了Proxy的两个方法,其中Proxy是运行在客户端的,他是用服务端返回来的Binder对象调用了public static IStudentManager asInterface(IBinder obj)
方法返回来的。
既然Proxy运行在客户端,那么客户端也是通过Proxy来调用远程服务端的方法的,也就是说我们将调用方法需要用到的参数传递给Proxy,接着由Proxy来访问服务端,所以我们能够看到,Proxy将我们的参数写进了_data,而_reply则代表从服务端返回来的结果。
从代码中我们还看到客户端在将数据传递给服务端之后就处于阻塞状态,直到服务端返回结果,所以如果调用的服务端方法是一个耗时方法,那么我们就需要在子线程中进行工作了。
数据准备好之后当然是需要传递了,可以看到Proxy通过transact方法讲数据传递出去了,接下来就来看transact方法:
1 | //Binder#transact |
可以看到transact方法实际上调用了Binder的onTransact,而这里的Binder就是指Stub了,我们看一下Stub的定义:1
public static abstract class Stub extends android.os.Binder implements com.bob.aidltest.aidl.IStudentManager
可以看到Stub确实继承了Binder并且也实现了IStudentManager接口,接下来我们继续看Stub中的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
34public 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_getStudentList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.bob.aidltest.aidl.Student> _result = this.getStudentList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addStudent:
{
data.enforceInterface(DESCRIPTOR);
com.bob.aidltest.aidl.Student _arg0;
if ((0!=data.readInt())) {
_arg0 = com.bob.aidltest.aidl.Student.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addStudent(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
可以看到,服务端通过客户端传递过来的code常量来判断客户端需要调用的是哪个方法,接着就执行该方法,执行完之后如果有数据返回则将结果写入reply,接着Proxy就可以收到结果了。而整个通信过程也就结束了。
最后我借用Carson_Ho的一张流程图来描述这个完整的流程:
后记
Binder机制在Android中的作用举足轻重,本文只是通过Java代码分析其工作流程,由于博主不擅长C/C++,所以无法研究native,不过本文依旧完整的阐述了Binder的整个运行机制,希望通过本文大家能够对Binder机制有更深刻的理解,关于Binder在Android系统中的更多运用我会在后续的文章中为大家奉上,希望大家多多支持。