本文首先概述了Android的进程间通信的Binder机制,然后结合一个AIDL的例子,对Binder机制进行了解析。
概述
我们知道,在Android app中的众多activity,service等组件可以运行在同一进程中,也可以运行在不同进程中。当组件运行在同一进程中进行通信就显得比较简单,在之前的Android线程间通信机制中已经讲过了;而当它们运行在不同的进程?中时,就需要使用我们本文中所要介绍的Binder机制了。
Binder作为一种进程间通信机制,负责提供远程调用的功能(RPC),它的系统组件主要包括四种:Client, Server, ServiceManager, Binder Driver. 它们之间的关系如下图所示:

从图中我们可以看出,Client, Server, ServiceManager运行在系统的用户态,而Binder Driver运行在内核态。为了完成Client端到Server端的通信任务,用户空间的需要操作Binder Driver提供的/dev/binder文件来完成交互。那么ServiceManager的工作是什么呢?ServiceManager负责管理Server并向Client端提供一个Server的代理接口(proxy)。通过代理接口中定义的方法,Client端就可以使用Server端提供的服务了。整个过程如下:
- Client端调用代理接口的方法,将Client的参数打包为parcel对象发送给内核空间中BinderDriver;
- Server端读取到BinderDriver中的请求数据,将parcel对象解包并处理;
- 处理好后,将处理结果打包返回给BinderDriver,再交给Client端。
另外,Client端与Server端的调用过程是同步的,即在Server返回结果之前,Client端是阻塞的。调用过程如下所示:

OK,下面我们通过AIDL的一个例子来分析一下以上过程时如何进行的。
AIDL小栗子
首先,创建一个aidl文件“ICalculateAIDL.aidl”,这个接口里面定义了要对外提供的服务,我们这里定义了计算加法和减法的函数:
package?com.cqumonk.calculate.aidl;
interface?ICalculateAIDL?{
????int?add(int?a,int?b);????
????int?minus(int?a,int?b);
}
创建好后,rebuild一下我们的项目,可以生成同名的java文件。这是一个接口,里面包含了一个名为Stub的静态抽象类,以及我们在aidl文件中定义的加减法函数。?里面详细代码在后面分析,接口文件结构如下:

然后我们需要实现服务端方面的功能,创建一个service,我们顺手把生命周期中各方法都打印出来,并完成加法和减法函数的实现:
public?class?CalculateService?extends?Service?{??
??private?static?final?String?TAG="SERVER";??
??public?CalculateService()?{??
??}??
??@Override??
??public?void?onCreate()?{????
????super.onCreate();????
????Log.e(TAG,"OnCreate");??
??}??
??@Override??
??public?int?onStartCommand(Intent?intent,?int?flags,?int?startId)?{????
????Log.e(TAG,"onStartCommand");????
????return?super.onStartCommand(intent,?flags,?startId);??
??}??
??@Override??
??public?IBinder?onBind(Intent?intent)?{????
????Log.e(TAG,"onBind");????
????return?mBinder;??
??}??
??@Override??
??public?void?onDestroy()?{????
????super.onDestroy();????
????Log.e(TAG,"onDestroy");??
??}??
??@Override??
??public?boolean?onUnbind(Intent?intent)?{????
????Log.e(TAG,"onUnbind");????
????return?super.onUnbind(intent);??
??}??
??@Override??
??public?void?onRebind(Intent?intent)?{????
????Log.e(TAG,"onRebind");????
????super.onRebind(intent);??
??}??
??private?final?ICalculateAIDL.Stub?mBinder=new?ICalculateAIDL.Stub(){????
????@Override????
????public?int?add(int?a,?int?b)?throws?RemoteException?{??????
??????return?a+b;????
????}????
????@Override????
????public?int?minus(int?a,?int?b)?throws?RemoteException?{??????
??????return?a-b;????
????}??
??};
}
在service中,我们使用刚刚生成的ICalculateAIDL.Stub静态抽象类创建了一个Binder对象,实现了其中有关计算的业务方法,并在onBind方法中返回它。另外我们还需要在manifest文件中对该服务进行注册:
<service?? ??android:name=".CalculateService"?? ??android:enabled="true"?? ??android:exported="true"?>?? ??<intent-filter>???? ????<action?android:name="com.cqumonk.adil.calculate"/>???? ????<category?android:name="android.intent.category.DEFAULT"/>?? ??</intent-filter> </service>
这时候,我们的服务端工作就完成了。我们开始编写客户端的代码来调用这个服务。首先,我们定义一个activity,包含四个按钮:

然后我们可以在activity中启动之前定义好的service并调用它所提供的服务:
public?class?MainActivity?extends?Activity?implements?View.OnClickListener?{?
??Button?mBind;?
??Button?mUnbind;??
??Button?mAdd;??
??Button?mMinus;??
??TextView?txt_res;??
??private?static?final?String?TAG="CLIENT";??
??private?ICalculateAIDL?mCalculateAIDL;?
??private?boolean?binded=false;?
??@Override??
??protected?void?onCreate(Bundle?savedInstanceState)?{????
????super.onCreate(savedInstanceState);????
????setContentView(R.layout.activity_main);????
????mBind=?(Button)?findViewById(R.id.btn_bind);????
????mUnbind=?(Button)?findViewById(R.id.btn_unbind);????
????mAdd=?(Button)?findViewById(R.id.btn_add);????
????mMinus=?(Button)?findViewById(R.id.btn_minus);????
????txt_res=?(TextView)?findViewById(R.id.txt_res);????
????mBind.setOnClickListener(this);????
????mUnbind.setOnClickListener(this);????
????mAdd.setOnClickListener(this);????
????mMinus.setOnClickListener(this);??
??}?
??@Override??
??protected?void?onStop()?{????
????super.onStop();???
????unbind();??
??}??
??private?void?unbind(){????
????if?(binded){??????
??????unbindService(mConnection);??????
??????binded=false;????
????}??
??}??
??@Override??
??public?void?onClick(View?v)?{????
????int?id=v.getId();???
????switch?(id){??????
??????case?R.id.btn_bind:????????
????????Intent?intent=new?Intent();???????
????????intent.setAction("com.cqumonk.adil.calculate");????????
????????bindService(intent,mConnection,?Context.BIND_AUTO_CREATE);????????
????????break;??????
??????case?R.id.btn_unbind:????????
????????unbind();????????
????????break;??????
??????case?R.id.btn_add:????????
????????if(mCalculateAIDL!=null){??????????
??????????try?{????????????
????????????int?res=mCalculateAIDL.add(3,3);????????????
????????????txt_res.setText(res+"");??????????
??????????}?catch?(RemoteException?e)?{????????????
????????????e.printStackTrace();??????????
??????????}????????
????????}else{??????????
??????????Toast.makeText(this,"please?rebind",Toast.LENGTH_SHORT).show();????????
????????}????????
????????break;??????
??????case?R.id.btn_minus:????????
????????if(mCalculateAIDL!=null){??????????
??????????try?{???????????
??????????int?res=mCalculateAIDL.minus(9,4);???????????
??????????txt_res.setText(res+"");??????????
?????????}catch?(RemoteException?e)?{????????????
??????????e.printStackTrace();?????????
?????????}????????
???????}else{?????????
?????????Toast.makeText(this,"please?rebind",Toast.LENGTH_SHORT).show();????????
???????}????????
???????break;????
????}??
??}?
??private?ServiceConnection?mConnection=new?ServiceConnection()?{????
????@Override????
????public?void?onServiceConnected(ComponentName?name,?IBinder?service)?{??????
??????Log.e(TAG,"connect");??????
??????binded=true;??????
??????mCalculateAIDL=ICalculateAIDL.Stub.asInterface(service);????
????}????
????@Override????
????public?void?onServiceDisconnected(ComponentName?name)?{?????
??????Log.e(TAG,"disconnect");??????
??????mCalculateAIDL=null;??????
??????binded=false;????
????}??
??};
}
当我们点击绑定按钮,观察日志:

点击加法和减法按钮,都可以完成计算并返回值。然后点击解绑按钮:

我们并未发现有连接断开的日志打印,这时候,我们继续点击加减法按钮,发现仍然可以完成计算功能,这又是为毛呢?我们看到unbind和destroy的日志打印,说明连接已经断开,service已经被销毁,但是我们返回的stub对象仍然是可以继续使用的。而并不是说service仍然在运行。
过程分析
首先,我们调用bindservice方法来启动了service:
一方面连接成功时调用了serviceConnection的onServiceConnected方法。我们从该方法中获取到一个Binder对象(注意,这个binder并不是我们在service中实现的那个哦。当service时本apk中的service时,这里返回的是同一个binder),我们通过此binder来与server进行通信。为了区分,我们称为clientBinder。
public?void?onServiceConnected(ComponentName?name,?IBinder?service)?{
????????????Log.e(TAG,"connect");
????????????binded=true;
????????????mCalculateAIDL=?ICalculateAIDL.Stub.asInterface(service);???????
}
另一方面,onBind方法返回了我们实现的Stub对象,其实也是一个Binder,用于和Client进行通信。?(之前我们定义了一个aidl接口文件,并根据它生成了ICalculateAIDL接口。这个接口中有我们定义的两个方法以及一个?静态抽象内部类Stub,它是一个Binder的子类。)我们来看一下Stub是如何定义的:
public?static?abstract?class?Stub?extends?android.os.Binder?implements?com.cqumonk.calculate.aidl.ICalculateAIDL
它继承了Binder类也实现了ICalculateAIDL接口,?我们实现了定义的add和minus方法。我们仍然要强调一下它并不是clientBinder,在负责与clientBinder进行通信交互的同时,它也维护了service描述符与服务端service的映射。
我们在Client中的onServiceConnected里调用了stub对象的asInterface方法,并将之前得到的clientBinder传入:
public?static?com.cqumonk.calculate.aidl.ICalculateAIDL?asInterface(android.os.IBinder?obj)?{????????????if?((obj?==?null))?{????????????????
??????????return?null;
????????????}
????????????android.os.IInterface?iin?=?obj.queryLocalInterface(DESCRIPTOR);//根据包名获取本地实现的一个接口的实例,如果是本地service则可以获取到
????????????if?(((iin?!=?null)?&&?(iin?instanceof?com.cqumonk.calculate.aidl.ICalculateAIDL)))?{????????????????????return?((com.cqumonk.calculate.aidl.ICalculateAIDL)?iin);??//如果得到的实例是ICalculateAIDL的对象,则返回
????????????}????????????
????????????return?new?com.cqumonk.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj);//如果无法得到本地实现的对象则会返回一个代理对象}
在这个方法中,首先在系统中查找注册的的service,如果没有找到,那么一定是别的apk实现的service,于是返回一个此service的静态代理类对象供Client调用。?我们来看一下这个代理,创建时我们将clientBinder传入了,同时也它实现了ICalculateAIDL接口:
private?static?class?Proxy?implements?com.cqumonk.calculate.aidl.ICalculateAIDL?{??????
??????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;??????}??????
??????@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);??????????
??????????_data.writeInt(a);??//将参数打包?????????
??????????_data.writeInt(b);??????????
??????????mRemote.transact(Stub.TRANSACTION_add,?_data,?_reply,?0);??//调用binderDriver的提供的方法将参数发给服务端??????????
??????????_reply.readException();??????????
??????????_result?=?_reply.readInt();??//读取到返回结果????????
????????}?finally?{??????????
??????????_reply.recycle();??????????
??????????_data.recycle();????????
????????}????????
????????return?_result;??????
???????}??????
???????@Override??????
???????public?int?minus(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);??????????
???????????_data.writeInt(a);??????????
???????????_data.writeInt(b);??????????
???????????mRemote.transact(Stub.TRANSACTION_minus,?_data,?_reply,?0);??????????
???????????_reply.readException();??????????
???????????_result?=?_reply.readInt();????????
?????????}?finally?{??????????
???????????_reply.recycle();?????????
???????????_data.recycle();????????
?????????}????????
?????????return?_result;??????
???????}????
?????}
代理中也实现了ICalculateAIDL接口定义的方法,我们以add方法为例,里面将参数打包发送给Server端。在Server端收到请求后,会调用service中我们实现的那个stub对象(mBinder)的onTransact方法:
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();?????????
?????????int?_result?=?this.add(_arg0,?_arg1);?//调用我们实现好的方法??????????
?????????reply.writeNoException();??????????
?????????reply.writeInt(_result);?//把结果返回??????????
?????????return?true;????????
???????}????????
???????case?TRANSACTION_minus:?{??????????
?????????data.enforceInterface(DESCRIPTOR);??????????
?????????int?_arg0;??????????
?????????_arg0?=?data.readInt();??????????
?????????int?_arg1;??????????
?????????_arg1?=?data.readInt();??????????
?????????int?_result?=?this.minus(_arg0,?_arg1);??????????
?????????reply.writeNoException();??????????
?????????reply.writeInt(_result);??????????
?????????return?true;???????
????????}??????
??????}??????
??????return?super.onTransact(code,?data,?reply,?flags);????
????}
调用完成后把结果打包返回给Poxy处理,最后返回给客户端。
总结
由上面的例子我们可以看出,在跨进程通信的时候,Client端使用的Poxy里面封装了一个binder与Server端的stub(也是一个binder对象)进行交互,两个binder作为接口调用BinderDriver的transact来发送数据包,以及onTransact接收处理数据包。
通过结合AIDL例子,我们对Android进程间的通信机制进行了分析,如果有错误的地方,欢迎指正。
万类霜天茎自由