基本上参考Android跨进程通信:图文详解 Binder机制 原理再结合自己的理解记录本次学习
Binder总结
Binder驱动
全部流程表
接下来就来理解并扒一下其中的细节
什么是进程间通讯,为什么要搞一个Binder机制
一个进程空间分为 用户空间和内核空间(kernel)
用户空间:数据不可共享=不可共享空间
内核空间:数据可共享=可共享空间
所有进程共用1各内核空间
内核空间、用户空间、内核态、用户态
对于32位操作系统而言,它的寻址空间(虚拟地址空间)为4G。操作系统将虚拟地址空间划分为两部分、一部分内核空间(最高1G字节),一部分用户空间(3G字节)
内核态:当进程运行在内核空间,CPU执行任何指令,可以自由的访问任何有效地址
用户态:当进程运行在用户空间,只能范文映射其地址空间的页表项中规定的用户态下可访问页面的虚拟地址
为什么要区分内核空间与用户空间
CPU将指令分为特权指令和非特权指令,对于危险的指令,比如清内存等,这种指令只允许操作系统及其相关模块使用,普通应用只能使用普通指令,所以Ring3级别被运行在用户态,Ring0级别被成为运行在内核态
用户空间与内核空间的交互
进程内两个空间的交互需要通过系统调用,主要函数
copy_from_user():将用户空间的数据拷贝到内核空间
copy_to_user():将内核空间的数据拷贝到用户空间
进程隔离&跨进程通讯(IPC)
进程间传递数据必须要经过内核空间而 发送到内核 和从内核拿出给另一个进程时需要两次数据的拷贝
Binder的优势
binder机制则只需要1次数据拷贝,它通过Binder驱动负责管理数据接受关村,原理是内存映射mmap系统调用、它连接两个进程,实现了mmap()系统调用,主要负责创建数据接收的缓冲空间&管理数据接收缓存
内存映射 mmap()
(memory map)含义
关联进程中的1个虚拟内存区域 & 1个磁盘上的对象,使二者存在映射关系
这样如果从缓冲区中取数据,就相当于读文件中的相应字节,而将数据存入缓冲区,就相当于写文件中的相应字节,这样不需要read和write也能直接之心I/O了
就是操作内存就可以操作磁盘中的对象了
示意图
如果多个进程虚拟内存区域和同1个共享对象,简历映射关系后,若1个进程对该虚拟区域进行写操作,那么对于也把共享对象映射到其自身虚拟内存区域的进程也是可见的
实现过程
mmap()的返回值是内存映射在用户空间的地址
该函数的作用 = 创建虚拟内存区域 + 与共享对象建立映射关系
/** * 函数原型 */
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
/** * 具体使用(用户进程调用mmap())
* 下述代码即常见了一片大小 = MAP_SIZE的接收缓存区
& 关联到共享对象中(即建立映射) */
fd = open("/dev/binder", O_RDWR);
mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
/** * 内部原理 *
步骤1:创建虚拟内存区域 *
步骤2:实现地址映射关系,即:进程的虚拟地址空间 ->> 共享对象
* 注: * a. 此时,该虚拟地址并没有任何数据关联到文件中,仅仅只是建立映射关系
* b. 当其中1个进程对虚拟内存写入数据时,则真正实现了数据的可见
*/
具体工作流程
Binder 模型
模型原理图
基于CS模型即Client-Server 模式
模型组成角色说明
角色作用备注
Client 进程使用服务 的进程Andoird 客户端 各种App
Server 进程提供服务 的进程服务端 System_server
ServiceManager进程管理Service查找和注册服务
(将字符形式的Binder名字 转化成Client中对该Binder 的引用)
由init进程启动的进程,作为Binder IPC通讯的守护进程
功能类似路由器
Binder 驱动一种虚拟设备驱动,是连接Service进程、Client进程和ServiceManager进程的桥梁,它们之间的交互(使用open和ioctl文件操作函数)而非直接交互
1.传递进程间的数据:通过内存映射
2.实现线程控制:采用Binder的线程池,并由Binder驱动自身进行管理
Binder驱动持有每个Service进程在内核空间中的Binder实体,并给Client进程提供Binder实体的引用Proxy
模型原理步骤说明
简单总结
注册服务:
系统Server服务进程在启动时会先把所有服务注册到ServiceManager进程
获取服务:
Client进程发起请求传入获取的服务名-》Binder驱动将请求-》SeviceManager进程找到所需服务后通过Binder驱动返回给Client进程,此时返回的是代理对象 除非Client与Server在同一进程
使用服务:
Binder驱动:为跨进程做准备实现内存映射(根据ServiceManager进程里的Service信息找到对应的Server进程,实现内核缓存区 和 Server 进程用户空间地址 同时映射到 同1个接收缓存区中)
Client进程:将参数写入Parcel对象中打包(序列化),通过BinderProxy的transact()将数据发送内核缓存区(此时Binder驱动由于存在映射关系相当于给到Binder驱动),等待Server返回replay,并挂起Client当前线程
Sever进程:收到驱动通知后,调用Binder对象的onTransact()将Parcel解包(反序列化)并执行要client要调用目标方法并返回replay,随后把replay返回给Client,此时Client线程被唤醒 完成了一次服务的使用
流程如下图
获取服务
Client获取服务时,会去ServiceManager获取服务名对应的IBinder,随后通过asInterface获取服务
static publicINotificationManagergetService()
{
if (sService!= null) {
returnsService;
}
IBinderb=ServiceManager.getService("notification");
sService=INotificationManager.Stub.asInterface(b);
returnsService;
}
而asInterface会去获本地对象,否则就给到Binder代理类
public static BookManager asInterface(IBinder binder){
if (binder == null)
return null;
//寻找Binder本地对象
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof BookManager){
return (BookManager) iin;
}
return new Proxy(binder);
/**
* 如果server端(service所在的进程)和client端处于同一个进程 那么此时不需要跨进程通信 直接调用Stub的方法即可(Stub实例化时已经实现了具体的方法)
* 反之,则返回proxy对象 从构造方法看 该对象持有着远程Binder引用 如果调用Stub.proxy接口 那么将会是IPC调用 即通过transact方法与服务端进行通信
* 从字面意思看stub是服务端实现的存根(Stub工作在server进程),
* proxy则是stub的代理 proxy工作在client进程
*/
}
使用服务
步骤1: Client进程 将参数(整数a和b)发送到Server进程
// 1. Client进程 将需要传送的数据写入到Parcel对象中
// data = 数据 = 目标方法的参数(Client进程传进来的,此处就是整数a和b) + IInterface接口对象的标识符descriptor
android.os.Parcel data = android.os.Parcel.obtain();
data.writeInt(a);
data.writeInt(b);
data.writeInterfaceToken("add two int");;
// 方法对象标识符让Server进程在Binder对象中根据"add two int"通过queryLocalIInterface()查找相应的IInterface对象(即Server创建的plus),Client进程需要调用的相加方法就在该对象中
android.os.Parcel reply = android.os.Parcel.obtain();
// reply:目标方法执行后的结果(此处是相加后的结果)
// 2. 通过 调用代理对象的transact() 将 上述数据发送到Binder驱动
binderproxy.transact(Stub.add, data, reply, 0)
// 参数说明:
// 1. Stub.add:目标方法的标识符(Client进程 和 Server进程 自身约定,可为任意)
// 2. data :上述的Parcel对象
// 3. reply:返回结果
// 0:可不管
// 注:在发送数据后,Client进程的该线程会暂时被挂起
// 所以,若Server进程执行的耗时操作,请不要使用主线程,以防止ANR
// 3. Binder驱动根据 代理对象 找到对应的真身Binder对象所在的Server 进程(系统自动执行)
// 4. Binder驱动把 数据 发送到Server 进程中,并通知Server 进程执行解包(系统自动执行)
步骤2:Server进程根据Client进要求 调用 目标方法(即加法函数)
// 1. 收到Binder驱动通知后,Server 进程通过回调Binder对象onTransact()进行数据解包 & 调用目标方法
public class Stub extends Binder {
// 复写onTransact()
@Override
boolean onTransact(int code, Parcel data, Parcel reply, int flags){
// code即在transact()中约定的目标方法的标识符
switch (code) {
case Stub.add: {
// a. 解包Parcel中的数据
data.enforceInterface("add two int");
// a1. 解析目标方法对象的标识符
int arg0 = data.readInt();
int arg1 = data.readInt();
// a2. 获得目标方法的参数
// b. 根据"add two int"通过queryLocalIInterface()获取相应的IInterface对象(即Server创建的plus)的引用,通过该对象引用调用方法
int result = this.queryLocalIInterface("add two int") .add( arg0, arg1);
// c. 将计算结果写入到reply
reply.writeInt(result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
// 2. 将结算结果返回 到Binder驱动
步骤3:Server进程 将目标方法的结果(即加法后的结果)返回给Client进程
// 1. Binder驱动根据 代理对象 沿原路 将结果返回 并通知Client进程获取返回结果
// 2. 通过代理对象 接收结果(之前被挂起的线程被唤醒)
binderproxy.transact(Stub.ADD, data, reply, 0);
reply.readException();;
result = reply.readInt();
}
}
Binder驱动加载过程中有哪些重要步骤
优点
对比 Linux (Android基于Linux)上的其他进程通信方式(管道、消息队列、共享内存、
信号量、Socket),Binder 机制的优点有:
IBinder、BpBinder、BBinder、Stub、IInterface
名称描述
IBinder一个接口类,代表了一种跨进程通讯的能力,实现这个接口,这个对象就能跨进程传输
BpBinder代理Binder,当clicnt调用service后,会得到一个BpXXXService,它将str参数打包到Parcel中,然后调用remote()->transact,remote()返回的就是一个BpBinder,所以实际上调用的是BpBinder的transact
BBinder服务端Binder,当Service接收到client请求后,会调用BBinder的taransact方法会调回子类实现的虚拟方法onTransact,该方法实现是在BnXXXservice中实现,所以BBinder就是接受传递过来的信息,解包数据,并调用XXXservice真正的实现
IInerfaceAIDL文件中定义的接口,表示Server进程对象具备哪些能力
Stub一个抽象类,其中由asInterface和onTransact关键函数组成
其中asInterface 传入binder对象,如果同一进程
序列化与反序列化
为什么要序列化与反序列化?
因为对象内的各种数据不能直接跨进程传递,所以需要将java对象转换成字节序列(序列化)等到了接收方便要从字节序列中恢复出Java对象(反序列化)
序列化有哪些好处?
实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上
Serializable与Parcelable的区别
SerializableParcelable
原理通过I/O读写存储再磁盘上,同各国反射解析出对象描述、属性的描述,以HandleTable 来解析存储信息,然后生成二进制、存储、传输主要用于Binder传输,将数据写入共享内存,其他进程通过Parcel可以从这块共享内存中读出字节流
存储介质使用I/O读写存储在硬盘上直接在内存中读取,内存中的结构是一块连续的内存,会根据需要的内存大小自动扩展
来源JavaAndroid
效率通过反射,需要大量的I/O操作自己实现封送和解封,效率更快
使用序列化:
要创建某些OutputStream对象
将其封装到ObjectOutputStream对象内
此后只需调用writeObject()即可完成对象的序列化
忘记关闭资源:objectOutputStream.close(), outputStream .close();
反序列化:
通过readObject()获取资源
通过IPC传递的对象都要继承于Parcelable接口,并实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。为类添加CREATOR静态字段,该字段是实现Parcelable.Creator接口的对象。最后,创建声明 Parcelable 类的.aidl文件
Binder中ServiceManager的作用
ServiceManage使用客户端可以获取binder实例对象的引用
AIDL
什么是AIDL
AIDL是Android提供的接口定义语言,简化Binder的使用,会生成一个服务代理端对象的代理类,可以理解成服务端代码,与Client客户端中要链接的binder相关如服务连接asInterface、transact、序列化,以及支持IPC的目标方法等
使用步骤
(1)建立aidl的服务方:
a.建立一个含有activity的service,其实也可以直接建立一个service
b.建立aidl文件,声明接口以及方法,建立完成后ADT会自动生成一个aild接口的实现java文件,在gen下面.
c.建立service继承类,内部实现aidl的stub.且在onbind中进行返回.
b.AndroidManifest.xml文件中配置AIDL服务,需要注意的是<action>标签中android:name的属性值就是客户端要引用该服务的ID,也就是Intent类的参数值。
(2)建立客户断完成跨进程的调用.
a.建立android project内部会自动创建一个activity,在activity中bind服务.bind服务成功后,onServiceConnected将会回调返回给service给定的ibind.
b.onServiceConnected根据返回的内容,实现跨进程的调用.
参考文章:
https://blog.csdn.net/carson_ho/article/details/73560642