作者:桂志宏
前面讨论过利用Messenger来进行进程间通信,这一节介绍另一种进程间通信的方法–AIDL
AIDL
AIDL的完整表述:Android Interface Definition Language 也就是android接口定义语言,它的本质是系统为我们提供的一种快速实现Binder的工具。
上面讲述利用Messenger来进行IPC通信,但是Messenger是以串行的方式来处理消息,如果有大量的并发请求,Messenger就不合适,另一方面,如果我们需要跨进程调用服务端的方法,Messenger就无法实现,因此这里需要借助AIDL来实现。
流程
我们先来看一下使用AIDL的流程,分为服务端和客户端。
对于服务端,首先需要创建一个Service,用来监听客户端的请求。然后创建AIDL文件,在AIDL文件中声明供客户端调用的接口,最后在Service中实现这个接口。
对于客户端,首先需要利用bindService来绑定Service,绑定成功后,利用asInterface将服务端返回的Binder转换为客户端需要的AIDL类型对象,接着就可以利用该对象来调用之前声明在AIDL文件中的方法,而该方法是运行在服务端的Binder线程池中。
上面的文字描述还是抽象,加入客户端需要在服务端计算矩阵特征值,因此我们需要创建IMatrixEgenValue.aidl文件,该文件的内容如下:
interface IMatrixEgenValue{
double egenValue(Matrix matrix);
}
其中egenValue就是我们具体计算特征值的函数,我们需要在服务端实现它,这里有两种做法:1)新建一个Java类IMatrixEgenValueImpl去继承IMatrixEgenValue.Stub(Stub继承Binder),如下
public class IMatrixEgenValueImpl extends IMatrixEgenValue.Stub{
@Override
public double egenValue(Matrix matrix) throw RemoteException{
...//具体实现省略
}
}
另一种做法是2)直接创建.Stub对象,如下
Binder binder=new IMatrixEgenValue.Stub(){
@Override
public double egenValue(Matrix matrix) throw RemoteException{
...//具体实现省略
}
};
不管利用上面哪种方式实现,都要在服务端中获取IMatrixEgenValue.Stub对象,该对象是一个Binder,在Service的onBind方法中返回的就是IMatrixEgenValue.Stub对象。服务端代码如下(采用第一种方式实现egenValue):
public class MatrixCaculationService extends Service{
private Binder mBinder=new IMatrixEgenValueImpl();
@Override
public void onCreate(){
...
}
@Override
public IBinder onBind(Intent intent){
...
return mBinder;
}
}
上面就是服务端的流程。下面给出客户端的流程
在Activity中创建ServiceConnection对象,在onServiceConnected方法中利用服务端返回的Binder对象获得IMatrixEgenValue对象,然后就可以利用该对象调用egenValue方法,而该方法是运行在服务端Binder线程池中,这样就实现了客户端调用服务端方法,主要代码如下:
public class MainActivity extends Activity{
...
private ServiceConnection mConnection=new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName className,IBinder service){
...
new Thread(new Runnable(){
@Override
public void run() {
...
IMatrixEgenValue matrixEgenValue=IMatrixEgenValue.Stub.asInterface(service);
matrixEgenValue.egenValue(mMatrix);
...
}
})
...
}
}
@Override
public void onCreate(Bundle savedInstanceState){
...
Intent intent=new Intent(this,MatrixCaculationService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
...
}
}
这里需要指出的是,我们新开启了一个线程,然后在该线程中调用egenValue方法,其原因在于远程调用egenValue方法可能很耗时,因此不能在UI线程中调用。
以上就是AIDL最基本的使用流程,实际上AIDL远不止这么简单,需要进一步探索。
下面对AIDL的使用做一些说明:
1)支持的数据类型:
AIDL文件并不是支持所有数据类型,它所支持的数据类型有: 基本数据类型(int,long,char,boolean,double,float等), String, CharSequence, ArrayList(不是所有List都支持), HashMap(不是所有Map都支持), 所有实现Parcelable接口的自定义对象, AIDL(即AIDL接口本身也可以在AIDL文件中使用)
(对象是不能直接在进程间传输,进程间对象的传输的本质就是反序列化,因此只有实现Parcelable的对象才能在进程间传输)
2)如果AIDL文件中使用了自定义的Parcelable对象,因此必须新建一个同名的AIDL文件,并且在文件中声明该Parcelable对象。
Binder连接池
前面讲述了利用AIDL来进行进程间通信的流程,如果存在很多的客户端都需要和服务端进行通信,难道要用AIDL一个一个建立服务?答案当然是否定的。 我们只用建立一个服务端,然后不同的业务模块向服务端提供唯一标识和其对应的Binder对象。其工作原理图如下:
接下来以一个具体的例子来说明。
加入公司需要开发三个业务模块,这三个业务模块互不影响,都需要和服务端进行通信,若这三个业务模块取名为ModuleA,ModuleB和ModuleC,因此我们先新建这三个业务模块的AIDL文件分别为ModuleA.AIDL, ModuleB.AIDL和ModuleC.AIDL。其代码如下:
(ModuleA.AIDL)
interface ModuleA{
void doTaskA(); //ModuleA需要执行的任务逻辑
}
(ModuleB.AIDL)
interface ModuleB{
void doTaskB(); //ModuleB需要执行的任务逻辑
}
(ModuleC.AIDL)
interface ModuleC{
void doTaskC(); //ModuleC需要执行的任务逻辑
}
接下来就要实现上面三个接口,新建ModuleAImpl,ModuleBImpl和ModuleCImpl三个java类,代码如下
public class ModuleAImpl extends ModuelA.Stub{
@Override
public void doTaskA(){
....
}
}
//
public class ModuleBImpl extends ModuelB.Stub{
@Override
public void doTaskB(){
....
}
}
//
public class ModuleCImpl extends ModuelC.Stub{
@Override
public void doTaskC(){
....
}
}
我们建立服务端BinderPoolService,代码如下:
public class BinderPoolService extends Service{
private Binder mBinderPool=new BinderPool.BinderPoolImpl();
@Override
public void onCreate(){
super.onCreate();
}
@Override
public IBinder onBind(Intent intent){
return mBinderPool;
}
}
接下来就要实现Binder连接池,我们新建IBinderPool.AIDL文件,文件中声明获取相应Binder对象的方法,其代码如下:
(IBinderPool.AIDL)
interface IBinderPool{
IBinder queryBinder(int binderCode);
}
通过binderCode来确定返回上述三个业务中哪个业务模块的Binder对象。接下就是实现上述接口,我们新建一个BinderPool.java的class,不过我们不直接继承IBinderPool.Stub,而是选择在BinderPool内部新建一个内部类来继承IBinderPool.Stub并实现queryBinder方法。我们希望客户端获取BinderPool实例时就连接上服务端,客户端只用通过获取相应的Binder,并将其转化为相应的AIDL对象(还记得前面的asInterface方法吗),然后就可以直接调用运行在服务端的方法(注意,若方法耗时,需要新开一个Thread,然后在子线程中调用),因此我们在BinderPool的构造方法中需要连接服务,BinderPool的代码如下:
(BinderPool.java)
public class BinderPool{
private Context mContext;
private IBinderPool mBinderPool;
//将sInstance声明为volatile是因为这里涉及到多线程并发访问服务端,需要对BinderPool对象加Lock
private static volatile BinderPool sInstance;
//先声明对应三个模块的binderCode,以便返回相应的Binder对象
public static final int MODULEA_BINDER_CODE=0;
public static final int MODUELB_BINDER_CODE=1;
public static final int MODULEC_BINDER_CODE=2;
//创建ServiceConnection实例
private ServiceConnection mBinderPoolConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool=IBinderPool.Stub.asInterface(service);
try{
/**这里给BinderPool设置死亡代理*/
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient,0);
}catch(RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
//接下提供获取BinderPool单例的方法,这里涉及到设计模式的内容
//将构造器私有化
private BinderPool(Context context){
mContext=context.getApplicationContext();
connectBinderPoolService(); //连接BinderPoolService
}
public static BinderPool getInstance(Context context){
if (sInstance==null){
synchronized(BinderPool.class){
if (sInstance==null){
sInstance=new BinderPool(context);
}
}
}
return sInstance;
}
//连接Service
private void connectBinderPoolService(){
Intent service=new Intent(mContext, BinderPoolService.class);
mContext.bindService(service,mBinderPoolConnection,Context.BIND_AUTO_CREATE);
}
//对Binder进行监听,当Binder死亡(一般是服务端进程意外停止)时,会回调binderDied方法,所以在这里重新连接Service
private IBinder.DeathRecipient mBinderPoolDeathRecipient=new IBinder.DeathRecipient(){
@Override
public void binderDied(){
Log.w(TAG,"binder died");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient,0);
mBinderPool=null;
connectBinderPoolService();
}
};
/**通过BinderPool对象获取相应的Binder对象,这里借助BinderPoolImpl的queryBinder*/
public IBinder queryBinder(int binderCode){
IBinder binder=null;
try{
if(mBinderPool!=null){
binder=mBinderPool.queryBinder(binderCode);
}
}catch(RemoteException e){
e.printStackTrace();
}
return binder;
}
//实现IBinderPool接口
public static class BinderPoolImpl extends IBinderPool.Stub{
public IBinder queryBinder(int binderCode){
IBinder binder=null;
switch(binderCode){
case MODULEA_BINDER_CODE:
binder=new ModuleAImpl()();
break;
case MODULEB_BINDER_CODE:
binder=new ModuleBImpl()();
break;
case MODULEC_BINDER_CODE:
binder=new ModuleCImpl();
break;
default:
break;
}
return binder;
}
}
}
上面是BinderPool的完整代码,这里梳理一下流程,当我们在客户端获取BinderPool实例时,这时要建立和服务端的连接,因此调用bindService,相应的就要创建ServiceConnection对象,当服务连接后,就要将服务端返回的Binder装换为IBinderPool.AIDL接口的对象mBinderPool,然后为了保证服务的健壮,当Binder死亡时能够自动重新连接服务,因此需要给Binder设置死亡代理,因此牵扯出DeathRecipient,进而创建DeathRecipient对象,重写binderDied方法,在该方法中重新连接Service。当我们在客户端得到BinderPool实例后,接下来就要获取相应的Binder对象,因此在BinderPool类中需要借助对象mBinderPool的queryBinder方法返回相应的Binder对象。获取Binder后,接下就要根据相应的模块将Binder转换为相应AIDL接口对象,然后利用该对象就可以远程调用远程方法。 我们以客户端执行模块B的任务为例,客户端的代码如下:
......
BinderPool binderPool=BinderPool.getInstance(MainActivity.this);
Binder binder=binderPool.queryBinder(BinderPool.MODULEB_BINDER_CODE);
ModuleB moduleB=ModuleBImpl.asInterface(binder);
moduleB.doTaskB();
......
上面调用代码最好放在子线程中执行。
至此,Binder连接池的原理和使用流程就介绍完毕了。但其实还有更多难点需要我们进一步探索