一. 概述
Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。
二. 定义 AIDL 接口
在构建每个包含 .aidl 文件的应用时,Android SDK 工具会生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后,客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC。
AIDL 使用一种简单语法,允许您通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是 AIDL 生成的其他接口。
您必须使用 Java 编程语言构建 .aidl
文件。每个 .aidl
文件均须定义单个接口,并且只需要接口声明和方法签名。
默认情况下,AIDL 支持下列数据类型:
1. Java 编程语言中的8中基本类型(如 byte char boolean short int float long double)
2. CharSequence类型,如String、SpannableString等;
3. List
List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List<String>)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。
4. Map
Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。
5. 所有Parceable接口的实现类,因为跨进程传输对象时,本质上是序列化与反序列化的过程;
6. AIDL接口,所有的AIDL接口本身也可以作为可支持的数据类型;
有两个需要注意的地方:
1、在Java中,如果一个对象和引用它的类在同一个package下,是不需要导包的,即不需要import,而在AIDL中,自定义的Parceable对象和AIDL接口定义的对象必须在所引用的AIDL文件中显式import进来,不管这些对象和所引用它们的AIDL文件是否在同一个包下。
2、如果AIDL文件中使用到自定义的Parceable对象,则必须再创建一个与Parceable对象同名的AIDL文件,声明该对象为Parceable类型,并且根据上一条语法规定,在AIDL文件中进行显式import。
定义服务接口时,请注意:
1)方法可带零个或多个参数,返回值或空值。方法前不能用 public/private/protected/final 等关键字修饰.
2)所有非原基本类型参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例) , 比如Parcelable 对象.
3)定向tag正确使用
AIDL中,除了基本数据类型,其他类型的方法参数都必须标上数据在跨进程通信中的流向:in、out或inout:
1、in表示输入型参数:只能由客户端流向服务端,服务端收到该参数对象的完整数据,但服务端对该对象的后续修改不会影响到客户端传入的参数对象;(表现为服务端修改此参数,不会影响客户端的对象)
2、out表示输出型参数:只能由服务端流向客户端,服务端收到该参数的空对象,服务端对该对象的后续修改将同步改动到客户端的相应参数对象;(表现为服务端收到的参数是空对象,并且服务端修改对象后客户端会同步变动)
3、inout表示输入输出型参数:可在客户端与服务端双向流动,服务端接收到该参数对象的完整数据,且服务端对该对象的后续修改将同步改动到客户端的相应参数对象;
4. oneway 关键字用于修改远程调用的行为,被oneway修饰了的方法不可以有返回值,也不可以有带out或inout的参数。
本地调用(同步调用)
远程调用(异步调用,即客户端不会被阻塞)
使用oneway时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。
其特点:
oneway可以用来修饰在interface之前,这样会造成interface内所有的方法都隐式地带上oneway;
带oneway的方法,不会生成局部变量_reply
。且Proxy中transact中第四个参数必为android.os.IBinder.FLAG_ONEWAY
。
如果tag用的inout的话,在写自定义的Parcelable类时,必须要重写此方法,不然就会报错
public void readFromParcel(Parcel parcel)
定向tag需要一定的开销,根据实际需要去确定选择什么tag,不能滥用。
写到这里也顺便复习一下 Parcelable 基础知识
Parcelable 是 Android 特有的序列化接口:
public interface Parcelable {
//writeToParcel() 方法中的参数,用于标识当前对象作为返回值返回
//有些实现类可能会在这时释放其中的资源
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
//writeToParcel() 方法中的第二个参数,它标识父对象会管理内部状态中重复的数据
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
//用于 describeContents() 方法的位掩码,每一位都代表着一种对象类型
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
//描述当前 Parcelable 实例的对象类型
//比如说,如果对象中有文件描述符,这个方法就会返回上面的 CONTENTS_FILE_DESCRIPTOR
//其他情况会返回一个位掩码
public int describeContents();
//将对象转换成一个 Parcel 对象
//参数中 dest 表示要写入的 Parcel 对象
//flags 表示这个对象将如何写入
public void writeToParcel(Parcel dest, int flags);
//实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable
public interface Creator<T> {
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
//对象创建时提供的一个创建器
public interface ClassLoaderCreator<T> extends Creator<T> {
//使用类加载器和之前序列化成的 Parcel 对象反序列化一个对象
public T createFromParcel(Parcel source, ClassLoader loader);
}
}
写一个自定义的ParcelableObj类
public class ParcelableObj implements Parcelable {
//元素name
private String mName;
//构造法方法
public ParcelableObj(String name){
mName = name;
}
protected ParcelableObj(Parcel in) {
mName = in.readString();
}
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
}
/*内容描述*/
@Override
public int describeContents() {
return 0;
}
//反序列化
public static final Creator<ParcelableObj> CREATOR = new Creator<ParcelableObj>() {
//反序列创建对象
@Override
public ParcelableObj createFromParcel(Parcel in) {
return new ParcelableObj(in);
}
//反序列创建对象数组
@Override
public ParcelableObj[] newArray(int size) {
return new ParcelableObj[size];
}
};
}
通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致.
三. 编写AIDL文件
AS中可以通过 new AIDL 自动创建一个aidl文件, 内容根据需求自定义编写.
在AndroidStudio中工程目录的Android视图下,右键new一个AIDL文件,
默认将创建一个与java
文件夹同级的aidl
文件夹用于存放AIDL文件,且aidl
文件夹下的包名与build.gradle中配置的applicationId
一致,而applicationId
默认值是应用的包名。
例子一 :普通的基本类型数据
// IMyAidlInterface.aidl
package com.example.myclient;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
String querybyID(int n);
}
注意事项: aidl 接口方法中 不能加 public/ private/ protected/final 修饰符;否则就会报错
例子二: aidl中 用到了自定义Parcelable对象
new 一个完整的app工程, 然后在new 一个 AIDL文件, 工程结构图如下:
PathParceTest.java implement 实现 Parcelable类
public class PathParceTest implements Parcelable
对于用到自定义Parcelable对象的aidl文件, 创建的规则如下:
1. 自定义的Parcelable对象和AIDL对象必须要显示的import进来,不管他们是否和当前的AIDL文件位于同一个包内.
2. 如果AIDL文件中用到了自定义的Parcelable对象, 那么必须新建一个和它同名的AIDL文件, 并在其中声明它为Parcelable类型.
第2条在代码中体现如下:
比如ITest.aidl 用到了 PathParceTest对象, 那么我们先得写一个PathParceTest类, 然后创建 一个同名的PathParceTest.aidl文件
上面是创建AIDL文件的语法规则, 按照规则书写代码即可,不然会报错.
对于例子二, 假如你在服务端已经写好了这些aidl代码, 现在要拷贝到客户端, 根据规则, 创建的aidl 必须保持包名一致拷贝过去, 但是你的PathParceTest.java是在服务端的src/main/java/com.example.myaidl/ 路径下, 如果要拷贝过去的话, 文件包名(路径名)在客户端又得新建,然后把PathParceTest.java放置在此路径下. 那么客户端的代码结构非常的难看.
那么,有没有什么更好优化解决办法呢?目的把aidl包所有文件整体拷贝就完事
如果我们把PathParceTest.java放置到同aidl包下, 在编译的时候会报错,提示这个类找不到.
原因:
我们是在src/main/aidl
文件夹下创建PathParceTest.java的,实际上这将因为找不到PathParceTest.java而报错,因为在AndroidStudio中使用Gradle构建项目时,默认是在src/main/java
文件夹中查找java文件的,如果把PathParceTest.java放在src/main/aidl
对应包名下,自然就会找不到这个文件了,所以需要修改app的build.gradle
文件,在sourceSets
下添加对应的源文件路径,即src/main/aidl
:
android {
compileSdkVersion 33
//加上这段代码 把 src/main/aidl/路径下的java文件也编译进来
sourceSets {
main {
java.srcDirs = ["src/main/java", "src/main/aidl"]
}
}
//加上这段代码 把 src/main/aidl/路径下的java文件也编译进来
defaultConfig {
applicationId "com.example.myaidl"
minSdkVersion 16
targetSdkVersion 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
........
........
这样子就解决了上述的问题, 当然客户端中也得加上这段代码, 接下来我们就可以直接拷贝整个aidl包了.
四. 总结
本文主要讲解了AIDL的语法规则和创建AIDL文件的注意点, 特别是引用自定义Parcelable对象的aidl文件创建注意事项. 也是为后续写 进程间通信机制 相关的文章做个铺垫.