大家都是知道Android是基于Linux内核的系统,Linux内核具有多种IPC机制,如命名管道,匿名管道,信号量,消息队列,Socket。但是Android中常用的只有Socket和匿名管道。可是值得思考的是,为什么选择了Binder作为进程间通信?其实作为程序员,无论以后在面试还是平时遇到这种问题,只有时间,空间,安全等角度去思考,因为代码的本质终归如此。
Binder简介
Binder是Android 系统中一种高效的进程间通信(IPC)机制,它提供了一种在不同进程之间安全、高效地传递数据和调用方法的方式。采用CS架构,即有客户端和服务端区分,并且采用代理模式的设计模式。架构模式主要分为四部分:Binder驱动:提供文件系统的标准接口,如open(),ioctl(),mmap(),并且提供通信通道;SM进程: 通过Binder名称到对象的引用转换服务;Binder服务端: 服务端分为匿名服务和实名服务,系统中常见的AMS,WMS就是实名服务,都是公开的,APP都可以直接去获取服务调用。像APP自己定义的AIDL接口的服务端,Service端,都是匿名服务,只有APP自己公开的业务能使用。Binder客户端:使用Binder服务的一端。
进程间通信必然要涉及到Linux内核空间,所以Binder需要有驱动层来去支持。Binder是一套非常复杂的通信框架,从Java层,Native层,驱动层全链路涉及。老夫只能尽微薄之力从全局角度来做点功夫。
Binder 的工作原理
了解Binder的工作原理,首先要了解两个前置技能:AIDL和MMAP。AIDL大家都知道,Android独有的跨进程通信机制,很好的展现了代理模式,其实对AIDL的使用,本质就是对Binder的使用,在使用AIDL时候基本上分为四个步骤:
-
定义服务接口:使用 AIDL(Android Interface Definition Language)定义服务端提供的接口。
-
实现服务端:继承
Stub类来实现定义的接口,并处理客户端的请求。 -
客户端获取服务引用:通过
serviceConnection和bindService方法连接服务,并获取服务的 Binder 对象引用。 -
客户端调用服务方法:使用获取到的Binder对象引用调用服务端提供的方法。
查看AIDL文件编译出的源码都会看到,Proxy和Stub这个两个类,这就是客户端和服务端的代理类。
再说MMAP,一种内存映射机制,就是将物理内存映射到文件上,Linux中一切皆文件,操作这种文件相当于直接操作对应的内存。普通的IPC传递数据时,需要从调用者的数据缓冲区到内核缓冲区,再到接收进程的缓冲区,
因此binder通过内存映射的方式为每个进程创建了缓冲区,这块缓冲区与内核空间共享,接收进程可以直接读取缓冲区的数据,而不用从内核复制到用户空间。所以Binder通信大致流程就是,客户端通过服务端的代理对象,从ServiceManager得到Binder服务,向服务端进程的缓冲区写数据(此过程都在驱动层),驱动层激活服务端Binder线程,读取数据,根据函数号和参数来处理。
整个过程需要考虑一些问题:
1、数据的传递,Java数据不可能直接写入内核缓冲区的,所以出现了序列化和反序列化。
2、Binder线程维护,Binder每次执行,都是在线程中完成,很明显线程的频繁释放和创建会有很大的系统开销。Binder线程不像普通线程池一下初始化多个线程,而是在驱动的请求下,没有可用的线程时创建线程。这样能保证线程的最低数量。
3、线程唤醒问题,Binder驱动本质是一个文件描述符,所有的Binder调用都会使用,如果客户端同时好多线程同时调用,那么服务端返回数据的时候,会通过Binder驱动记录的调用信息,包括线程ID,根据ID将数据返回到对应线程。
简单总结一下流程:
1、客户端从某线程发起调用,将参数通过ioctl函数发给驱动,客户端挂起并等待返回结果。
2、驱动记录调用线程信息,根据调用的Binder对象寻找服务端的进程。
3、找到服务进程,查看是否有空闲线程处理,没有则创建,通过线程处理驱动数据对应执行函数,并返回结果给驱动。
4、驱动将处理结果返回给调用线程,并唤醒调用线程。
5、客户端调用线程得到反馈结果,做相应处理。
Binder 的优势
按照文章开头所说,从时间和空间考虑就是Binder的高效性,前面提到Binder驱动提供了mmap()方法,主要是为了提供内存映射。另外就是安全性,前面文章接受APP进程有提到ApplicationThread是一个Binder类,并且APP进程会通过进程的ID,向ServiceManager来注册一个身份标识,所以Binder本身具有身份证的作用。
Binder一些异常情况
-
数据丢失
- 原因:可能是缓冲区溢出、网络不稳定等。
- 解决方法:优化数据大小、增加错误处理和重试机制等。
-
权限问题
- 原因:客户端没有足够的权限访问服务。
- 解决方法:正确配置权限,确保客户端具有所需的权限。
-
进程间死锁
- 原因:不当的同步操作导致进程等待。
- 解决方法:仔细检查同步逻辑,避免死锁情况的发生。
总之,Binder 在 Android 系统中扮演着重要的角色,开发者需要深入理解其工作原理和使用方法,以构建高效、稳定的应用程序。 当在 Android 中遇到 Binder 通信丢数据的情况,可以考虑以下几种优化方式:
- 数据大小优化:尽量减少通过 Binder 传输的数据量。对大型数据进行拆分、压缩或只传递关键信息,在接收端再进行恢复或获取完整数据。
- 缓冲区管理:确保发送和接收端有足够的缓冲区来处理数据。合理设置缓冲区大小,以避免数据溢出导致丢失。
- 错误处理和重试机制:在发送和接收数据时,添加完善的错误处理逻辑。当检测到数据丢失或传输错误时,进行适当的重试操作。
- 数据序列化和反序列化优化:选择高效的序列化和反序列化方法,例如 Protocol Buffers 或 FlatBuffers ,以减少数据转换的开销和潜在的错误。
- 优化通信频率:避免过于频繁的 Binder 通信,将多个小数据请求合并为一个较大的数据请求,以减少通信次数和潜在的错误。Binder风暴问题了解一下。
- 检查权限和进程优先级:确保应用具有足够的权限进行 Binder 通信,并且进程的优先级设置合理,以避免被系统意外终止或限制资源。
- 监控和日志记录:添加详细的监控和日志记录,以便在出现数据丢失时能够准确分析问题所在,例如记录发送和接收的数据大小、时间戳、错误信息等。
通过综合运用以上方法,可以有效地优化 Binder 通信,减少数据丢失的情况。
源码好无聊,有兴趣自己去看吧,说了那么多,面试的时候你知道该怎么吹了吧......