前面一篇文章对Binder的机制进行了总结,但作为一个应用层开发者,其实很少能使用到Binder相关的技术。实际上Android framework为我们封装了Binder,使我们只需要轻松编写Java代码就可以进程间通信了,这个神奇的封装就是AIDL。相信有不少开发者对这个名字都非常熟悉,但让他来讲一下AIDL具体机制和使用场景可能又说不清,那么我们今天就来聊一聊AIDL。 轉自 http://liuzhiyong.org/2015/11/12/AndroidAIDL/
Android IPC 为何设计AIDL
- AIDL是android为了方便开发者进行进程间通信,定义的一种语言。
- Android有一个跨进程间通信的工具Messenger,那为什么还设计AIDL呢?原因是AIDL可以处理多线程、多客户端并发访问的,而Messenger只能是单线程处理。
- Binder可以处理多线程、多客户端并发访问的进程间通信,那为什么还设计AIDL呢?实际上AIDL就是把Binder通讯的一些细节封装了起来,使得我们在JAVA层写IPC的代码时,不用考虑write、read、transact等操作,只需要把注意力专注在自身定义的接口上即可。
其实Messenger、AIDL、Binder这三个东西的关系是这样的: AIDL是对Binder的封装,Messenger是对AIDL的封装,就是为了更好的方便开发者在不同场景下使用进程间通信。
这么讲还是有点晕吧,这篇文章就带大家看看AIDL到底帮我做了哪些事情,以为我们如何更好的使用AIDL。
AIDL的概述
AIDL的基本概念
- AIDL语言: android interface definition language, Android接口描述语言,是接口描述语言中的一种。
- 封装: Android framework将Binder封装的几乎不可见了,应用开发者接触到的IPC大部分都是AIDL。
- 面向接口: AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。
- CS模型: AIDL和Binder一样,采用的是Client-Server的通信模型。
- proxy模式: 也和Binder一样,采用的是proxy设计模式,使用代理类在客户端和实现端传递数据。
AIDL的使用场景
官方文档特别提醒我们何时使用AIDL是必要的:你允许不同的客户端为了进程间的通信而去访问你的service,以及你想在service中处理多线程。这种场景才需要用到AIDL。
1. 定义AIDL接口:
在Server端用AIDL语言定义接口。和普通的接口内容没有什么特别,只是它的扩展名为.aidl,保存在src目录下。Android SDK tools就会在gen目录自动生成一个IBinder接口文件(java文件)。
2. 实现这些接口:
Server端实现这个AIDL中的所有接口。
3. 调用这些接口:
客户端程序要copy这个aidl文件到自己的src目录,通过代码绑定service并在IPC时从proxy中调用方法。
AIDL的注意事项
- 对于Java编程语言的基本数据类型 (int, long, char, boolean等),String和CharSequence,集合接口类型List和Map,不需要import 语句。
- 如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。
- 对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
- AIDL只支持接口方法,不能公开static变量。
如何使用AIDL
创建AIDL文件
package com.liuzhiyong.calculate.aidl; interface ICalculateAIDL { int add(int a,int b); int minus(int a,int b); } |
创建一个aidl文件“ICalculateAIDL.aidl”,这个接口里面定义了要对外提供的服务,我们这里定义了计算加法和减法的函数。
Server端实现
public class CalculateService extends Service { private static final String TAG="SERVER"; public CalculateService() { } ...... @Override public IBinder onBind(Intent intent) { Log.e(TAG,"onBind"); return mBinder; } ...... 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的mBinder对象(这个对象非常重要,我们后面再来讨论),并在onBind方法中返回它,这个mBinder完成了加法和减法函数的实现。
Client端实现
-
连接Server:
//连接Service的回调 private ServiceConnection mConnection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder binder) { Log.e(TAG,"connect"); binded=true; mCalculateAIDL=ICalculateAIDL.Stub.asInterface(binder); } @Override public void onServiceDisconnected(ComponentName name) { Log.e(TAG,"disconnect"); mCalculateAIDL=null; binded=false; } };
-
调用Server端的方法:
@Override public void onClick(View v) { int id=v.getId(); switch (id){ case R.id.btn_bind: //绑定service Intent intent=new Intent(); intent.setAction("com.liuzhiyong.adil.calculate"); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); break; case R.id.btn_unbind: //解绑service if (binded){ unbindService(mConnection); binded=false; } break; case R.id.btn_add: //调用server端的加法 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://调用server端的减法 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; } }
这段Client连接service方法应该很好理解,就不废话了。
看到了么,使用AIDL来IPC,客户端和服务端就这么点代码。
下面我们就进入AIDL真正核心的机制分析。
AIDL的机制分析
上一章是个AIDL很简单的示例用法,并没有对其调用过程做分析,下面我们就开始从客户端的调用开始分析。
客户端绑定服务端得到proxy
客户端调用bindservice方法来启动了service,连接服务端Service成功了会有回调:
public void onServiceConnected(ComponentName name, IBinder binder) { Log.e(TAG,"connect"); binded=true; proxy = ICalculateAIDL.Stub.asInterface(binder); } |
在onServiceConnected的参数中获取到一个Binder对象,通过binder得到proxy,然后就可以通过proxy调用服务端的接口。
接下来我们会分析proxy是怎么获得的?
客户端得到proxy
proxy = ICalculateAIDL.Stub.asInterface(binder);
我们先看一下Android tools帮我们生成的ICalculateAIDL.java有什么内容?
package com.liuzhiyong.calculate.aidl; public interface ICalculateAIDL extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.liuzhiyong.calculate.aidl.ICalculateAIDL { private static final java.lang.String DESCRIPTOR = "com.liuzhiyong.calculate.aidl.ICalculateAIDL"; public Stub() { this.attachInterface(this, DESCRIPTOR); } public static com.liuzhiyong.calculate.aidl.ICalculateAIDL asInterface( android.os.IBinder obj) { ...... } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { ...... } private static class Proxy implements com.liuzhiyong.calculate.aidl.ICalculateAIDL { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } ...... @Override public int add(int a, int b) throws android.os.RemoteException { ...... } @Override public int minus(int a, int b) throws android.os.RemoteException { ...... } } } public int add(int a, int b) throws android.os.RemoteException; public int minus(int a, int b) throws android.os.RemoteException; } |
现在我们来具体研究下代码,其实这里面的重点很简单,一个是Stub类,一个是Proxy类。
-
先看Stub类,Stub翻译成中文是存根的意思
Stub对象是在服务端进程,是ICalculateAIDL的内部类,它继承自Binder并实现了这个生成的java接口ICalculateAIDL。讲了这么多,Stub究竟怎么理解呢。实际上,stub是为了帮助客户端和服务端通信的一个中介,Service的实现类需要去继承这个stub服务桩类,客户端bind服务端的service返回了一个clientBinder对象,通过调用ICalculateAIDL.Stub.asInterface(clientBinder);得到服务端的Binder。
asInterface这个方法能得到proxy对象了,那我们来看下asInterfce干了什么:public static com.liuzhiyong.calculate.aidl.ICalculateAIDL asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } //根据包名获取本地实现的一个接口的实例,如果是本地service则可以获取到 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.liuzhiyong.calculate.aidl.ICalculateAIDL))) { //如果得到的实例是ICalculateAIDL的对象,则返回 return ((com.liuzhiyong.calculate.aidl.ICalculateAIDL) iin); } //如果无法得到本地实现的对象则会返回一个代理对象 return new com.liuzhiyong.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj); //简单来看,就做了两件事 //1. 首先在Client进程中这个名字的service,找到了,就返回Stub,本地调用。 //2. 如果没有找到,那么一定别的进程实现的service,于是返回一个service的静态代理类对象(proxy)供Client调用。 //其实就是去查询是不是在同一个进程里去调用的,如果是同一个进程,直接本地调用。如果不是一个进程,会返回一个Proxy对象供远程RPC。 }
-
再看Proxy类,客户端通过proxy来调用服务端的接口方法
Stub的一个内部类,也同样实现了ICalculateAIDL接口
接下来,我们重点来研究下Proxy类,这个东东又在搞什么?private static class Proxy implements com.liuzhiyong.calculate.aidl.ICalculateAIDL { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } ...... @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); //调用binderDriver的提供的方法将参数发给服务端 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); //读取到返回结果 _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public int minus(int a, int b) throws android.os.RemoteException { ......//同上 } } }
proxy也实现了ICalculateAIDL接口。我们以add方法为例,在add方法中将请求函数命令(Stub.TRANSACTION_add)、参数(data)和返回结果(reply)都打包发送给Binder驱动。Binder驱动会将这个请求函数、参数和返回结果发送给Server端,Server端在Stub类中的onTransact方法中收到这个请求包裹,然后真正执行相关的调用,把执行结果打包回传给Binder驱动,最后Binder驱动把结果回传给Client端,Client端把结果解析出来,整个交互就结束了。
具体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); } |
在onTransact中,真正调用add()或者minus()方法,得到返回结果,然后结果打包返回给Proxy处理,最后返回给客户端。
总结,以add()为例
1. 客户端绑定Service:
客户端绑定服务端的service,绑定成功后回调onServiceConnected,在参数里有一个ServerBinder,通过这个binder得到一个proxy对象,供RPC使用。
2. 客户端得到Proxy:
客户端通过proxy = ICalculateAIDL.Stub.asInterface(binder);这个方法得到proxy对象。asInterface会判断此ServerBinder是不是本地binder,如果是本地的(在一个进程中),直接返回Stub对象,本地调用方法即可。 如果是远端的(不在一个进程中),返回一个proxy对象,这个proxy也实现了AIDL的ICalculateAIDL接口。
3. 客户端调用add():
Client端通过proxy调用AIDL的add接口:proxy.add(3,3);
4. 服务端处理add请求,把计算结果回传给客户端:
如果是本地Stub对象,直接调用add()方法。如果是proxy对象,proxy对象会调用他的add方法(注意这里,由于proxy也实现了AIDL的ICalculateAIDL接口,所以他也有add和minus方法,这就是proxy设计模式),在add方法中把相关命令、参数和空结果打包发给Binder驱动,Binder驱动将包裹发送给对应的Server端,Server端在Stub类的onTransact()方法中收到,执行真正的add方法调用,得到计算结果,然后把结果打包发回给Binder驱动,Binder驱动再回给Client端,Client解析包裹,得到计算结果,over。
AIDL 交互过程client<–>proxy<–>stub<–>service
从上述交互可以看到,stub和proxy是为了方便client/service交互而生成出来的类,这样client/service的代码就会比较干净,不会嵌入很多很难懂的与业务无关的代码。这也是AIDL的目标,让开发者只关心自己设计的业务接口,不关心底层binder传输的细节。