IPC常见方式
- Bundle
- 优点: 简单易用
- 缺点: 只能传输Bundle支持的数据类型
- 使用场景: 四大组件间的进程间通信
- 文件共享
- 优点 简单易用
- 缺点 不适合高并发场景,无法做到即时通信
- 使用场景: 无并发访问请教,简单交换的数据实时性不高的场景
- AIDL
- 优点:功能强大,支持一对多并发通信,支持实时通信
- 缺点: 使用复杂些
- 使用场景: 一对多,RPC需求
- ContentProvider
- 优点: 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作
- 缺点 受约束的AIDL,主要提供数据源的CRUD操作
- 使用场景: 一对多的进程间数据共享
- Socket
- 优点功能强大,可以通过网络传输字节流,支持一对多并发实时通信
- 缺点 实现细节稍微有点麻烦,不支持直接RPC
- 使用场景: 数据交换
Binder是什么?
Binder是Android提供的一套进程间相互通信框架
系统服务ActivityManagerService,LocationManagerService,等都是在单独进程中的,使用binder和应用进行通信。
Android系统框架
Android系统分成三层。最上层是application应用层,第二层是Framework层,第三层是native层。
由下图可知几点:
Android中的应用层和系统服务层不在同一个进程,系统服务在单独的进程中。
Android中不同应用属于不同的进程中。
Android应用和系统services运行在不同进程中是为了安全,稳定,以及内存管理的原因,但是应用和系统服务需要通信和分享数据。
优点
- 安全性:每个进程都单独运行的,可以保证应用层对系统层的隔离。
- 稳定性:如果某个进程崩溃了不会导致其他进程崩溃。
- 内存分配:如果某个进程以及不需要了可以从内存中移除,并且回收相应的内存。
Binder通信
一个进程是不能直接直接操作另一个进程的,比如说读取另一个进程的数据,或者往另一个进程的内存空间写数据,进程之间的通信要通过内核进程才可以
client和service并不想知道binder相关协议,所以进一步client通过添加proxy代理,service通过添加stub来进一步处理与binder的交互。
Activity请求Activity ManagerService服务为例:
ActivityManagerService通过调用serviceManager中的addService方法,然后调用ServiceManagerNative类中的addservice(name)方法。
ServiceManagerNative会通过Binder发送一条SVG_MGR_ADD_SERVICE的指令,然后通过svcmgr_handler()调用do_add_service()方法往svc_list中添加相应的service。
client要请求服务,比如说在activity中调用context.getSystemService()方法,这个时候serviceManager就会使用getService(name),然后就会调用到native层中的ServiceManagerNative类中的getService(name)方法。
ServiceManagerNative会通过Binder发送一条SVG_MGR_GET_SERVICE的指令,然后通过svcmgr_handler()调用do_find_service()方法去svc_list中查找到相关的service。
查找到相应的服务后就会通过Binder将服务传给ServiceManagerNative,然后传给serviceManager,最后client就可以使用了。
服务是在svclist中保存的,svclist是一个链表,因此客户端调用的服务必须要先注册到svclist中。
Binder结构设计
- Java层AIDL
- Framework层, Android.os.Binder
- Native 层: libBinder.cpp
- 内核层的通信都是通过ioctl来进行的,client打开一个ioctl,进入到轮询队列,一直阻塞直到时间到或者有消息。
Binder中使用的设计模式
1、代理模式(Proxy Pattern )
在Android中client不是直接去和binder打交道,client直接和Manager交互,而manager和managerProxy交互,也就是说client是通过managerProxy去和binder进行交互的。同时service也不是直接和binder交互,而是通过stub去和binder交互。
Binder与内存映射mmap
Binder IPC 是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。
进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。
而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder传输数据的大小限制
Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,比如崩溃等,这其实就跟Binder传输数据大小的限制有关系,在上面的一次拷贝中分析过,mmap函数会为Binder数据传递映射一块连续的虚拟地址,这块虚拟内存空间其实是有大小限制的
普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 110241024) - (4096 *2) :这个限制定义在ProcessState类中,如果传输说句超过这个大小,系统就会报错,因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的:
系统服务与bindService等启动的服务的区别
服务可分为系统服务与普通服务
服务的启动方式 :
系统服务一般是在系统启动的时候,由SystemServer进程创建并注册到ServiceManager中的,这些服务本身其实实现了Binder接口,作为Binder实体注册到ServiceManager中,被ServiceManager管理,而SystemServer进程里面会启动一些Binder线程,主要用于监听Client的请求,并分发给响应的服务实体类,可以看出,这些系统服务是位于SystemServer进程中
bindService类型的服务,这类服务一般是通过Activity的startService或者其他context的startService启动的,这里的Service组件只是个封装,主要的是里面Binder服务实体类,这个启动过程不是ServcieManager管理的,而是通过ActivityManagerService进行管理的
服务的注册与管理:
- 系统服务一般都是通过ServiceManager的addService进行注册的,这些服务一般都是需要拥有特定的权限才能注册到ServiceManager
- bindService启动的服务可以算是注册到ActivityManagerService,只不过ActivityManagerService管理服务的方式同ServiceManager不一样,而是采用了Activity的管理模型,详细的可以自行分析
使用方式:
- 使用系统服务一般都是通过ServiceManager的getService得到服务的句柄,这个过程其实就是去ServiceManager中查询注册系统服务。
- 而bindService启动的服务,主要是去ActivityManagerService中去查找相应的Service组件,最终会将Service内部Binder的句柄传给Client。
Binder请求的同步与异步?
- 在单次Binder数据传递的过程中,其实都是同步的。只不过,Client在请求Server端服务的过程中,是需要返回结果的,即使是你看不到返回数据,其实还是会有个成功与失败的处理结果返回给Client,这就是所说的Client端是同步的。
- 服务端是异步的,可以这么理解:在服务端在被唤醒后,就去处理请求,处理结束后,服务端就将结果返回给正在等待的Client线程,将结果写入到Client的内核空间后,服务端就会直接返回了,不会再等待Client端的确认,这就是所说的服务端是异步的,
Android APP有多少Binder线程,是固定的么?
Android APP上层应用的进程一般是开启一个Binder线程,而对于SystemServer或者media服务等使用频率高,服务复杂的进程,一般都是开启两个或者更多。驱动会根据目标进程中是否存在足够多的Binder线程来告诉进程是不是要新建Binder线程。
Binder如何精确制导,找到目标Binder实体,并唤醒进程或者线程?
- 首先Service会通过addService将binder实体注册到ServiceManager中去,Client如果想要使用Servcie,就需要通过getService向ServiceManager请求该服务。
- 在Service通过addService向ServiceManager注册的时候,ServiceManager会将服务相关的信息存储到自己进程的Service列表中去,同时在ServiceManager进程的binder_ref红黑树中为Service添加binder_ref节点,这样ServiceManager就能获取Service的Binder实体信息。
- 当Client通过getService向ServiceManager请求该Service服务的时候,ServiceManager会在注册的Service列表中查找该服务,如果找到就将该服务返回给Client,在这个过程中,ServiceManager会在Client进程的binder_ref红黑树中添加binder_ref节点,可见本进程中的binder_ref红黑树节点都不是本进程自己创建的,要么是Service进程将binder_ref插入到ServiceManager中去,要么是ServiceManager进程将binder_ref插入到Client中去。
- Client就能通过Handle句柄获取binder_ref,进而获取到binder_proc与binder_node信息,之后Client便可有目的的将binder_transaction事务插入到binder_proc的待处理列表,进而访问Service服务。
- ServiceManager是比较特殊的服务,所有应用都能直接使用,因为ServiceManager对于Client端来说Handle句柄是固定的,都是0,所以ServiceManager服务并不需要查询,可以直接使用。
参考链接:
https://blog.csdn.net/happylishang/article/details/62234127
https://www.jianshu.com/p/adaa1a39a274