在Linux系统中,采用的是虚拟内存地址,分为内核空间和用户空间,运行在内核空间的进程可以直接进行通信,常为系统进程。而客户程序进程则运行在用户空间,它们之间的通信则需要通过内核空间进行。这样区分主要避免客户程序直接操作内核空间,影响系统的正常运行,对系统的操作交给执行在内核空间的操作系统的程序去执行,避免造成奔溃。
用户程序进程之间的通信,Linux系统支持一下几种。
管道:管道又分匿名管道和命名管道。一端写,一端读,是一种半双工的通信,单向数据流。管道效率较低,涉及四次拷贝:用户空间->内核空间->内存->内核空间->用户空间。且双向通信需要开启两条管道,对资源占用也是比较高。
匿名管道只能在有血缘关系的进程之间进行通信,如父子进程,兄弟进程。由于匿名管道是建立在内存的缓存区上,所以容量一般不太大,且传递的字节流格式需要双方约定。
命名管道则是在实际的文件系统上实现的通信机制,遵循先进先出原则。
信号:是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。接收进程接收到该信号后去做相应的处理逻辑。
消息队列:存放内核中的消息链表,处理进程不停读取消息内容,然后处理。
共享内存:直接读写同一块内核空间,最快IPC形式。依靠同步机制互斥。
套接字:套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
Binder机制是Android跨进程通信的一种常用方式之一,例如ContentProvider、MediaServer等等底层大多数进程间通信都是基于Binder机制。
在应用层之上想使用Binder机制进行跨进程通信一般采用AIDL进行开发。
一、序列化
在开始介绍AIDL之前,先介绍一下对象序列化与反序列化。对象要持久化到内存、本地磁盘或者在网络上传输,需要进行序列化。Android序列对象可以通过
1.Serializable 接口
Serializable接口是Java所提供的,为对象提供标准的序列化和反序列化操作。通常一个对象实现Serializable接口,该对象就具有被序列化和反序列化的能力,而且几乎所有工作有系统自动完成。Serializable接口内serialVersionID可指定也可以不指定,其作用是用来判断序列化前和反序列化的类版本是否发生变化。该变量如果值不一致,表示类中某些属性或者方法发生了更改,反序列化则出问题。(静态成员变量和transient关键字标记的成员不参与序列化过程)
2.Parcelable 接口
Parcelable接口是Android所提供的,其实现相对来说比价复杂。实现该接口的类的对象就可以通过Intent和Binder进行传递。
3.两者的区别
Serializable是Java提供的接口,使用简单,但序列化与反序列化需要大量的IO操作,所以开销比较大。Parcelable是Android提供的序列化方法,使用麻烦当效率高。在Android开发中,将对象序列化到设备或者序列化后通过网络传输建议使用Serializable接口,其他情况建议是用Parcelable接口,尤其在内存的序列化上。例如Intent和Binder传输数据。
二、AIDL
在Java层,想利用Binder机制进行跨进程的通信,那就得通过AIDL(Android 接口定义语言)了,AIDL是客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口,只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL,如果是在单应用(单进程),建议使用Messager。
1、AIDL支持的数据类型
Java编程语言中的所有原语类型(如int、long、char、boolean等等);String和CharSequence;- 所有实现了
Parcelable接口的对象; - AIDL接口;
List,目前List只支持ArrayList类型,持有元素必须是以上讲到类型;Map,目前只支持HashMap类型,持有元素必须是以上讲到类型;
自定义的Parcelable对象和AIDL接口必须显示导入到AIDL文件中。
数据的走向
Parcelable对象和AIDL接口在使用前必须标明数据的走向:
in客户端流向服务端的数据out服务端流向客户端的数据inout服务端与客户端双向流向的数据
示例:
1 | void addUser(inout User user); |
2、服务端的实现
通过创建一个Demo来实践AIDL。这里通过新建一个module来表示一个新的应用进程。
2.1、定义数据对象
定义User类,实现了Parcelable接口,作为客户端和服务端传输的数据对象。
1 | public class User implements Parcelable { |
2.2、抽象服务端的服务能力
通过下面方法,建立一个UserManger.aidl文件,表示服务端能为客户端提供什么样的服务。通过Android Studio的右键来新建一个AID。

下面代码通过建立UserManager.aidl文件,为客户端提供addUser和getUser的能力。UserManager.aidl可以理解为,服务端和客户端的共同约定,两者能进行怎么样的交互。
1 | package com.gitcode.server; |
在相同目录下创建User.aidl,可以直接复制UserManager.aidl,内容修改如下。
1 | package com.gitcode.server; |
定义UserManager.aidl文件后,sync后,系统默认会生成UserManager.java文件。

UserManager.java的代码如下,为了减少篇幅,去掉了一些实现。
1 | public interface UserManager extends android.os.IInterface { |
从上文可知,UserManager本身是一个接口,并继承IInterface接口。UserManager.java声明了addUser和getUser,和在UserManager.aidl的声明是一致的。同时声明两个整型TRANSACTION_addUser和TRANSACTION_getUser,用于在transact()方法中标识调用服务端哪个方法。如果服务端和客户端在不同进程,方法调用会走transact()方法,逻辑由Stub和Proxy内部类完成。
内部类Stub的一些概念和方法含义:
DESCRIPTOR
Binder的唯一标识,一般用当前的类名全名标识。
asInterface(IBinder obj)
将服务端的Binder对象转换成客户端的AIDL接口类型的对象,如果客户端和服务端同一进程,直接返回Stub对象本身,不在同一进程,则返回由系统封装的Stub.proxy对象。
asBinder
返回当前Binder对象
onTransact(int code, Parcel data, Parcel reply, int flags)
运行在服务端Binder线程池,当客户端跨进程发起请求后,系统封装后交由此方法来处理。code表示调用服务端什么方法,上文声明的整型。data表示客户端传递过来的数据,reply为服务端对客户端的回复。
内部代理类 Poxy,客户端持有服务端的一个代理引用,通过该引用调用相关服务。
在服务端进程,通常提供多种服务,而每种服务而通过Service来体现,下面定义UserService,继承至Service。
1 | public class UserService extends Service { |
在AndroidManifest.xml文件声明Service,以两个组件形成单独的app来体现两个进程,通过AIDL进行数据交互。在客户端通过bindService()来启动该服务。
1 | <service android:name="com.gitcode.server.UserService" |
3、客户端的实现
客户端主要是通过共同的约定(UserManger.aidl)向服务端进行请求,服务端响应客户端的请求。为了提高效率和减少出错,通过拷贝来实现客户端的AIDL文件。将服务端的aidl整个文件拷贝到客户端的main目录下,不做任何修改。

在客户端建立与服务端User类同包的目录,并将User类拷贝过来,不做任何修改。

在Activity中绑定服务端的Service,绑定成功后进行数据交互。
1 | public class MainActivity extends AppCompatActivity { |
运行效果:
客户端:

服务端:

4、小结
客户端调用服务的方法,被调用的方法运行在服务端的的Binder线程池,同时客户端会被挂起,如果服务端方法执行耗时操作,就会导致客户端ANR,所以不要在客户端主线程访问远程服务方法。同时服务端不应该自己新建新建线程运行服务方法,因为方法会交由线程池处理,同时对数据也要做好并发访问处理。
AIDL可以说为应用层开发提供了封装,不用过多的了解Binder的机制,通过生成的UserManager.java,初步可以了解Binder的IPC机制。使用AIDL在进程之间进行数据通信,更注重的是细节和业务的实现。

三、Binder
Binder进制是Android系统提供的一种IPC机制。由于Android是基于Linux内核,之所有不使用原有的 IPC机制,是因为使用Binder机制,能从性能、稳定性、安全性带来更好的效果。例如,Socket是一套通用的接口,传输速率低下,适合网络传输这种情况,而管道和消息队列需要数据的两次拷贝,共享内容难以管控等。而Binder对数据只需要一次拷贝,使用C/S架构,职责明确,容易维护和使用。
通过下图可以了解到,Binder机制通过内存映射实现跨进程通信,Binder在IPC机制只是作为一个数据的载体,当进程A向虚拟内存空间中写入数据,数据会被实时反馈到进程B的虚拟内存空间。整个发送数据的过程,只从用户空间拷贝一次到虚拟内存空间。

在Binder机制中,主要涉及到Client、Server、ServiceManger,三者通过Binder进行跨进程通信,支撑着Android这个大网络。它们的关系如下图。

服务端
服务端,表示提供服务能力的一方,服务端通过向ServiceManger中注册一些Service,对外告知其可提供的服务。例如上文AIDL中,会注册UserService,并为客户端提供添加User和获取User的操作。
客户端
对服务端提供的服务进行业务逻辑操作。通过服务端提供的Service的名称在ServiceManger查询对应的Service。
ServiceManager
ServiceManger集中管理所有Service,和施加权限。为服务端提供注册Service,为客户端提供查找Service能力,类似网络编程中DNS的作用。
图表示三者的C/S架构,例如Client查询向ServiceManger查询Service时,Client就是客户端,而ServiceManger就是服务端。而虚线则表示两者之间通过Binder进行进程间的通信,因此通过了解一条虚线的流程,就可以知道Binder的机制。
1、Service的注册过程
我们常通过getSystemService()函数来获取系统的相关服务,例如ActivityManagerService,那么这些Service从哪里来呢?就是有些服务进程向ServiceManager注册自己的Service,表示自己能对客户端提供什么样的服务。通过了解Service的注册过程,来学习Binder机制的工作原理。

BpServcieXXX和BnServcieXXX是客户端与服务端进程业务层辑实现的封装。BnServiceManger派生自IServiceManager和BBinder类,通过调用BBinder对象的transcat函数的参与通信。是一个虚类,具体的功能需要子类去实现,存在服务端。BpServiceManager派生自BpInterfacle和``BpRefBase接口,其中BpRefBase的mRemote函数返回的就是BpBinder`对象,存在客户端。
BBinder和BpBinder都是IBinder的子类。两者相互对应,客户端通过BpBinder对象与服务端进行通信,而客户端通过BBinder与客户端进行通信。
通过IServiceManger与IBinder架构将业务逻辑与IPC进行分离,下层Binder机制完全可以切换成Linux中的其他通信机制。
1.1 ProcessState
每个进程通过单例模式创建了唯一的ProcessState对象,在其构造器中,通过open_driver()方法打开了/dev/hwbinder设备,相当于Server进程打开了与内核的Binder驱动交互的通道,并设置最大支持线程数为15。binder设备是Android在内核中为完成进程间通信而专门设置的一个虚拟设备。
1.2 注册相关Service
BpServiceManger对象实现对IServiceManger的业务函数,又有BpBinder作为通信代表,下面分析一下注册的过程。
将字符串名字和Service对象作为参数传到BpServiceManger对象的addService()函数,该方法将参数数据打包后传递给BpBidner的transact()函数。业务层的逻辑到此就结束,主要作用是将请求信息打包交给通信层去处理。
在BpBinder的transact()函数调用了IPCThreadState对象的transact()函数,所以说BpBinder本身没有参与Binder设备的交互。每个线程都有一个IPCThreadState对象,其拥有一个mOut、mIn的缓冲区,mOut用来存储转发Binder设备的数据,而mIn用来接收Binder设备的数据。通过ioctl方式与Binder设备进行交互。
1.3 小结
通过上文Service的注册过程,分析了Binder的机制。Binder只是通信机制,业务可以基于Binder机制,也可以基于其他IPC方式的机制,也就是上文为啥有BpServiceManger和BpBinder。Binder之所以复杂,是Android通过层层的封装,巧妙的将业务与通信融合在一起。主要还是设计理想很牛逼。

2、ServiceManger
通过1小节的分析,是否应该也有一个类继承自BnServiceManger来处理远方请求呢?
很可惜的是在服务端并没有BnServiceManger子类来响应远程客户端的请求,而是交给了ServiceManger来处理。
2.1 成为Service管理中心
ServiceManger通过binder_open函数打开binder设备,并映射内存。通过handler等于0标识自己,让自己成为管理中心,所有service向ServiceManger注册时,都是通过handle标识为的0的BpBinder找到ServiceManger对应的BBinder,ServiceManager会保存要注册的Service的相关信息,方便Client查找。并不是所有的Service都可以在ServiceManger注册,如果Server进程的权限不够root或system,那么需要在allowed添加相应的项。
2.2 ServiceManger集中管理带来的好处
- 统一管理,施加管控权
- 通知字符串名称查找Service
- Server进程生命无常,通过
ServiceManger,Client可以实时知道Server进程的最行动态。
3、Client
Client想要使用Server进程提供的Service,又该进行哪些步骤呢?
3.1 查询ServiceManger
Client想要得到某个Service的信息,就得与ServiceManager打交道,通过调用getService()方法来获取对应Service信息。Client通过服务名称向ServiceManger查询对应的Service。如果Service未注册,则循环等待直到该Service注册;如果已注册,则会对应封装了一个能与远程Service通信的BpBinder的BpXXXService,通过该Service,Client客户调用相关业务逻辑函数。
3.2 请求信息的处理
Client调用的业务函数,莫非就是将请求参数打包发送给Binder驱动,BpBinder通过handler的值找到对应端的Service来处理。
在1.4小节中,说到IPCThreadState对象,在其executeCommand函数中,通过调用实现了BnServiceXXX的对象onTransact函数,直接定位到业务层。这就是在AIDL中,为什么在onTransact()函数中处理响应数据。
四、总结
通过对Binder机制的学习,了解Android是如何通过层层封装将Binder机制集成要应用程序,对Binder机制有一个较深入的理解。可以通过第Java层AIDL的使用,加深对Binder机制的理解。
个人水平有限,有误请帮忙勘正,谢谢大佬。喜欢就帮忙点个赞呗。
