阅读前言:欢迎评论、一起交流学习,如有错误及不足,望海涵,如这篇文章对您有用,麻烦帮忙点点小蛋糕,谢谢您的喜欢~
明确:Android是基于Linux实现的
明确:在Android中使用Activity、Service等组件需要和AMS(ActivityManagerServer)进行通信,这种跨进程的通信都是通过Binder完成的
明确:Activity、Service等组件和AMS不是同一个进程,所以为多进程通信
1.为什么Android要采用Binder?
因为Linux的通信机制不适用于Android,Android需要高效、安全、一对多的通信设计模式
解释:为什么Linux不满足呢?
我们由此需要了解Linux进程通信有哪些,并逐一分析它们的缺点
进程的本质:占据内存的资源
进程通信的本质:内存拷贝
首先: Linux通信包含(管道通信、共享内存、socket、File)
(1)管道通信
性能:需要拷贝2次
安全:正常、一般
通信设计模式:1对1 (这一点特别重要,根本上就不能成为Android通信机制)
(2)内存共享
性能:不需要拷贝
安全:不安全 (大家都可以互相访问的话......那...太简单粗暴了)(pass关键)
通信设计模式:n对n(也被pass,不能成为Android通信机制)

(3)socket
性能:需要拷贝2次,效率低
安全:不安全 (pass关键)
通信设计模式:1对n (优点:和Binder一样)
(4)File:和管道通信差不多,不在此介绍了
而Binder机制(优势)
性能:需要拷贝1次,效率高
安全:由Binder内核展开,很安全,为每个APP分配UID
通信设计模式:1对n
特点:基于C/S架构,易用性高
2.Binder是如何做到一次拷贝的?
需要明确:用户空间与内核空间


需要明确:物理地址与虚拟地址
(1)MMU:内存管理单元 功能:进行虚拟地址到物理地址的转换
传统设备:CPU与内存芯片直接相连

现代设备:CPU与内存芯片之间相连要经过MMU

(2)现代设备以来MMU之前的为虚拟地址,之后的为物理地址
(3)MMU调节活跃代码与不活跃代码,活跃代码放入内存,不活跃代码放入磁盘
(4)MMU同时维护一个页表,将虚拟地址与物理地址建立联系
举个例子执行代码:1.int a;2.a = 10
第一步:CPU执行代码int a;判断内存是否开辟a空间
第二步:没有 MMU就去磁盘读取int a代码
第三步:MMU拿到磁盘读出的int a代码
第四步:去物理内存开辟a的空间
第五步:同时创建a的物理地址
第六步:页表保存开辟出来的a的地址
第七步:根据a的物理地址随机创建一个虚拟地址
第八步:CPU拿到虚拟地址
第九步:执行代码a = 10
第十步:根据a找到其虚拟地址
第十一步:在找到其物理地址
第十二步:在物理内存中赋值10
需要明确:虚拟地址不能存数据,只能在物理内存中存
那么如何去开辟物理内存存数据呢?
举例:A、B进程间要进行通信,进程B是service端需要开辟物理内存空间(如图)
开辟物理空间时序图如下:
第一步:所有APP从zygote启动,zygote进程启动时调用app_main.cpp(入口)
第二步:在app_main.cpp中调用onzygoteinit()方法中的self()方法进入ProcessState.cpp中
第三步:在ProcessState.cpp中调用self()方法中的new方法,构造对象new ProcessState()
第四步:进入ProcessState的构造方法中打开驱动(返回磁盘Binder路径fd),赋值给mDriverFD
第五步:如果打开成功(mDriver>=0),说明文件路径存在,调用mmap函数,将文件映射到内存(物理内存),开辟物理空间 返回虚拟地址mVMStart
以上五步为开辟物理内存流程
下面详细介绍mmap()函数参数:
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
0:物理内存开始的地址—— 0即为随机系统进行分配
BINDER_VM_SIZE:开辟物理内存的大小,最大为1M-8K,不足一页(4K),按一页处理
PROT_READ:协议 可读/可写
MAP_PRIVATE | MAP_NORESERVE:映射类型能否共享,即进程A开辟物理空间后,进程B、C、D能否读取使用
mDriverFD:Binder的系统文件路径,将物理内存和磁盘数据连接了
用来将活跃代码写入内存,将不活跃代码写入磁盘 而且通过此地址可以找到物理地址之前映射的区域
0:偏移量 0即为不偏移
引申:Activity传递的参数受到什么的限制?最大传输量是?
收到mmap开辟物理空间的大小限制,最大传输1M-8K,即Activity进行进程通信一次最多拷贝1M-8K数据
Binder是用来通信的,不适合用来传输文件(更不适用于传bitmap),通信数据量本身就很小,所以是1M
返回的虚拟地址有什么用呢?
虚拟地址mVMstart存储在内核空间当中,进程A需要使用copy_from_user将数据从客户端通过内核空间中的虚拟地址mVMstart在页表找到物理地址从而复制数据到物理内存,而这里的物理内存就是进程B通过mmap函数开辟的,所以B进程可以在物理内存中拿到数据啦 (反复看,去理解)
这里是Binder进行一次拷贝的本质
总结Binder如何做到一次拷贝的:
(1)进程A、B开始通信传数据啦
(2)进程B通过mmap函数开辟物理内存空间
(3)mmap返回一个叫mVMstart的虚拟地址放到内核进程(内核空间)中
(4)进程A通过虚拟地址找到物理地址把数据复制到物理内存
(5)进程B直接拿取物理内存空间的数据
(6)传输数据成功!奖励一块小蛋糕!
3.Binder机制是如何跨进程的?
假设你(Client 进程)想要去图书馆(Server 进程)借书。但是图书馆不能随便进,有一个图书馆管理员(Service Manager)专门负责管理这件事
1. Server 端注册:
图书馆(Server 进程)在图书馆管理员(Service Manager)那里登记了自己的信息,比如图书馆的名字(唯一的服务标识),并且告诉管理员这里有很多书(创建了 Binder 对象,代表图书馆提供的服务)。这样,图书馆管理员就知道有这么一个图书馆可以提供借书服务了
2. Client 端获取服务代理:
你(Client 进程)想去借书,就跑到图书馆管理员(Service Manager)那里,跟管理员说你要去那个特定名字的图书馆借书。管理员就给你一张 “借书通行证”(Binder 代理对象)。这张通行证看起来就像你自己的东西(在 Client 进程中看起来像本地对象),但实际上它是用来让你进入图书馆借书的凭证,代表着图书馆那边的服务
3. 跨进程通信:
第一步:封装数据 你拿着 “借书通行证” 准备去借书,你得先想好你要借什么书(方法名),书的作者、出版社等信息(参数)。然后你把这些信息写在一张纸条上(封装成 Parcel 对象),这张纸条就包含了你借书的所有要求
第二步:发送请求 你把这张纸条交给门口的保安(Binder 驱动),保安会把纸条从你手里(Client 进程的地址空间)拿到他那里(内核空间),然后再把纸条送到图书馆里面(Server 进程的地址空间)
第三步:Server 端接收请求并处理 图书馆里面有很多工作人员(Binder 线程池中的线程),他们收到了保安送过来的纸条,打开一看(解析请求),知道你要借什么书了。然后他们就去书架上找你要的书(调用实际 Binder 对象的相应方法,执行具体业务逻辑)
第四步:返回结果 工作人员找到书后,把书的相关信息(处理结果)也写在一张纸条上(封装成 Parcel 对象),再交给保安。保安又把这张纸条从图书馆里面(Server 进程)拿出来,送到你手里(Client 进程)。你拿到纸条后,一看就知道你要借的书找到了,或者没找到等具体情况(代理对象解析并返回给调用者)
补充内容:
服务端重写onBind()方法,此方法内实例化aidl,生成接口,该接口中有两个内部类Stub、Proxy,Stub用来接受数据,Proxy用来发送数据
调用端调用函数,进入proxy()函数发送参数,再序列化参数(字符串、字节),以序列化的形式写入,再调用transact()方法,直接走到驱动层的binder_transaction()方法,此方法的代码都运行在内核空间中,在此方法内内核空间调用copy_from_user()方法
binder在调用端拷贝几次?1次
binder在调用端传递参数实际拷贝几次?2次 两次copy_from_user,两次都必须会执行,第一次调用传递数据,第二次调用传递请求头。请求头大小为8K。请求头包含信息:哪个类、目标进程,目的是为了可以确定传递方向以及回调数据方向
4.Android APP有多少Binder进程?
15个,超出15个就要进行等待执行
5.Intent传参,异步情况下,数据量是多大?
同步执行:
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("", "");
startActivity();
异步执行:
将上段代码放入一个线程中执行
new Thread() {
@Override
public void run() {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("", new byte[(1 * 1024 * 1024 - 8 * 1024) / 2]);
startActivity(intent);
}
}.start();
数据量为同步的一半:即(1M-8K) / 2