跨进程通信机制
1. Binder
1.1. Binder定义
Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。
1.2. Binder架构
Binder跨进程通信机制模型基于Client - Server
模式,其在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构。而在Native层,Binder 则以 ioctl 的方式与 Binder 驱动进行通讯,其架构图如下:

Binder架构中的组件主要包括 Client、 Server、 ServiceManager 以及 Binder 驱动四种,各角色作用如下:
角色 | 作用 |
---|---|
Client | Android客户端,使用服务的进程 |
Server | 服务器端,提供服务的进程 |
ServiceManager | 管理Service的注册与查询,将字符形式的Binder名字转化成Client中对该Binder的代理 |
Binder驱动 | 负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持 |
1.3. Binder机制
Binder机制如下图所示:

- 首先需要注册
Server
端,只有注册了Server
端,Client
端才有通讯的目标,Server
端通过ServiceManager
注册服务,注册的过程就是向 Binder 驱动的全局链表binder_procs
中插入Server
端的信息(binder_proc
是结构体,每个binder_proc
结构体中都有todo
任务队列),然后向ServiceManager
的svcinfo
列表中缓存注册的服务; - 在
Server
端注册完成后,Client
端就可以与其进行通讯了。在通讯之前Client
端需要先获取服务,即拿到服务的代理,也可以理解为引用。获取Server
端的方式就是通过ServiceManager
到svcinfo
列表中查询所需服务并返回Server
端的代理,svcinfo
列表就是所有已注册服务的通讯录,保存了所有已注册服务的信息; - 在有了
Server
端的代理之后即可向Server
端发送请求。Client
端通过BinderProxy
将请求参数发送给ServiceManager
,通过共享内存的方式使用内核方法copy_from_user()
将请求参数先拷贝到内核空间,此时Client
端进入等待状态,然后 Binder 驱动向Server
端的todo
队列里面插入一条事务,执行完成之后通过copy_to_user()
将内核的执行结果拷贝到用户空间(这里只执行拷贝命令,并没有拷贝数据),唤醒等待的客户端并把结果返回,即完成了一次通讯。
1.4. Binder驱动
Linux 内核的运行空间与用户程序的运行空间是相互隔离的,即使用户程序崩溃了,内核也不受影响。内核空间与用户空间的交互过程如下图所示:

内核空间可以执行任意命令,调用系统的一切资源,而用户空间只能执行简单的运算,不能直接调用系统资源。虽然从逻辑上抽离出内核空间和用户空间,但是不可避免的的是,总有那么一些用户空间需要访问内核的资源。而用户空间访问内核空间的唯一方式就是系统调用(System
call
),通过这个统一的入口接口,所有的资源访问都是在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,就称进程处于内核运行态(或简称为内核态),此时处理器处于特权级最高的(0级)内核代码中执行,处理器在特权等级高的时候才能执行特权CPU指令。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态),即此时处理器在特权级最低的(3级)用户代码中运行。
通过系统调用,用户空间可以访问内核空间,而当一个用户空间想与另外一个用户空间进行通信时就需要让操作系统内核添加支持,如Socket、管道等都是内核支持的。但 Binder 并不是 Linux 内核的一部分,而是 Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)。Binder是具有独立功能的程序,可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。因此,通过添加一个运行在内核空间的内核模块,作为用户进程之间通信的桥梁,从而实现进程间通信。在 Android 系统中,这个运行在内核空间的,负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动;
在上图中,用户空间中 binder_open()
, binder_mmap()
, binder_ioctl()
方法通过 System
call
来调用内核空间 Binder 驱动中相对应的方法。内核空间与用户空间的共享内存通过 copy_from_user()
和 copy_to_user()
内核方法来完成用户空间与内核空间的数据传输。此外,Binder 驱动中有一个全局的 binder_procs
链表,用来保存Server
端的进程信息。
1.5. Binder进程与线程

对于底层Binder驱动而言,通过 binder_procs
链表记录所有创建的 binder_proc
结构体,Binder 驱动中的每一个 binder_proc
结构体都与用户空间中的一个用 Binder 通信的进程相对应,且每个进程有且只有一个 ProcessState
对象,通过单例模式实现。在每个进程中可以有多个线程,每个线程对应一个 IPCThreadState
对象,IPCThreadState
对象也是通过单例模式实现,在 Binder 驱动层也有与之相对应的结构,即Binder_thread
结构体。在 binder_proc
结构体中通过成员变量 rb_root threads
来记录当前进程内所有的 binder_thread
。
每个 Server 进程在启动时创建一个 Binder 线程池,并向其中注册一个 Binder 线程,之后 Server 进程也可以向 binder 线程池注册新的线程。当 Binder 驱动在探测到没有空闲 binder 线程时,也可以主动向 Server 进程池注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 Binder 线程。对于所有 Client
端进程的 Binder 请求都是交由 Server
端进程的 Binder 线程来处理的。
1.6. ServiceManager启动
ServiceManager提供注册服务与查询服务的功能,其启动如下图所示:

ServiceManager 分为 framework 层和 native 层,framework 层只是对 native 层进行了封装方便调用,图上展示的是 native 层的 ServiceManager 的启动过程。
ServiceManager 的启动是系统在开机时,init
进程解析 init.rc
文件并调用 service_manager.c
中的 main()
方法启动的。 native 层的 binder.c
封装了一些与 Binder 驱动交互的方法。
ServiceManager 的启动分为三步:首先打开驱动创建全局链表 binder_procs
;然后将自己当前进程信息保存到 binder_procs
链表;最后开启 loop
不断的处理共享内存中的数据,并处理 BR_xxx
命令(ioctl
的命令)。
1.7. ServiceManager 注册服务
Service组件运行在Server进程中,首先要将Service注册到Service Manager中,再启动一个Binder线程池来等待和处理Client端的通信请求。
注册过程(addService)的核心工作是在服务所在进程创建binder_node
,在ServiceManager进程创建binder_ref
。
以Media服务为例,注册的过程涉及到MediaPlayerService(作为Client进程)和Service Manager(作为Service进程),通信流程图如下所示:

- 注册
MediaPlayerService
服务端,通过 ServiceManager 的addService()
方法来注册服务; - 首先 ServiceManager 向 Binder 驱动发送
BC_TRANSACTION
命令,并携带ADD_SERVICE_TRANSACTION
命令,同时注册服务的线程进入等待状态waitForResponse()
。 Binder 驱动收到请求命令向 ServiceManager 的todo
队列里面添加一条注册服务的事务。事务的任务就是创建服务端进程binder_node
信息并插入到binder_procs
链表中; - 事务处理完之后发送
BR_TRANSACTION
命令,ServiceManager 收到命令后向svcinfo
列表中添加已经注册的服务。最后发送BR_REPLY
命令唤醒等待的线程,通知注册成功。
1.8. ServiceManager 获取服务
请求服务过程,就是向serviceManager进程查询指定服务,当执行binder_transaction()
时,会区分请求服务所属进程情况:
- 当请求服务的进程与服务属于不同进程,则为请求服务所在进程创建
binder_ref
对象,指向服务进程中的binder_node
,即返回请求服务的一个代理对象; - 当请求服务的进程与服务属于同一进程,则不再创建新对象,而是返回的对象的真实子类;
ServiceManager 获取服务的流程如下图所示:

- 获取服务的过程与注册类似,相反的过程。通过 ServiceManager 的
getService()
方法来注册服务; - 首先 ServiceManager 向 Binder 驱动发送
BC_TRANSACTION
命令,并携带CHECK_SERVICE_TRANSACTION
命令,同时获取服务的线程进入等待状态waitForResponse()
; - Binder 驱动收到请求命令向 ServiceManager 的发送
BC_TRANSACTION
查询已注册的服务,若查询到所需服务则直接响应BR_REPLY
并唤醒等待的线程,否则将与binder_procs
链表中的服务进行一次通讯再响应。
1.9. 进行一次完整通讯
进行一次完整通讯的流程如下图所示:

- 服务注册完成;
- 首先通过 ServiceManager 获取到服务端的
BinderProxy
代理对象,通过调用BinderProxy
将参数,方法标识传给 ServiceManager,同时客户端线程进入等待状态; - ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束。
2. AIDL
2.1. 概述
AIDL,全称是 “Android Interface Definition Language”,也就是 “Android接口定义语言”。设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象。而通过 AIDL 即可定义客户端与服务端均认可的编程接口,以便二者之间实现通信。
2.2. AID语法
AIDL 的语法基本上和 Java 是一样的,只是在一些细微处有些许差别,差别之处主要如下所示:
-
文件类型:用 AIDL 书写的文件的后缀是**
.aidl
**,而不是.java
。 -
数据类型:AIDL 默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的。但是除了这些类型之外的数据类型,在使用之前必须导包,即使目标文件与当前正在编写的 .aidl 文件在同一个包下也是需要导包的(在 Java 中,这种情况是不需要导包的)。
- 默认支持的数据类型包括:
- Java中的八种基本数据类型:boolean,byte,char,short,int,float,double,long;
- String 类型;
- CharSequence 类型;
- List 类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable,List可以使用泛型;
- Map 类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable,Map是不支持泛型的。
- 默认支持的数据类型包括:
-
定向tag:AIDL 中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端; out 表示数据只能由服务端流向客户端;而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个客户端中传入方法的对象的完整数据,但是客户端该对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
注:Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in。
-
两类AIDL文件:第一类是用来定义 parcelable 对象,以供其他 AIDL 文件使用 AIDL 中非默认支持的数据类型的。第二类是用来定义方法接口,以供系统使用来完成跨进程通信的。两类文件都是在“定义”,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
注:所有的非默认支持数据类型必须通过第一类AIDL文件定义 parcelable 对象才能被使用。
举例如下:
目录结构如图所示:
其中,Pet.java
、Person.java
分别与Pet.aidl
、Person.aidl
对应,对应的java文件与aidl文件的包名需一致(自动编译)。
Pet.aidl
、Person.aidl
以及Ipet.aidl
文件代码如下:
package com.example.aidlparcelableservertest;
// Pet.aidl
// 第一类AIDL文件
// 这个文件的作用是引入一个序列化对象 Pet 供其他的AIDL文件使用
// Pet.aidl 与 Pet.java的包名应该是一样的
parcelable Pet;
package com.example.aidlparcelableservertest;
// Person.aidl
// 第一类AIDL文件
// 这个文件的作用是引入一个序列化对象 Person 供其他的AIDL文件使用
// Person.aidl 与 Person.java的包名应该时一样的
parcelable Person;
package com.example.aidlparcelableservertest;
// Ipet.aidl
// 第二类AIDL文件
// Pet和Person不是默认支持的类型
// 即使Ipet.aidl与Pet.aidl、Person.aidl在同一包内,也需要导入
import com.example.aidlparcelableservertest.Pet;
import com.example.aidlparcelableservertest.Person;
interface IPet {
// 无论是什么类型,所有的返回值前都不需要加任何东西
// 定义一个Person对象作为传入参数
List<Pet> getPets(in Person owner);
// 传参时除Java基本类型
void addPetIn(in Pet pet);
void addPetOut(out Pet pet);
void addPetInout(inout Pet pet);
}
2.3. 如使用AIDL实现跨进程通信
在进行跨进程通信时,AIDL 中定义的方法里包含非默认支持的数据类型与否,要进行的操作是不一样的。如果不包含,则只需要编写一个.aidl
文件(第二类),如果包含,那么我们通常需要写 n+1 个 .aidl
文件( n 为非默认支持的数据类型的种类数)。所以接下来以 AIDL 文件中包含非默认支持的数据类型为例进行介绍。
在构建每个包含.aidl
文件的应用时,Android SDK 工具会生成基于该.aidl
文件的IBinder
接口,并将其保存到项目的 app/build/generated/aidl_source_output_dir
目录中。服务必须视情况实现IBinder
接口。然后,客户端应用便可绑定到该服务,并调用IBinder
中的方法来执行 IPC。
使用 AIDL 创建绑定服务的步骤如下:
- 创建
.aidl
文件:- 此文件定义带有方法签名的编程接口。
- 实现接口:
- Android SDK 工具基于
.aidl
文件,使用 Java 编程语言生成接口。此接口拥有一个名为Stub
的内部抽象类,用于扩展Binder
类并实现 AIDL 接口中的方法,必须扩展该Stub
类并实现这些方法。
- Android SDK 工具基于
- 向客户端公开接口:
- 实现
Service
并重写onBind()
,从而返回Stub
类的实现。
- 实现
2.3.1. 创建 .aidl
文件
创建Pet.aidl
、Person.aidl
文件,代码如下:
package com.example.aidlparcelableservertest;
parcelable Pet;
package com.example.aidlparcelableservertest;
parcelable Person;
定义接口服务时注意:
- 方法可带零个或多个参数,返回值或空值;
- 所有非默认支持的数据类型的参数均需要指示数据流向的方向标记:
in
、out
或inout
。默认支持的数据类型的参数只能为in; - 可以在 ADL 接口中定义 String 常量和 int 字符串常量,如:
const int VERSION = 1;
2.3.2. 实现 Parcelable 接口
可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。但必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 Parcelable
接口。
实现 Parcelable 接口相当于 Android 提供的一种自定义序列化机制,不仅要求实现Parcelable
接口中定义的方法,而且要求在实现类中定义一个名为CREATOR
、类型为Parcelable.Creator
的静态常量。
- 创建与
Pet.aidl
、Person.aidl
对应的文件Pet.java
、Person.java
,对应的java文件与aidl文件的包名一致。Pet.aidl
、Person.aidl
中的内容分别如下所示:
package com.example.aidlparcelableservertest;
import android.os.Parcel;
import android.os.Parcelable;
public class Pet implements Parcelable {
private String name;
private double weight;
public Pet() {
}
public Pet(String name, double weight) {
super();
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
// 实现Parcelable接口必须实现的方法
@Override
public int describeContents() {
return 0;
}
// 实现Parcelable接口必须实现的方法
// 将Pet对象的数据写入到parcel中
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeDouble(weight);
}
// 添加一个静态成员CREATOR,该对象实现了Parcelable.Creator接口
// 该静态常量的值负责从parcel数据包中回复Pet对象
public static final Creator<Pet> CREATOR = new Creator<Pet>() {
@Override
public Pet createFromParcel(Parcel parcel) {
// 从Parcel中读取数据,返回Pet对象
return new Pet(parcel.readString(), parcel.readDouble());
}
@Override
public Pet[] newArray(int i) {
return new Pet[i];
}
};
@Override
public String toString() {
return "Pet{" + "name='" + name + ", weight=" + weight + '}';
}
}
package com.example.aidlparcelableservertest;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
public class Person implements Parcelable {
private int id;
private String name;
private String pass;
public Person() {
}
public Person(Integer id, String name, String pass) {
super();
this.id = id;
this.name = name;
this.pass = pass;
}
public int getId()