Binder浅析
1. 背景知识
Binder在Android系统中是用来进行进程间通信的,所以在介绍Binder之前需要首先明确进程之间的通信,再次之前需要明确一些概念。
进程可以说是操作系统中最为重要的抽象概念,操作系统为了确保进程之间不会产生相互干扰,同时为了便于编写程序,采用了进程隔离的机制,即为每个进程分配独立虚拟地址空间,进程之间感觉不到彼此的存在,感觉自己仿佛占用整个内存空间。这样保证了进程的数据安全,也使得编程更加方便,但是必然存在另外的问题,那就是进程间通信,进程不可能完全独立运行,有时候需要相互通信获取别的进程的运行结果等,因此需要想办法解决进程间通信的问题。
操作系统除了为负责管理和调度进程之外,还需要管理计算机的硬件资源,但是不同进程对硬件资源的使用权限需要得到控制,因此操作系统区分了内核空间和用户空间。部分计算机资源由内核空间的进程负责管理,用户空间的进程无法访问,这种隔离可以起到安全作用。但是用户空间的进程有时候是需要访问一些计算机资源的,这时操作系统的内核就提供系统调用,可以在用户空间中使用,于是就出现了内核态和用户态的概念。用户进程运行在用户空间,此时处于用户态,没有特殊权限,不会对计算机资源造成威胁,如果需要部分的计算机资源可以通过系统调用使得该用户进程陷入到内核态,运行在内核空间,由内核控制访问计算机资源。
明白了系统调用和用户空间,内核空间就能够想到如何进行进程间通信了,既然用户空间的进程之间相互隔离,感觉不到彼此的存在,那么就可以通过操作系统的内核进行进程间通信了。Unix或者Linux系统的传统进程间通信机制比如socket,共享内存,信号量等都是基于内核实现的。Android是基于Linux的操作系统,而Linux内核是可以动态加载内核模块的,即在运行时链接到内核作为内核的一部分运行于内核空间,所以Android系统通过添加一个内核模块,用户空间的进程都是通过这个模块作为桥梁进行进程间通信,这里就出现了第一个与Binder有关的概念,即Binder驱动。
驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;
这里Binder驱动的个人理解是,它作为一个内核模块,管理一块内存,运行在内核空间,并提供进程间通信的功能,如果将这块内存看做是一个硬件的话,这个内核模块就是一个驱动程序,所以称之为Binder驱动。由于水平有限,本文只是对Binder通信机制的应用层次做浅显分析,因此对Binder驱动并不做了解。
2.Binder通信模型
在介绍Binder通信模型之前有一个问题那就是Android为什么选择使用Binder,而不是之前Linux系统中就存在的传统进程间通信机制,网上的资料说是考虑到安全和性能两个方面,由于对Linux系统了解很少,对Socket,共享内存等知之甚少,而且对Binder理解也十分浅显,所以对于Binder在安全和性能两个方面的提升并不理解,这部分需要进一步学习。
Binder通信模型采用了C/S架构,即Client和Server之间通信。在这里需要首先明确Client和Server的概念,他们代表了两种身份,某个进程在进行进程间通信时并不是一直扮演一种身份,而是根据一次进程间通信中,进程是请求某项功能还是提供某种功能扮演着不同身份。此外,这里还有另外两个概念,就是ServiceManager(以下简称SM)和Service,SM是一个守护进程,在Binder通信模型中作为中心的调度者,而Service则是代表Server为Client提供的一项服务,可以理解为一些功能的集合。
所以Binder通信模型可以简单总结为以下的过程,Server向SM注册,它可以提供哪些Service,Client在需要某些功能时向SM发出请求,SM通过查询找到相应Server,此时通过Binder驱动在Client与Server之间建立连接,这样Client与Server就实现了通信。这里需要注意的是,注册和请求过程也是通过Binder通信完成的。也就是说Server在注册的时候它是Client,SM是Server,请求过程同理,在建立连接以后,Server才作为Server身份,Client依然作为Client使用Server提供的Service。
Binder的通信模型很简单,网上的流程简介图也很多,这里就不再贴出来了,由于采用的是C/S架构,那么与计算机网络的应用层必然十分相似。Client和Server很简单,类似于浏览器和服务器,SM则类似于DNS,不过也有一些区别,DNS是负责根据域名查找IP地址,然后传输到相应的服务器则是交由网络层处理,而这里的SM则是根据请求的Service查找到相应的Server,然后则是由Binder驱动负责在Client与Server之间建立连接。最后Service的类比概念也很简单,就像是HTTP,FTP等,也就是Server所能提供的服务。
3. Binder通信原理
上一部分介绍Binder的通信模型中说SM负责在Client与Server之间建立连接,底层由Binder驱动负责,但是在应用层如何建立连接依然不清楚,由于本文是浅析Binder,所以对Binder驱动不做分析,只是对应用层的内容做出分析。
我们都知道Android操作系统是基于Linux内核,底层是C语言实现,是面向过程的语言,而FrameWork层以及应用层则是基于Java语言,Java语言最大的特点就是面向对象的语言,那么在应用层使用进程间通信,如果在进程间传递对象则是最好的方法。那么这里就出现了第二个与Binder相关的概念,即Binder对象,即可以在进程间传递的对象。但是之前说进程间通信底层是由Binder驱动负责,Binder驱动是运行在内核空间的内核模块,那么肯定是由C语言实现的,并不支持对象的传递,除非是可以序列化的对象,那么如果在应用层中屏蔽底层细节,建立一种可以传递对象的假象呢,那么就是使用代理,就是使用BinderProxy。
Binder对象是Server的本地对象,可以提供某项功能或者说可以作为某个Service,当Client需要某个服务向SM查询时,SM找到对应的Server,由Binder驱动向Client进程提供这个Binder对应的BinderProxy,Client进程可以拿到BinderProxy对象,就像是一个对Binder对象的引用,仿佛Binder对象从Server进程传递到了Client进程。其实Binder本地对象只有一个,而有很多的代理处在不同的Client进程中,Client进程通过调用BinderProxy方法就可以使用Server在Binder中实现的功能,中间的传递则是由Binder驱动完成,所以Binder驱动只需要负责Binder与BinderProxy之间的通信即可,这样Binder看起来就像是可以传递的对象。不过这里有一个问题,就是有很多BinderProxy的情况下,如果做同步与互斥,还需要进一步学习。
4. AIDL分析
上面概念扯了一堆,该分析一点实际的代码了,即在Android开发过程中进程间通信通常使用的AIDL语言。但是在介绍使用AIDL之前还是需要啰嗦几个概念,即在Java中的IBinder, Binder, BinderProxy, IInterface, Stub, StubProxy等几个涉及到的类。
首先是第一个接口IBinder, 它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。
Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。
然后第二个接口IInterface,代表Server所提供的功能或者说是服务。在Server中定义的功能接口都需要扩展这个接口。
最后是Stub和Stub.Proxy。Stub是在AIDL中自动生成的类,它的字面意思是存根,它继承自Binder,同时实现了IInterface的接口,有点使用的适配器模式的意思,即代表了可以在进程间传递的对象,同时又可以提供对应的功能。而Stub.Proxy则是Stub的一个内部类,虽然看名字像是Stub的代理,其实这里并没有使用代理模式,Stub.Proxy中,个人理解也有点使用适配器模式的意思,而它并没有继承, 而是使用了组合的方式,即持有一个BinderProxy, 因为是BinderProxy,所以名字是StubProxy,而不是只它是Stub的代理。这两个类在这里说过于空虚,在后面的AIDL的生成代码中再做详细介绍。
首先是定义自己的AIDL文件
interface IMyAidlInterface {
void myMethod();
}
然后在生成的文件中找到IMyAidlInterface对应的Java文件,本文主要是对生成的文件进行分析,从而理解Binder通信机制。
下面的代码中是对生成的代码做了简化,便于阅读
public interface IMyAidlInterface extends IInterface{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends Binder implements IMyAidlInterface{
private static final java.lang.String DESCRIPTOR = "com.example.androidlearning.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub(){
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.androidlearning.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static IMyAidlInterface asInterface(IBinder obj){
if ((obj==null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof IMyAidlInterface))) {
return ((IMyAidlInterface)iin);
}
return new Stub.Proxy(obj);
}
@Override public IBinder asBinder(){
return this;
}
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
switch (code){
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_myMethod:
{
data.enforceInterface(DESCRIPTOR);
this.myMethod();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements IMyAidlInterface{
private IBinder mRemote;
Proxy(IBinder remote){
mRemote = remote;
}
@Override public IBinder asBinder(){
return mRemote;
}
public String getInterfaceDescriptor(){
return DESCRIPTOR;
}
@Override public void myMethod() throws RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_myMethod, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_myMethod = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void myMethod() throws android.os.RemoteException;
}
这里首先看一下生成代码的主体结构,IMyAidlInterface扩展IInterface,而其内部类Stub继承Binder,同时实现IMyAidlInterface,并且Stub是一个抽象类,我们所定义的功能方法作为抽象方法,留由子类实现。Stub的一个子类Stub.Proxy则是使用另一种适配器模式的方法,即组合方式包含BinderProxy, 同时实现IMyAidlInterface, 而其他具体代码后面再做分析,我们先看Client与Server如何通过Binder完成通信。
首先看Server进程中的代码,在Service中:
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mStub;
}
private IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {
@Override
public void myMethod() throws RemoteException {
Log.d("recluse", "log from remote process");
}
};
}
这里使用匿名内部类扩展了IMyAidlInterface.Stub,并实现了我们所定义的功能方法,这里mStub就是Server进程中的本地Binder对象。在onBinder 中返回,当有客户端调用bindService()方法时,系统会回到onBind方法。
再看Client中的代码,在Activity中:
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface mAidlInterface;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyService.class);
bindService(intent, mConnection, 0);
try {
mAidlInterface.myMethod();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
这里的ServiceConnection也是使用的内部类实现,为了代码简便。这样在连接成功以后,就可以获取IMyAidlInterface.Stub对象,它实现了IMyInterface接口,在Client进程中就可以使用我们所定义的功能方法,而该方法则是在Server进程中的Service中定义的Stub中实现的,从而完成了进程间调用。那么下面就根据这三段代码分析内部如何完成进程间通信的。
首先从bindService()方法开始,Client进程就会想SM请求查询对应的Service,SM找到Server以后,系统回调Service的onBinde()方法,返回Server进程中的Binder对象,也就是我们所定义的IMyInterface.Stub对象,而由于Activity所在的Client进程和Service所在的Server进程并不是同一个进程,在Activity里面的ServiceConnection的onServiceConnection()方法被系统回调时则则传递进去Binder本地对象的代理,即BinderProxy对象。此时我们再看Stub提供的静态方法asInterface(),该方法的功能就是根据Binder对象,返回我们想要的实现我们定义接口,也就是IMyAidlInterface的对象,在该方法中,
/**
* Cast an IBinder object into an com.example.androidlearning.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static IMyAidlInterface asInterface(IBinder obj){
if ((obj==null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof IMyAidlInterface))) {
return ((IMyAidlInterface)iin);
}
return new Stub.Proxy(obj);
}
DESCRIPTOR是一个字符串,是Binder的唯一标识。在这个方法中首先查找本地Binder对象,如果没有,说明传递进来的IBinder是一个BinderProxy对象,此时返回一个Stub.Proxy对象,下面我们来看Stub.Proxy的代码:
private static class Proxy implements IMyAidlInterface{
private IBinder mRemote;
Proxy(IBinder remote){
mRemote = remote;
}
@Override public IBinder asBinder(){
return mRemote;
}
public String getInterfaceDescriptor(){
return DESCRIPTOR;
}
@Override public void myMethod() throws RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_myMethod, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
Stub.Proxy同样实现了我们定义的功能接口,而且包含一个BinderProxy对象,当我们在Client进程中调用我们所定义的功能方法时,其实就是调用Stub.Proxy中实现的方法,在实现该功能方法时,它首先将参数序列化,然后调用BinderProxy的transact()方法,调用该方法以后,Binder驱动会唤醒Server进程中的本地Binder对象, 并调用它的onTransact()方法,这个过程有Binder驱动实现,暂时还没有学习到。最后Binder负责将返回数据序列化返回该方法,写入到_reply中。下面看Stub的onTransact()方法:
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
switch (code){
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_myMethod:
{
data.enforceInterface(DESCRIPTOR);
this.myMethod();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
在Stub类的代码的最后我们看到定义了TRANSACTION_myMethod,是一个整型,也就是说在Binder中对每一个方法都进行了编号,在transact()方法中传入编号,然后在onTransact()方法中,根据请求的变化调用相应的方法。这里我们看到data接收参数,然后调用本地Binder中定义的功能方法,这里是抽象方法,留有子类实现,最后将结果写入到_reply中,由Binder驱动负责将返回值传递到BinderProxy的transact()方法中的_reply。到此我们就完成了对于进程间调用的整个过程,分析完了生成的代码,最后只剩下asBinder方法,这是IInterface中定义的方法,它负责返回对应的IBinder。这里有IBinder和IInterface两个接口,其中IBinder有两个实现类Binder和BinderProxy, 所以一个需要继承或者组合,一个需要实现,当然对于IInterface的实现也可以通过定义其他类实现并组合到Stub或者Stub.Proxy类中,总之具有两个接口的功能即可。AIDL的生成代码中,对于IBinder的处理,Stub使用继承,可以理解,因为需要回调,Stub.Proxy则是使用了组合,其实继承也可以,但是有什么坏处暂时还不明白。对IInterface则是统一处理,这样也最简单。
连接过程可以总结为Client调用bindService, 系统回调Server进程中Service的onBind(), 系统根据onBind()中返回的Binder,返回给Client进程一个对应的BinderProxy, 这个是在onServiceConnected()回调方法中传递到Client进程。这样Client进程持有BinderProxy对象,也就表示与Server建立连接。Client用拿到的BinderProxy或者Binder本地对象(这种是绑定本地服务,不需要进程间通信,如asInterface中,直接返回了自身), 调用静态方法asInterface()将IBinder对象转换成为我们所定义的接口对象,该对象可能继承自Binder或BinderProxy, 也可能组合的方式持有Binder或BinderProxy。这个IBinder对象就像是传到Client进程中了,同时还可以调用功能方法。
调用过程可以总结为, 在Client进程中使用asInterface()返回的对象调用功能方法,如果是进程间通信,则是序列化参数并调用BinderProxy的transact()方法,有Binder驱动完成中间过程,然后回调本地Binder的onTransact()方法,在该方法中完成远程方法的调用,并将结果写会,有Binder驱动再完成中间过程,将写过传回到BinderProxy的transact()方法中,从而完成一次进程间通信。
至此,Binder进程间通信的应用层的机制已经分析结束,关于Binder涉及到三个概念,第一个就是Binder驱动,运行在内核中,是进程间通信的关键,第二个是Binder类,通过代理模式,在BinderProxy和Binder中建立连接,造成一种可以在进程间传递对象的假象,从而使得Binder对象仿佛变成可以传递的对象。第三个就是Binder进程间通信机制,其实本文所介绍的整个可以称之为Binder通信机制。Binder之所以复杂,可能就是因为它的多重身份吧。
网友评论