Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。
在构建每个包含 .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
4. Map
Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map
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表示输入输出型参数:可在客户端与服务端双向流动,服务端接收到该参数对象的完整数据,且服务端对该对象的后续修改将同步改动到客户端的相应参数对象;
如果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 {public T createFromParcel(Parcel source);public T[] newArray(int size);}//对象创建时提供的一个创建器public interface ClassLoaderCreator extends Creator {//使用类加载器和之前序列化成的 Parcel 对象反序列化一个对象public T createFromParcel(Parcel source, ClassLoader loader);}
}
写一个自定义的ParcelableObj类
public class ParcelableObj implements Parcelable {//元素nameprivate String mName;//构造法方法public ParcelableObj(String name){mName = name;}protected ParcelableObj(Parcel in) {mName = in.readString();}//序列化@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(mName);}/*内容描述*/@Overridepublic int describeContents() {return 0;}//反序列化public static final Creator CREATOR = new Creator() {//反序列创建对象@Overridepublic ParcelableObj createFromParcel(Parcel in) {return new ParcelableObj(in);}//反序列创建对象数组@Overridepublic ParcelableObj[] newArray(int size) {return new ParcelableObj[size];}};
}
通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致.
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 statementsinterface 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 16targetSdkVersion 33versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"................
这样子就解决了上述的问题, 当然客户端中也得加上这段代码, 接下来我们就可以直接拷贝整个aidl包了.
本文主要讲解了AIDL的语法规则和创建AIDL文件的注意点, 特别是引用自定义Parcelable对象的aidl文件创建注意事项. 也是为后续写 进程间通信机制 相关的文章做个铺垫.