前言
作为Android开发者一定知道Binder机制,Binder是Android系统提供的一种IPC机制(进程间通讯机制),比如在Android中的四大组件都会涉及到AMS通信,而这种跨进程通信都是由Binder来完成的,我们通常说Binder就是Android中的血管系统。而Binder机制是非常复杂的,要想完完整整搞懂是非常不容易的,作为应用层开发者来说理解其基本的原理就可以了,因此本文不会涉及到Native层和Kernel层。
多进程的优势
一般我们开发的app都是默认一个进程的,但是虚拟机给每个进程分配的运行内存是有限的,当应用越做越大,内存占用越来越多,将一些独立的组件放到不同的进程,它就不占用主进程的内存空间了。
多进程的优势主要有如下:
- 突破进程内存限制:如图库占用内存过多滑动卡顿
- 功能稳定性:独立的通信进程保持长连接的稳定性
- 规避系统内存泄漏:独立的WebView进程阻隔内存泄露导致的问题
- 隔离风险:对于不稳定的功能放入独立进程,避免导致主进程崩溃
Linux和Android进程间的通信机制
在Linux系统中提供了管道 、消息队列 、共享内存 和 Socket 等IPC机制,下面是它们详细的介绍:
- 管道:管道是Linux由Unix那里继承过来的进程间的通信机制,它是Unix早期的一个重要通信机制。管道的主要思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个共享文件比较特殊,它不属于文件系统并且只存在于内存中。另外还有一点,管道采用的是半双工通信方式的,数据只能在一个方向上流动。
- 消息队列:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识,并且允许一个或多个进程向它写入与读取消息。信息会复制两次,因此对于频繁或者信息量大的通信不宜使用消息队列。
- 共享内存:多个进程可以直接读写的一块内存空间,是针对其他通信机制运行效率较低而设计的。为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大的提高效率。
- Socket:套接字是更为基础的进程间通信机制,与其他方式不同的是,套接字可用于不同机器之间的进程间通信,主要缺点是效率比较低。
Binder与传统的IPC机制相比有什么优势
Android系统是基于Linux内核的,在Linux内核基础上又增加了Binder机制 ,那为什么又要增加Binder机制呢,主要是基于 性能、稳定性 和 安全性 等三个方面的原因。
性能
Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信。Socket需要从发送方通过copy_from_user拷贝到内核空间,然后在从内核空间通过copy_to_user拷贝到接收方,总共需要拷贝两次。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要通过copy_from_user一次数据拷贝,性能上仅次于共享内存。
稳定性
Binder是基于C/S架构的,这个架构通常采用两层结构,在技术上已经很成熟。
安全性
传统的IPC机制是依赖上层开放的协议访问接入点,可以在数据包中随意的填入UID,无法判断是否真实,是非常不安全的。而Binder可以为每个APP分配UID,同时支持实名和匿名,非常安全,例如我们的AMS、WMS就是实名的。
| Binder | 共享内存 | Socket |
---|
拷贝次数 | 一次 | 无需拷贝 | 拷贝两次 | 特点 | 基于C/S 架构易用性高 | 控制复杂,易用性差 | 基于C/S 架构作为一款通用接口,其传输效率低,开销大 | 安全性 | 为每个APP分配UID同时支持实名和匿名,安全 | 依赖上层协议访问接入点是开放的,不安全 | 依赖上层协议访问接入点是开放的,不安全 |
Binder是怎么做到只拷贝一次的
用户空间和内核空间
在Linux系统中将内存划分为用户空间和内核空间。用户空间是用户代码运行的地方,内核空间是内核代码运行的地方,比如一个32位的操作系统寻址空间是2^32,也就是4G,系统划分3G分配给用户空间,划分1G给内核空间。
由于进程之间是相互隔离的,进程A要想发送数据到进程B是需要系统调用来实现,系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。进程A首先通过copy_from_user拷贝到内核空间,然和通过copy_to_user从内核空间拷贝到进程B。
Binder通信原理
Binder机制的核心就是利用内核空间和用户空间的共享数据的原理来实现进程间通信。Linux系统中的mmap,借助的是硬盘文件与内存地址之间形成一个映射关系,操作这块内存并可以直接操作该文件。但是android中并不存在这么一个文件。Android借助是/dev/binder驱动,在内核空间中开辟了一块接收缓存区,并将之与用户空间地址进行了映射。所以通过这个映射关系,接收方在用户空间中就可以访问到接收缓存区中的数据,不需要再次进行拷贝。同时,接收缓存区与内核缓存区同样建立了映射关系,当内核空间copy_from_user用户空间时,将数据拷贝到内核缓存区。至此,接收方就可以访问到这些数据了,整个过程,只用了一次拷贝。 图片来自网络:
mmap是什么
mmap能够让虚拟内存和指定物理内存直接联系起来,mmap是操作系统中一种内存映射的方法,mmap通常用在有物理介质的文件系统上,使用mmap可以把文件映射到进程的地址空间,实现磁盘地址与进程虚拟空间地址的对应关系。
内存映射
就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
AIDL
什么是AIDL
AIDL 全称是 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以快速生成Binder机制的代码,省去了手动编写Binder机制。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板。
AIDL通信流程
使用AIDL实现进程通信需要客户端和服务端配合
- 服务端:通常是需要在Service中,使用AIDL生成的Stub类并实现这个类然后创建对象返回给onBind方法,作用是监听客户端的请求。
- 客户端:需要绑定Service,绑定成功后将服务端返回的代理Binder对象转换成AIDL接口所属的类型,然后调用AIDL中的方法。
具体用法可以参考官方文档
通过手写AIDL生成的类加深对Binder的理解
由于AIDL生成的类是java,我们也可以改为kotlin。 😜
interface IStudentManager : IInterface {
fun addStudent(stu: Student?)
fun getAllStudent(): List<Student?>?
companion object {
const val DESCRIPTOR = "com.jk.binder.common.IStudentManager"
}
}
class Proxy(private val mRemote: IBinder) : IStudentManager {
override fun addStudent(stu: Student?) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeInterfaceToken(IStudentManager.DESCRIPTOR)
if ((stu != null)) {
data.writeInt(1);
stu.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addStudent, data, reply, 0)
reply.readException()
} finally {
data.recycle()
reply.recycle()
}
}
override fun getAllStudent(): List<Student?>? {
val data = Parcel.obtain()
val reply = Parcel.obtain()
val result: List<Student>?
try {
data.writeInterfaceToken(IStudentManager.DESCRIPTOR)
mRemote.transact(Stub.TRANSACTION_getAllStudent, data, reply, 0)
reply.readException()
result = reply.createTypedArrayList(Student.CREATOR)
} finally {
data.recycle()
reply.recycle()
}
return result
}
override fun asBinder(): IBinder = mRemote
}
abstract class Stub : Binder(), IStudentManager {
init {
attachInterface(this, IStudentManager.DESCRIPTOR)
}
override fun asBinder(): IBinder? {
return this
}
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
when (code) {
INTERFACE_TRANSACTION -> {
reply?.writeString(IStudentManager.DESCRIPTOR)
return true
}
TRANSACTION_addStudent -> {
data.enforceInterface(IStudentManager.DESCRIPTOR)
var stu: Student? = null
if (0 != data.readInt()) {
stu = Student.createFromParcel(data)
}
this.addStudent(stu)
reply?.writeNoException()
return true
}
TRANSACTION_getAllStudent -> {
data.enforceInterface(IStudentManager.DESCRIPTOR)
val result: List<Student?>? = this.getAllStudent()
reply?.writeNoException()
reply?.writeTypedList(result)
return true
}
}
return super.onTransact(code, data, reply, flags)
}
companion object {
const val TRANSACTION_addStudent = FIRST_CALL_TRANSACTION
const val TRANSACTION_getAllStudent = FIRST_CALL_TRANSACTION + 1
fun asInterface(binder: IBinder?): IStudentManager? {
if (binder == null) {
return null
}
val iin = binder.queryLocalInterface(IStudentManager.DESCRIPTOR)
return if (iin != null && iin is IStudentManager) iin else Proxy(binder)
}
}
}
客户端:
class MainActivity : AppCompatActivity() {
private var mIStudentManager: IStudentManager? = null
private var index = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent(this, RemoteService::class.java)
intent.action = "com.jk.binder"
bindService(intent, connection, BIND_AUTO_CREATE)
findViewById<View>(R.id.add).setOnClickListener {
mIStudentManager?.addStudent(Student("jack:$index", 9))
index++
}
findViewById<View>(R.id.getAll).setOnClickListener {
val students = mIStudentManager?.getAllStudent()
students?.forEach {
Log.e("client", it?.name ?: "")
}
}
}
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mIStudentManager = Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
mIStudentManager = null
}
}
}
服务端:
class RemoteService : Service() {
private val students = ArrayList<Student>()
override fun onBind(intent: Intent): IBinder {
return binder
}
val binder = object : Stub() {
override fun addStudent(stu: Student?) {
stu?.let {
students.add(it)
}
}
override fun getAllStudent(): List<Student?> {
return students
}
}
}
bindService流程
我们在客户端通过bindService,绑定成功后会在ServiceConnection中onServiceConnected得到了RemoteService的IBinder对象,我们就可以通过IBinder和服务端通信。那么到底是在什么地方返给我们的?Service的绑定流程是怎样的?那就需要详细的阅读源码了。
bindService流程见下篇文章。
参考文章
Binder学习指南 中级Android开发应该了解的Binder原理 Android Binder设计与实现 - 设计篇
|