安卓中Binder机制是一种跨进程通信的方式,在日常应用开发中四大组件底层通信机制、Activity传递对象以及AIDL的使用等,都涉及到Binder机制。既然Binder是一种跨进程通信方式,那么首先就要知道什么是跨进程通信,为什么需要跨进程通信。(PS:计算机领域和自然科学领域的学习方法有很大的差异,自然科学的很多原理是自然固有的原理,只不过被人类总结归纳出来并用于发展科学技术。而计算机以及其他工程技术,即便是再高深复杂的技术手段,均不是凭空冒出来的,都是在不断解决问题中提炼优化出来的,是对某一类问题的解决方法的一种抽象。因此只要沿着这种技术发展的脉络和历史不断深究,层层递进,就能描绘出整个技术发展的轮廓。要记住的是,技术不会凭空出现,都是为了解决某一类的问题而提出来的)。
跨进程通信
在操作系统中不同进程之间是无法相互访问的,通过虚拟内存空间的方式,每个进程认为自己是独占整个内存空间。但是如果一个进程想要访问另外一个进程的数据应该如何实现?传统的Linux跨进程通信方式有Socket,共享内存,信号,管道,消息队列。(PS:这里面需要提前介绍一下内核空间和用户空间。操作系统将每个进程的虚拟内存空间分为用户空间和内核空间。内核空间涉及到访问底层硬件设备以及一些操作系统核心操作等,为了防止进程随意访问造成对操作系统的破坏,进程在正常情况下是无法访问内核空间的。但是有时候进程需要访问存储硬件或者访问网络,需要访问内核空间怎么办?这就需要系统调用(System call)。用户空间访问内核空间的唯一方式就是系统调用。程序通过系统调用访问内核空间称为进入内核态。)那么传统Linux的IPC是如何传递数据的呢。通常的做法是发送进程将数据放在内存缓冲区,然后调用系统调用进入内核态,在内核空间开辟内核缓冲区。然后调用copy_from_user将用户空间的内存缓冲区数据拷贝到内核空间的内核缓冲区。同样接受进程在用户空间开辟缓冲区,内核程序调用copy_to_user将内核缓冲区的数据拷贝到用户空间的内存缓冲区。
传统IPC方式有两个缺点:
1需要拷贝两次,性能低下。虽然共享内存只要拷贝一次,但是共享内存控制困难,需要处理复杂的同步问题。
2由于接受进程不知道接受的数据有多大,因此就会分配过多空间。
Binder跨进程通信
那么相比于传统IPC方式,Binder跨进程通信方式有什么优点呢?下面结合Binder通信原理进行介绍。Binder机制和网络一样都是基于Client/Server架构的。Binder机制中有Client/Server/ServiceManager/Binder驱动四个角色。Client和Server就是跨进程通信的双方,而ServiceManager就是安卓SystemServer中负责管理各个service的。首先Server会在SM中注册一个实体Binder,同时生成一个代理BinderProxy.Client通信时会让SM帮忙去注册表里查找是否存在这个Server的Binder,如果存在,会返回这个代理。Client想要调用服务端的方法时,其实不是调用Server里实体binder的方法,而是通过调用这个代理,代理会把数据返回到Server中实体Binder进行处理,然后再返回给Client.而整个通信过程涉及到系统调用或者与底层交互的,都是通过binder驱动来实现的。
同时Binder通过采用内存映射的方式传递数据,因此只需要拷贝一次数据就可以。具体做法是:将内核缓冲区与接受进程的数据接受缓冲区建立映射,当数据从发送进程的内存缓冲区拷贝到内核缓冲区时,接受进程的数据接受缓冲区就会立即变化。
基于AIDL源码进一步分析Binder机制
上面概括性的介绍了Binder跨进程通信的原理。下面结合安卓Aidl进行详细的讲解
AIDL(Android Interface Define Language 安卓接口定义语言)其实就是方便用户建立Binder机制的一种模板。用户完全可以不需要AIDL自己生成对应的代码。
定义一个接口继承IInterface.表明Server端具备什么能力供Client调用。
public interface ICalculate extends IInterface {
void add(int a, int b) throws RemoteException;
}
利用ICalculate.AIDL生成ICalculate.java文件。下面具体分析。
public interface ICalculate extends android.os.IInterface {
//Stub类继承Binder表明该类是一个实体Binder,同时继承了ICalculate接口表明具有该接口具
//备的能力.
public static abstract class Stub extends android.os.Binder implements ICalculate {
// Binder唯一标识。用于Server注册和Client查找
private static final java.lang.String DESCRIPTOR = "com.example.ICalculate";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
// 调用bindService后onServiceConneted中需要调用这个函数来获得这个Binder.代
// 码中通过调用queryLocalInterface查找是否存在本地Binder,如果存在,表明Client
// 和server在同一个进程,可以直接返回这个对象。如果不存在则需要创建一个代理
// Binder对象。
public static ICalculate asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof ICalculate))) {
return ((ICalculate) iin);
}
return new ICalculate.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
//根据代理发送过来的Code来决定具体执行哪一个方法。每个方法都有一个唯一的Code.
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
//调用本地add方法得到计算结果,写入reply返回。
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements ICalculate {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
//代理Binder中实现了和实体Binder相同能力的接口。Client得到代理Binder后,就可以
//调用add函数。如之前所描述的,这个Proxy中的add函数不是实体binder的add函
//数,而是将数据发送给Server,让server调用实体Binder的add函数来执行,并把结果
//返回。
@Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//将a,b通过parcel序列化成data
_data.writeInt(a);
_data.writeInt(b);
//transact中会调用onTransact方法将计算结果写入_reply返回。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
//得到计算结果。
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}
Binder的优点
1.性能。这一点上面阐述过了,Binder通过内存映射只需要拷贝一次数据。
2.安全性。在Linux中每个用户都有一个唯一的UserId.而安卓是单用户,每个应用拥有一个Uid.只有Uid相同的进程才可以进行数据共享和通信。这是安卓安全权限的一种方式。在Binder中会通过识别Uid来限制其他进程的访问,保证了应用的安全性。