AIDL使用详解及原理(附加例子)
AIDL是什么?
AIDL(Android Interface Define Language ——Android接口定义语言)是一种IPC通信方式,可以利用它来定义两个进程相互通信的接口,与 Service 进行跨应用、跨进程通信的一种机制,高效、灵活,使用方便。它的本质是C/S架构的,需要一个服务器端,一个客户端。既然一个服务器端一个客户端,那么开始撸码实现。
开始AIDL的实现
服务端步骤如下:
- 创建一个安卓工程
- 生成aidl文件,在aidl文件中写入接口方法
- 实现接口,并向客户端放开接口
客户端步骤如下:
- 创建一个安卓工程
- 新建aidl目录
- 绑定服务,调试服务端接口
创建一个安卓工程(服务端)
首先在Android Studio中创建一个Andorid工程,作为aidl的服务端,包名为com.lu.aidlserver。
生成aidl文件
利用AS自带的插件,在src目录中右键生成一个aidl文件(new——>AIDL——>AIDL File——>输入文件名) 。
从图中可以看出生成了一个IMyAidlInterface.aidl文件。打开文件看到如下内容。
package com.lu.aidlserver;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
* 从注解可以看得出 basicTypes()方法中的参数都是 aidl定义可以用的参数类型
*/
// void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
// double aDouble, String aString);
String getName();//获取一个名称
int countSum(int a ,int b);//计算a+b
}
看到aidl的语法跟java是一样的,也很好理解,声明了一个接口,里面定义了aidl服务器端暴露给客户端调用的方法,方法的参数是一些可支持的数据类型,还能支持别的数据类型?这里先放一放,下面会讲到。从注解可以看得出 basicTypes()方法中的参数都是 aidl定义可以用的参数类型。当然可以根据自己的需求定义接口。在里面的就定义了两个接口方法。完成这部分操作之后还没有结束,我们需要手动编译程序,生成aidl对应的Java代码。
通过实现接口,并向客户端放开接口
/**
* Author: 安仔夏天勤奋
* Date: 2019/6/18
* Desc: 服务
*/
public class MyAidlService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();//返回给客户端的Binder
}
//获取aidl的Binder
private class MyBinder extends IMyAidlInterface.Stub{
@Override
public String getName() throws RemoteException {
return "我是AIDL";
}
@Override
public int countSum(int a, int b) throws RemoteException {
return a+b;
}
}
}
创建一个安卓工程(客户端)
客户端和服务端一样的,就不多创建一个工程了,与服务端一个工程下,创建一个客户端module,包名为com.lu.aidlclient
客户端新建aidl目录
将服务器端的aidl拷贝到客户端,特别要注意,拷贝后的客户端的aidl文件包目录必须与服务器端保持一致,拷贝完后同样时编译工程,让客户端也生成对应的java文件。
绑定服务,调试服务端接口
在MainActivity中调试服务端的接口打打印。
public class MainActivity extends AppCompatActivity {
private com.lu.aidlserver.IMyAidlInterface binder = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//android5.0之前都可以通过配置在manifest里service 的action来启动。android5.0之后都必须使用显示intent。
// //启动aidl 的服务
// Intent intent = new Intent();
// intent.setComponent(new ComponentName("com.lu.aidlserver",
// "com.lu.aidlserver.MyAidlService"));
// //第一个参数是包名,第二个是类名
Intent service = new Intent("com.lu.aidlserver.MyAidlService");
// 绑定AIDL 隐式
bindService(service, connection, BIND_AUTO_CREATE );
}
// 创建远程调用对象
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 从远程service中获得AIDL实例化对象
//这里不能强制转换,因为虽然类的内容是一样的,但是却不是同一个。(每个app能访问到的毕竟是自己的类)
binder = com.lu.aidlserver.IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
}
};
public void onClickTest(View v){
try {
Log.e("lu","Name : "+binder == null ? "null" :
binder.getName()+" 计算a+b="+binder.countSum(9,1));
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
运行结果
06-19 00:44:32.389 4314-4314/com.lu.aidlclient E/lu: 我是AIDL 计算a+b=10
打印出来结果,说明远程调用成功了,aidl的整个流程就走完了。做到这一步起码你对AIDL有一定的认知了。下面讲一下上面提到的的问题,除了AS生成aidl文件给出的数据类型还能用其他数据类型作解答。
AIDL可使用的参数类型
官方的数据类型就是只有这几种了。
void basicTypes(int anInt, long aLong,boolean aBoolean, float aFloat, double aDouble, String aString);
我们知道Java有8中基本数据类型,分别为byte、char、short、int、long、float、double、boolean,这些类型是否都能作为aidl的参数进行传递呢?我们可以在aild中尝试下
void basicTypes(byte aByte, char aChar, short aShort, int anInt, long aLong, float aFloat,
double aDouble, boolean aBoolean);
定义好后,我们手动去编译,发现无法成功编译,经过筛查发现aidl并不能支持short的数据类型,至于为什么呢,可以看一看Android中的Parcel,Parcel是不支持short的。更具体还没有查阅资料,有兴趣的可以自行查阅。官方文档
AIDL引用数据类型
引用数据类型根据官方文档介绍,可以使用String、CharSequence、List、Map。这样的话我们也可以使用自定义数据类型。
自定义数据类型
话不多说,代码先行。在服务端创建一个学生类,并添加一些属性。
QQ图片20190619215714.png
public class Student implements Parcelable {
private int age;
private String name;
private String sex;
public Student(int age, String name, String sex) {
this.age = age;
this.name = name;
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "\n名字="+this.name+"\t\t性别="+this.sex+"\t\t年龄="+this.age;
}
/**
* Parcelable对数据进行分解/编组的时候必须使用相同的顺序,
* 字段以什么顺序分解的,编组时就以什么顺序读取数据,不然会有问题
* @param in Parcel
*/
protected Student(Parcel in) {
this.age = in.readInt();
this.name = in.readString();
this.sex = in.readString();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(name);
dest.writeString(sex);
}
}
自定义数据类型,用于进程间通信的话,用Parcelable接口实现,Parcelable是类似于Java中的Serializable,Android中定义了Parcelable,用于进程间数据传递,对传输数据进行分解,编组的工作,相对于Serializable,Parcelable对于进程间通信更加高效。创建完Student实体后,还需要创建一个aidl文件,来定义一下我们的Student,否则Student在aidl中无法识别,在服务端的aidl文件中创建一个Student的aidl文件。
Student.aidl文件就两个话。
// Student.aidl
package com.lu.aidlserver;
parcelable Student;
在之前的服务器端IMyAidlInterface.aidl文件中添加addStudent方法。
// IMyAidlInterface.aidl
package com.lu.aidlserver;
// Declare any non-default types here with import statements
import com.lu.aidlserver.Student;
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
* 从注解可以看得出 basicTypes()方法中的参数都是 aidl定义可以用的参数类型
*/
// void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
// double aDouble, String aString);
String getName();//获取一个名称
int countSum(int a ,int b);//计算a+b
List<Student> addStudent(in Student student);
}
在MyAidlService中实现添加的addStudent()。
public class MyAidlService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();//返回给客户端的Binder
}
//获取aidl的Binder
private class MyBinder extends IMyAidlInterface.Stub{
@Override
public String getName() throws RemoteException {
return "我是AIDL";
}
@Override
public int countSum(int a, int b) throws RemoteException {
return a+b;
}
@Override
public List<Student> addStudent(Student student) throws RemoteException {
List<Student> students=new ArrayList<>();
students.add(student);
return students;
}
}
}
服务端一切都准备好了,记得重新手动编译。下面进行客户端操作。
将服务端的两个aidl文件复制到客户端,包结构必须一致,aidl文件发生变化不要忘记重新编译代码。
接着将Student实体也复制到客户端,并且包结构一致。
最后在客户端的MainActivity中的点击事件方法中调用服务端的addStudent()。
public void onClickTest(View v){
try {
Log.e("lu","Name : "+binder == null ? "null" :
binder.getName()+" 计算a+b="+binder.countSum(9,1));
if(binder!=null){
List<Student> students = new ArrayList<>();
students.addAll(binder.addStudent(new Student(18,"小卢","男")));
students.addAll(binder.addStudent(new Student(19,"张三","男")));
students.addAll(binder.addStudent(new Student(20,"李四","男")));
Log.e("lu","List<Student> : "+students.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
运行服务端与客户端App,点击测试,输出以下日志,说明调用成功。
06-19 22:29:04.609 5323-5323/com.lu.aidlclient E/lu: 我是AIDL 计算a+b=10
06-19 22:29:04.610 5323-5323/com.lu.aidlclient E/lu: List<Student> : [
名字=小卢 性别=男 年龄=18,
名字=张三 性别=男 年龄=19,
名字=李四 性别=男 年龄=20]
AIDL的demo搭建完成并能正常运行。下面就聊聊AIDL原理。
AIDL原理
在上面的服务端的例子手动编译时,在路径为aidlserver\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\com\lu\aidlserver\目录下生成了一个IMyAidlInterface.java文件
要了解aidl原理,我们需要看一下根据aidl生成的对应的java代码(IMyAidlInterface.java),一段一段进行分析。
package com.lu.aidlserver;
public interface IMyAidlInterface extends android.os.IInterface{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.lu.aidlserver.IMyAidlInterface
{...}
public java.lang.String getName() throws android.os.RemoteException;
public int countSum(int a, int b) throws android.os.RemoteException;
//计算a+b
public java.util.List<com.lu.aidlserver.Student> addStudent(com.lu.aidlserver.Student student) throws android.os.RemoteException;
}
从生成的代码结构来看很简单,一个静态抽象类Stub,以及aidl中定义的方法。我们也不难看出Stub就是AIDL的核心代码,Stub类的结构如下图。
Stub的目录结构也不复杂,Stub继承系统中的Binder并实现了我们定义的aidl接口。一个构造函数,一个asInterface方法,一个asBinder方法,一个onTransact方法,一个Proxy代理类,这边Proxy与Stub同时实现了我们定义的aidl,且Proxy中实现了我们在aidl中定义的getName、countSum、addStudent方法,下面三个int为告诉系统的方法Id。
在客户端,我们绑定服务的时候通过Stub.asInterface()回去aidl对象,代码如下:
// 创建远程调用对象
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 从远程service中获得AIDL实例化对象
//这里不能强制转换,因为虽然类的内容是一样的,但是却不是同一个。(每个app能访问到的毕竟是自己的类)
binder = com.lu.aidlserver.IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
}
};
也不难发现,客户端获取到的是一个远程服务的代理Stub.Proxy。查看asInterface源码。
public static com.lu.aidlserver.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.lu.aidlserver.IMyAidlInterface))) {
return ((com.lu.aidlserver.IMyAidlInterface)iin);
}
return new com.lu.aidlserver.IMyAidlInterface.Stub.Proxy(obj);
}
客户端调用getName、countSum、addStudent方法其实调用的时Stub.Proxy中实现的getName、countSum、addStudent,在Stub.Proxy中可以看到这几句核心代码。
//getName()
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
//countSum()
mRemote.transact(Stub.TRANSACTION_countSum, _data, _reply, 0);
//addStudent()
mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
mRemote其实就是IMyAidlInterface.Stub,随意mRemote.transact传递到了IMyAidlInterface.Stub.OnTransact, onTransact中执行了getName、countSum、addStudent,回调到了我们在服务端定义MyAidlService中声明MyBidner时重写的getName、countSum、addStudent,这就是AIDL的整个流程。
private class MyBinder extends IMyAidlInterface.Stub{
@Override
public String getName() throws RemoteException {
return "我是AIDL";
}
@Override
public int countSum(int a, int b) throws RemoteException {
return a+b;
}
@Override
public List<Student> addStudent(Student student) throws RemoteException {
List<Student> students=new ArrayList<>();
students.add(student);
return students;
}
}
总结
- 服务端编写完时或AIDL文件发生变化不要忘记重新编译代码
- 客户端复制AIDL文件时,一定要注意包名一致
- 自定义数据类型时,Parcelable对数据进行分解/编组的时候必须使用相同的顺序
- 客户端android5.0之前都可以通过配置在manifest里service 的action来启动。android5.0之后都必须使用显示intent。