“粘合剂”,可以理解为:粘合两个进程。它在不同的应用情景中,代表的意思不一样:
1、进程间通讯的本质
2、什么是C/S架构
3、Linux进程间通信(IPC)方式有7种:
4、Andorid 提供了4种跨进程通讯的方法
5、数据拷贝次数
6、Android 五层架构图,Binder驱动 位于最下层,由下往上结构分为:
7、aidl 是什么?为什么采用 aidl ?
8、Binder 驱动、Binder 引用、Binder 接口 是什么?
9、AIDL 和 binder的区别:
10、Binder 使用 Pacelable 实现序列化
在Intent、Bundle 传输对象时,需要对它实现序列化,实现序列化有两种方法:Serrialzable 和 Parcelable,后者由于性能好效率高,因此被用于 Binder 底层数据传输。原因详见
11、句柄是什么?
英文"handle",能够从一个数值拎起一大堆数据的东西都可以叫做句柄。我们可以这样理解Windows句柄:
系统规定:任何 IPC 都必须使用句柄0来访问,Binder驱动为 ServiceManager 分配了 0号句柄,其余进程句柄都是一个的大于0的值
主要由4部分组成:Binder驱动、ServiceManager、Server、Client。下面分别对每个模块进行分析:
1、Binder驱动:
2、服务端 注册:即 Server 向 ServerManager 注册
对应上图 2_1:初始化时,服务端(Server)提供参数(进程的名称),请求Binder驱动(binder.c),要求注册到ServiceManager。binder驱动把binder_proc (见上面第2点说明,下同)的信息发送给ServiceManager
对应上图 2_2:在ServiceManager中有一个链表svc list,每当向ServiceManager注册一个服务端,就会往svc_list链表添加一个节点(node),节点名称就是Server的名称
3、客户端 查询 服务端
对应上图 3_1:客户端(Client)根据Server服务名称,向Binder驱动(binder.c)请求查询ServiceManager 的链表。
对应上图 3_2:ServiceManager 遍历svc list 链表,找到与服务名称相同的节点(node)
对应上图 3_3:ServiceManager 把响应结果handle(整型值)回复给Client
对应上图 3_4:Client 收到 handle 值
4、客户端 调用 服务端
客户端根据handle 值,经过binder 驱动内部的一系列转化,将handle 的值对应到binder_pro 里面,进而找到Server 进程。
下面简述一下每部分的工作流程:
ServiceManager:
1_1)通过open函数,打开binder驱动(/dev/binder)
使用mmap。在内核空间,创建一个内存空间,应用层需要通过mmap拿到内存首地址
1_2)成为管理者:告诉binder驱动,我是各种服务的管理者(handle值为0。查询到注册的服务的handle值都是 >0)
1_3)循环。一直处于工作状态
1)读取数据----读取binder驱动,获取请求进程数据(Server 端注册的数据 / Client 端查询的数据)
2)解析数据----判断数据类型(注册 or 查询),如果是注册服务,在svc_list 链表添加数据;如果是查询服务,找到链表中内容
3)回复数据----回复一个handle值
源码位置:service_manager.c
Server 流程:
2_1)open函数,打开binder驱动。/dev/dbinder
mmap函数 在内核空间,创建一个内存空间,应用层需要通过mmap拿到内存首地址
2_2)注册服务---向serviceManager注册服务
2_3)循环
1)读取数据---除了读取servicemanager返回的数据外,重点是读取client 端数据
2)解析数据---解析client数据,得知client 到底想要调用server的哪个函数,然后server内部执行这个函数
3)回复数据---把执行函数的结果,告知client
Client 流程:
3_1)open函数,打开binder驱动。/dev/dbinder
mmap函数 在内核空间,创建一个内存空间,应用层需要通过mmap拿到内存首地址
3_2)查询服务---向ServiceManager查询,得到handl整数值,
3_3)调用服务---根据handle值,通过驱动执行server的函数
命令码:
ServiceManager、Server、Client 三者之间的任意交互,要实现进程间的传输,都需要通过命令码来实现:
1、数据传输:
请求方 发送命令码 BC_Transaction---> 经过binder驱动后,该命令码会被转换成 BR_Transaction ---> 接收方得到BT_Trasaction
2、数据回复:
接收方 发送命令码 BC_Reply----> 经过binder驱动后,该命令码会被转换得到 BR_Reply ---> 请求方 得到Br_Reply
一个不恰当的比喻:程序猿找媳妇
1)程序猿(Client),想要找媳妇(Server),要通过婚姻介绍所(ServiceManager)介绍,而婚姻介绍所(ServiceManager)要到政府机关(Binder驱动)注册企业信息、接受行政管理。
2)程序猿(Client)想找一个称心如意不跑路的媳妇(Server),需要按照政府部门(Binder驱动)指引,到正规婚姻介绍所(ServiceManager)提出要求即可:年龄、身高、三围、工作、长相等等。
3)婚姻介绍所(ServiceManager)按照要求,找到了凤姐(指定的Server),告诉程序员(Client)凤姐的电话号码,让他们两个私下联系。
4)程序猿(Client)跟凤姐(Server)进行约会(传输通讯)
5)上述流程环节都要通过政府部门(Binder驱动)的监督、指引,在合法范围内办事。
借用张图:
先来理清几个类:
1、请求:进程A通过Proxy通过访问Binder驱动,Binder驱动找到进程B的Stub
进程A的Proxy会在callMyService中调用transact,通过JNI连接到IBinder驱动,IBinder驱动会通过native代码找到进程B的AIDL,找到后调用AIDL的Stub,回调onTransact方法
transact是连接IBinder引用后写数据,onTransact是IBinder引用连接Stub接受数据
2、响应:进程B通过自身的Proxy访问Binder驱动,Binder驱动把结果响应给进程A的Stub
进程A 访问 进程B,先是A 根据自己的aidl标识,通过JNI调用NDK,找到进程B 在binder驱动程序中的 binder引用。因为A、B进程的aidl标识都是类名、路径那些都是一样的,所以binder驱动可以很轻易找到进程B的信息,然后根据类名执行C/C++代码,访问B进程中的java层的变量和方法
IPC在我们的应用开发过程中随处可见,下面我将举一个例子来说明他的重要性。
我们在MainActivity修改一个静态变量,接着在另一个进程的SecondActivity中去访问该变量,看看能否读取已经修改过的变量。
1、新建一个Student类,并声明一个静态变量
public class Student {
public static String name="BOB";
}
2、在MainActivity的onCreate方法中修改name的值,并打印log
@Override
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方法中访问name
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_activity);
Log.d("SecondActivity:Sname=" , Student.name);
}
}
通过log,可以看到在MainActivity中修改了name的值,但是在SecondActivity中却无法读取修改后的值
通过以上的实验,大家应该明白了一点:
在不同的进程之间访问同一个静态变量是行不通的。其原因是:
每一个进程都分配有一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机上访问同一个对象会产生多个副本。
例如我们在MainActivity中访问的name的值只会影响当前进程,而对其他进程不会造成影响,所以在SecondActivity中访问name时依旧只能访问自己进程中的副本。
未完,待续.....