AIDL内部实现详解 (一)
AIDL的作用是实现跨进程通讯使用方法也非常的简单,他的设计模式是典型的C/S架构。使用AIDL只要在Client端和Server端的项目根目录下面创建一个aidl的文件夹,在aidl文件夹的下面用java代码编写一个后缀名为.aidl的接口文件然后重新编译一下就会在gen目录下生成相对应的java文件。这里主要研究aidl的运作流程以及原理。
aidl结构
首先我在Server端去实现了一个aidl的接口文件代码如下:
//IMyAidl.aidl
interface IMyAidl {
int add(int arg1, int aarg2);
}
重新编译(rebuild)就会在generated目录下面生成相应的IMyAidl.java的文件代码如下:
public interface IMyAidl extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.coder_f.aidlserver.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.coder_f.aidlserver.IMyAidl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.coder_f.aidlserver.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.coder_f.aidlserver.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.coder_f.aidlserver.IMyAidl))) {
return ((com.coder_f.aidlserver.IMyAidl) iin);
}
return new com.coder_f.aidlserver.IMyAidl.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
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;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.coder_f.aidlserver.IMyAidl {
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 arg1, int arg2) 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(arg1);
_data.writeInt(arg2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int arg1, int arg2) throws android.os.RemoteException;
}
主要就是根据aidl文件自动生成了stub的内部类,这个内部类不仅继承了Binder还继承了父类。这里继承父类主要是为了能够获得回调add接口方法,stub类并没有去真正的实现add方法。
stub类结构很清楚里面只有3个方法加上1个内部类分别是:
- asInterface 根据传入的binder来判断当前是否是跨进程,如果跨进程则返回一个Proxy对象,如果不是跨进程则直接返回当前接口实现类(想当是调用本地接口);
- asBinder 返回当前binder对象,继承IInterface必须实现的方法(满足内部跨进程)
- onTransact Binder的方法当client端的Binder对象调用了transact方法时此方法会被回调
- Proxy类 一个代理类,主要作用是配置Binder对象的transact方法(这个方法继承自IBinder)需要的参数以及调用transact方法。Proxy类也继承了我们自己定义的aidl接口,主要为了保证接口中的方法都有相对应transact去调用真正的server端方法(应为继承了接口,如果不是抽象类那就必须得去实现这些方法吧)。
这里还有一个特别的参数就是TRANSACTION_add这么一个常量,他是由一个常量+0构成的,这个常量其实就是0,这个常量主要来标记方法的如果有多个方法:
//当前只有一个方法所以是+0,当更多的方法时每个方法都会对应一个数字。例如有两个时那就会如下
static final int TRANSACTION_xxx = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_yyy = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
...
这些方法(类)现在有一个印象就可以,后面会详细的分析。
以上是一个aidl的完整结构,当然真正要使用还需要去有一个类来继承stub类并且实现最初aidl文件中定义的所有接口(注意stub类是一个抽象类,它继承了aidl的接口但却没有真正的去实现它,这里需要我们来实现)。当然这个java类也需要在client端实现一遍(也就是自动生成一遍)。
aidl流程
要分析aidl流程所以我先创建了一个Service(MyService),在里面创建了一个内部类MyAidlImpl来继承IMyAidl.Stub并且实现我们最初定义在aidl文件中一直未被实现的add方法。在Service的onBind方法中返回这个内部类的实例(因为Stub是继承了Binder类所以MyAidlImpl的实例也会是Binder类型),具体代码如下:
Server端:MyService.java
//MyService.java
public class MyService extends Service {
private MyAidlImpl myAidlImpl;
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
myAidlImpl = new MyAidlImpl();
}
@Override
public IBinder onBind(Intent intent) {
return myAidlImpl;
}
}
class MyAidlImpl extends IMyAidl.Stub {
@Override
public int add(int arg1, int arg2) throws RemoteException {
return arg1 + arg2;
}
}
接口的实现方法很简单,就是把传进来的两个参数加一下再返回回去。现在这个Service就相当于是一个Server端,来远程处理一些任务(这里就是arg1 + arg2)并且返回结果。
至于Client端代码就是最普通的一个Activity里面去绑定刚刚实现的Service,这里我为了保证是跨进程我重新创建了一个项目。(也可以在当前项目里面来做,只要在manifest文件中把Service的android:process设置为“:remote”就可以了,这样Service就不会和activity运行在一个进程中)
Client端:MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "AIDLClient";
private ServiceConnection serviceConnection;
private IMyAidl myAidl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidl = IMyAidl.Stub.asInterface(service);
try {
Log.d(TAG, "onServiceConnected: data from server = " + myAidl.add(123, 456));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAidl = null;
}
};
bindService(new Intent("com.coder_f.aidlserver.MyService"), serviceConnection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
根据上面代码我们现在看一下,首先是Client端来bindService,这里绑定成功以后就会返回一个Binder对象,这个对象就是Server端onBind的时候返回的,是MyAidlImpl的实例,通过IMyAidl.Stub.asInterface(service)方法来获得一个Proxy类的对象(这里考虑的是跨进程调用的情况所以返回的是proxy对象),然后我们调用了proxy对象里面的add方法,那我们就跟着去看一下根据aidl自动生成的java类中proxy的add方法里面具体是什么逻辑。
private static class Proxy implements com.coder_f.aidlserver.IMyAidl {
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 arg1, int arg2) 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(arg1);
_data.writeInt(arg2);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
这里的remote就是asInterfa方法中传入的Binder对象,通过这个Binder对象,接着调用了add方法。这里来仔细看看这个add方法的逻辑,总共就三步。
-
创建了两个Parcel对象(跨进程只能传递Parcel以及Binder类型的对象)_data用来存放参数,_reply用来存放返回值。这里传入的DESCRIPTOR是一个包名。这个DESCRIPTOR也起到标识的作用,只有transact和onTransact方法中parcel对象的一致时才能成功调用。这也是为什么我们要求Server端和Client端aidl存放目录结构一致的原因,不一致的话这个自动生成DESCRIPTOR也会不一样;
-
调用了Binder的transact方法,这个方法其实就是跨进程调用的核心,他的第一个参数是一个int值,用来告诉Server端(onTransact方法)我要调用的具体是哪个方法,这里Stub.TRANSACTION_add标志的就是add方法,第二个和第三个之前已经说了分别是参数和返回值,第四个是个标志位,当0的时候代表有返回值,当1的时候则代表没有返回值(此时就算你的方法真的有返回值_reply中也不会有结果)
-
读取_reply中返回的结果。
这里要注意的是当调用transact方法时当前线程会被挂起。此时如果这个方法运行在主线程时,而server端又因为各种原因或者方法本身就是耗时的导致不能及时返回结果就会使得ui线程卡住。而我上面Client端方法中myAidl.add(123,456)是放在了onServiceConnected方法里面,而onServiceConnected方法其实是运行在主线程的(具体可以看我之前bindservice流程分析文章中最后面的说明)
这里调用Binder的transact方法以后经过一系列的流程(这里具体什么流程没有仔细研究过,涉及到jni底层native的binder方法)最后会回调到Server端的onTransact方法。
好了那我们就看看自动给我们生成的java文件中onTransact方法的逻辑代码如下。
@Override
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;
}
}
return super.onTransact(code, data, reply, flags);
}
这里的onTransact方法的参数其实和transact方法的参数是一一对应的,第一个就是标志方法的code默认会有有一个INTERFACE_TRANSACTION用来验证之前说的DESCRIPTOR,另外一个就是TRANSACTION_add,这里我们接口中只有一个所以总共onTransact方法中只对这一个方法处理。
这个add方法和proxy类中add方法的结构很类似,就是从_data中取出(proxy.add是创建写入)参数调用真正add方法(在service中实现的add方法)然后把结果写入到reply(proxy.add是创建读取)中。注意这个方法有一个返回值,如果返回false则Client端会调用失败。
上面就是aidl的一整套流程。
总结:
-
aidl其实就是利用Binder来实现跨进程调用。而这个系统根据.aidl文件生成的java文件就是对binder的transact方法进行封装。
-
onTransact方法是提供给server端用的,transact方法(内部类proxy封装了transact方法)和asInterface方法是给client端用的。系统自动生成不知道你要把哪个当做server端哪个当做client端所以就都生成了。
-
DESCRIPTION是根据aidl文件位置(包名)来生成的,DESCRIPTION是binder的唯一标识,这也解释了为什么要保证client端和server端.adil文件的包结构一样;
-
aidl的transact的调用会导致当前线程挂起,因此如果Server端的方法耗时最好另起线程来调用transact方法;
-
aidl通过Parcel来传递参数和返回值,因为binder只支持Binder对象和parcel对象的跨进程调用;
-
Binder,IBinder,IInterface三个的关系。Binder是继承自IBinder对象的,IBinder是远程对象的基本接口。另外IBinder的主要API是transact(),与它对应另一方法是Binder.onTransact(),而IInterface主要为aidl服务,里面就一个方法——asBinder,用来获得当前binder对象(方便系统内部调用)。
最后说白了aidl的核心就是client调用binder的transact方法,server通过binder的onTransact方法了来执行相应的方法并返回。