AIDL是 Android Interface definition language的缩写,即:Android接口定义语言。
进程隔离:不同进程间不可以相互访问内存空间,要想相互调用,必须要进行进程间通信。
本篇中不涉及Binder的底层原来,但是要理解一个知识点:客户端进程持有BinderProxy类的对象,通过Binder驱动,向对应的运行在服务端进程中的Binder对象发送消息(执行远程方法调用)。可以类比java的Socket编程中的,Socket 向SocketServer发送消息的过程,不过Binder不仅仅是发送报文消息那么简单,他对远程方法调用实现了封装。
注:这不是一篇介绍如何使用AIDL的文章(如果想学习aidl如何使用移步官网案例,任玉刚博客),这篇文章主要解读编译系统根据aidl文件生成的java代码,目的是为了将来读懂ActivityManagerService的源码。
AIDL生成代码解析
为了便于阅读生成的代码,我们来写一个最最简单的AIDL,让服务端为我们实现两个数相加的功能。客户端界面如下:
AIDL客户端界面 2017-09-12 18.01.31.png首先定义AIDL接口:
// ICalculate.aidl
package me.febsky.aidl;
interface ICalculate {
int add(int a, int b);
}
然后在AndroidStudio中运行Build-->Rebuild Project
或者点击Gradle同步按钮,这时候会在app-->build-->generated-->source-->aidl
下面生成ICalculate.java
这个类。这些代码是自动生成的,不可修改,应该说改了也不起作用,下次编译还会被覆盖。
打开这个类文件,来看下生成的源码(为了便于阅读,在Mac上可以按command+alt + L
来格式化代码),现在摘录代码如下:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/liuqiang/Desktop/AIDL/app/src/main/aidl/me/febsky/aidl/ICalculate.aidl
*/
package me.febsky.aidl;
public interface ICalculate extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements me.febsky.aidl.ICalculate {
private static final java.lang.String DESCRIPTOR = "me.febsky.aidl.ICalculate";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an me.febsky.aidl.ICalculate interface,
* generating a proxy if needed.
*/
public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
return ((me.febsky.aidl.ICalculate) iin);
}
return new me.febsky.aidl.ICalculate.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 me.febsky.aidl.ICalculate {
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);
_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 a, int b) throws android.os.RemoteException;
}
好吧,怪不得Android要搞出AIDL这么个东西,我在aidl中就写了一行代码,系统生成了这么多,要不是系统自动生成,每次用到Binder和AIDL进行进程间通信的时候,都要手撸这么写重复代码。
这些代码从何看起呢,为了便于从宏观上观察生成的代码,我们折叠一下暂时不关心的代码:
代码总览1 2017-09-12 18.17.35.png然后是这样:
代码总览2 2017-09-12 18.19.25.png最后是这么个样子:
代码总3 2017-09-12 18.23.30.png从以上代码来看,在生成的java文件中主要有三个类:ICalculate
,ICalculate.Stub
,ICalculate.Proxy
。其中Stub
是接口ICalculate
的静态内部类,Proxy
是Stub的私有静态内部类(个人认为其实Stub
和Proxy
没有必要一定要做为ICalculate
的静态内部类,这样放置只是为了便于管理和查看他们之间的关联关系)
ICalculate
这个接口其实很简单继承与IInterface
,先不用管这个IInterface的作用,只看ICalculate的话就是个普通的接口,这里面有我们定义的add
方法,就是定了了我们要在AIDL中实现的业务逻辑。这个接口其实为了进程间通信,所有定义的是客户端需要服务端提供的功能。
ICalculate.Stub
这个类很重要,它继承了Binder类,实现了ICalculate接口。从继承关系来看他是一个具有ICalculate功能的Binder。好,既然是一个Binder就具有了进程间通信的功能。注意这个类是个抽象类,它只是定义了Binder的业务层通信功能,但是具体的通信内容(也就是我们的业务方法add方法)并没有具体实现,需要子类来实现。一般Stub的子类在服务端实现。
说到这里必须说下Binder,Binder是Android上比较复杂的一个东西了。但这里我们不分析Binder的通信原理。只需要知道,Binder和BinderProxy是成对出现的,客户端进程持有BinderProxy对象,然后BinderProxy可以和binder驱动交互,binder驱动再去发消息给Binder对象从而实现IPC。
个人认为为了便于理解,完全可以把BinderProxy和Binder类比成javaTCP 中的Socket和SocketServer
看下Binder的源码结构:
Binder 和BinderProxy 源码2017-09-13 14.30.06.pngBinder和BinderProxy只是实现了进程间通信功能,具体通信内容是啥他不关心。通信内容交给ICalculate.Stub.Proxy 和 ICalculate.Stub的子类来实现。
在ICalculate.Stub中有几个很重要的方法:
- asInterface
- onTransact
首先看看asInterface方法,这个方法是一个静态方法,我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过这个方法拿到一个远程的service(这个Service不是Android的四大组件的那个Service)的代理(客户端和服务端不在同一个进程中的情况下),binderService时候的代码如下:
ICalculate calculate;
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
calculate = ICalculate.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
//一般情况下,如果是跨进程的穿件来的参数都是BinderProxy类型的
public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
//这种情况基本不存在,可以忽略,你传了个null进来大家还玩啥?
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//这个if来判断,客户端和服务端是不是在一个进程中
//也就是来判断,传进来的参数obj是Binder对象还是BinderProxy对象
//如果在同一个进程中传入的是Binder对象,也即是Stub子类的对象,以下if语句成立
if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
return ((me.febsky.aidl.ICalculate) iin);
}
return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
}
onTransact
后面我们和Proxy
中的transact
方法一块分析。接下来先看Stub.Proxy
这个类。
ICalculate.Stub.Proxy
Stub.Proxy 2017-09-13 15.35.57.png从代码中可以看出,这个类的构造方法接收一个IBinder的实现类,其实这里主要是BinderProxy的对象。然后忽略其他,直接看我们的add方法。前面也提到了,客户端通过Stub.asInterface 静态方法,持有Stub.Proxy 类的对象,然后和存在于服务端进程中Stub子类的对象进行通信。其实归根到底是客户端BinderProxy和服务端Binder的通信。
这个add方法可以解读为,Stub.Proxy 类的对象,持有BinderProxy的对象,通过BinderProxy对象,像远程的Binder对象发送消息。看下发送消息的主要代码。可以先不用去管Parcel对象,可以把它看做一个可以序列化的对象,或者向远程发送数据的载体。把要传递给远程对象的参数放到Parcel中,然后调用BinderProxy的transact 方法,发送消息到Stub子类对象中。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
这个方法由三个参数
- Stub.TRANSACTION_add 方法名的唯一标示,告诉远程对象,我要执行你的哪个方法,目前我们的接口中只定义了一个add方法,
- _data封装了要调用的远程方法的所有的需要的参数
- _reply 远程方法返回值的载体
- flag 最后一个参数是个flag,默认0就可以了,好像是用来指定是不是单向的IPC的
通过以上过程,这样一个远程方法调用,就会通过Binder机制,把消息(调用某个方法)发送到服务端进程中的相应对象中。我们在此依然忽略BinderProxy和Binder之间跨进程通信的底层原理,只要知道,BinderProxy通过调用transact 方法,能通过Binder驱动,发消息到Binder进程就可以了。继续分析当BinderProxy通过transact 方法发送消息到服务端Binder子类对应的进程的时候,Stub的子类是如何接收处理这个消息的,看Stub类的onTransact方法:
Stub中的onTransact 方法 2017-09-13 16.08.30.png可以看到在这个方法中有个switch语句,这个code就是刚刚在transact中的第一参数,用了标志该调用哪个方法。其余方法也和transact 中的一一对应,不再解释。其实上面的代码也很好理解,主要看第二个case里面语句吗,先把方法需要参数从data这个载体中读出来,对应 BinderProxy transact方法的写入操作,然后调用真正的业务方法addint _result = this.add(_arg0, _arg1);
并把返回值写入到reply 这个返回值载体中从而能把方法返回值传递个客户端。从上面可以看到Stub是个抽象类,并没有实现业务方法add,这个要在他的子类中实现,具体代码如下:
//注意这个Service要放到单独的进程中运行
public class CalculateService extends Service {
private ICalculate.Stub calculate = new ICalculate.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
};
@Override
public IBinder onBind(Intent intent) {
return calculate;
}
}
【[测试代码下载]
(http://download.csdn.net/download/niyingxunzong/9977048)】
测试效果图:
测试效果图 2017-09-13 16.57.14.png重要知识点
在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。
其中基本的UML类图如下,类图中并没有标注出所有的方法,只是标注了我们关心的几个:
UML类图.png
网友评论