Binder是Android系统进程间通信(IPC)最重要的方式。要想了解Android的系统原理,必须要先对Binder框架有一定的理解。Binder是什么?Binder可以理解为能在进程间进行"通信"的对象,这个通信不是指在不同进程中操作同一个对象,而应理解为一种通信协议。
一、Binder的引入背景
传统的进程间通信方式有管道,消息队列,共享内存等,其中管道,消息队列采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。Binder通过内存映射的方式,使数据只需要在内存进行一次读写过程。
内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,相反,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
二、Binder的通信模型
主要分为4个部分Binder驱动,Client,Server,ServiceManager(SM),其中Client,Server,ServiceManager(SM)指的是用户空间,并且分别在不同的进程。Binder驱动是内核空间,Binder驱动是一段c语言实现的代码。
服务分系统服务和本地服务,系统服务一般指设备开机时就创建供所有应用使用的服务,如AMS,PMS等,本地服务一般指本地创建的Service,一般供当前应用使用。
由上图可以看出Binder通信的流程是这样的:
- 首先不同的服务Server一般为service需要先在SM上进行注册
- Client想获取服务时,先到SM上通过服务名请求服务
- 得到对应的服务的引用,通过这个引用进行进一步的通信。
由于Client、Server、SM不在同一个进程,所以都需要借助Binder驱动完成通信。这其中有几个关键点。先明确几个概念,Binder Server对象是指真正的进行服务的对象,Biner内核对象是根据Binder Server对象在内核空间的一种表述方式,因为语言层面不同,所以具体的表现形式也不同。比如用应用层表现为Java类或C++类,在内核层表现为结构体了。也可以称为实体,主要是和引用区分开,实体只有一个,而引用可以有很多。
- Client、Server、SM不在同一个进程,最初Client和Server怎么得到SM的通信的?
- 具体的注册流程是怎样的?
- Binder内核对象,BinderProxy对象是同一个对象吗,有什么联系?
首先第一个问题,首先ServiceManage进程本身就是也是采用Binder通信,在系统初始化的时候使用BINDER_SET_CONTEXT_MGR命令将自己注册为ServiceManage,这个过程会在内核空间产生一个ServiceManage的Binder实体。而这个Binder就在内核的0号引用,其他进程通过0号引用找到ServiceManage的Binder实体,通过内存映射就可以和SM建立联系。
第二个问题,首先拥有服务名的Server进程向SM注册时需要借助Binder驱动,此时创建了一个在内核的实体对象,和Server的对象是不同的结构,但拥有Server实体对象的关键信息,比如可以提供的方法等。这个实体对象有一项数据保存Server实体的引用(在内存中可以理解为映射的地址),借助Binder驱动将内核中的实体对像的引用和服务名注册到SM中,这样就有这样一条关系链,SM中服务名—>内核的实体对象的引用—>Server实体对象。
第三个问题,Binder内核对象,BinderProxy对象是同一个对象可定不是同一个对象,因为对应不同的空间,语言层面都有不一样,BinderProxy是系统根据Binder内核对象在Cilent进程新建(new)的一个Binder对象,里面包含Binder内核对象的关键信息,比如所能提供的方法等,这个对象就和真正远程的Server实体对象很相似了,这样Client对象就能像操作Server实体对象一样进行操作,不用考虑Server实在远程还是本地。但具体方法的实现还要通过代理的方式调用远程对象来实现。一般通过transact()和onTransact()来实现代理的调用。
常见的Binder通信过程分析
1. 利用Binder不使用AIDL实现IPC的过程
服务端:
public class NoAidlService extends Service {
public static final int TRANSACTION_studyBinder = 0x001;
private static final String DESCRIPTOR = "NoAidlService";
private Binder mNoAidlBinder = new NoAidlBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mNoAidlBinder;
}
private class NoAidlBinder extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case TRANSACTION_studyBinder: {
data.enforceInterface(DESCRIPTOR);
String _arg0;
_arg0 = data.readString();
String _result = _arg0+" Study NoAidlService";
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
}
}
注册服务,并指定为远程进程
<service
android:name=".NoAidlService"
android:process=":remote" />
客户端:
private ServiceConnection mNoAidlConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder clientBinder) {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken("NoAidlService");
_data.writeString("SilenceDut");
service.transact(NoAidlService.TRANSACTION_studyBinder, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
mResultTv.setText(mResultTv.getText()+"\n"+_result);
} catch (RemoteException e) {
e.printStackTrace();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
绑定服务:
Intent intent = new Intent(this,NoAidlService.class);
bindService(intent, mNoAidlConnection, Context.BIND_AUTO_CREATE);
具体的过程是NoAidlService需要先在Manifest注册信息,这相当于是将服务信息注册到ServiceManage。客户端用Intent将NoAidlService的服务名等信息包装,通过bindService在ServiceManage里寻找NoAidlService,如果成功,则通过onServiceConnected回调得到服务端的信息ComponentName,和服务通信接口clientBinder。
这个clientBinder和NoAidlService通过onBind返回的mNoAidlBinder相似,如果是远程服务,就不是同一个对象,可以理解为是通过Binder驱动得到的一个代理ProxyBinder对象,但看起来和mNoAidlBinder是同一个对象,其实不是的。但如果在一个进程,两个就是同一个对象。
clientBinder通过transact指定服务端对应的函数code比如TRANSACTION_studyBinder,来调用服务端的相应函数,通过Binder驱动,最后会调用到服务端NoAidlBinder的onTransact,通过判断code来确定相应的函数,然后通过再将结果返回,这个过程对Client是阻塞性的,所以客户端调用最好是在异步线程和服务端通信。
对服务端,系统会为每个服务提供线程池,这也容易想到,因为不可能让不同的服务为在调用服务时相互等待。
2. 使用AIDL来和Service通信AIDL
AIDL实现IPC通信Demo
AIDL即Android Interface Definition Language,Android接口定义语言。它是一种IDL语言,可以拿来生成用于IPC的代码。
其实就是AIDL文件生成一个帮助类,屏蔽parcel的读写细节,让客户端使用者专注于业务的实现。有一点需要注意的是,如果Service不是在一个新的进程,通过IBinder.queryLocalInterface(DESCRIPTOR)的方式得到本地的Binder对象,也就是和onBind返回的对讲是同一个,就不会涉及到跨进程通信和Binder驱动的调用。这点和不使用AIDL实现IPC是一样的。