Android 10 添加了对稳定的 Android 接口定义语言 (AIDL) 的支持。稳定的 AIDL 是一种跟踪由 AIDL 接口提供的应用编程接口 (API)/应用二进制接口 (ABI) 的新方法。稳定的 AIDL 与 AIDL 的主要区别如下:
- 在编译系统中使用
aidl_interfaces
定义接口。 - 接口只能包含结构化数据。对于代表所需类型的 Parcelable,系统会根据其 AIDL 定义自动创建,并自动对其进行编组和解组。
- 可以将接口声明为“稳定”接口(向后兼容)。声明之后,会在 AIDL 接口旁的一个文件中对这些接口的 API 进行跟踪和版本编号。
定义 AIDL 接口
aidl_interface
的定义如下:
aidl_interface {
name: "my-module-name",
local_include_dir: "tests_1",
srcs: [
"tests_1/some/package/IFoo.aidl",
"tests_1/some/package/Thing.aidl",
],
api_dir: "api/test-piece-1",
versions: ["1"],
}
name
:模块的名称。在这种情况下,系统会分别创建两个使用相应语言的存根库:my-module-name-java
和my-module-name-cpp
。要防止创建 C++ 库,请使用gen_cpp
。这还会创建可用于检查和更新 API 的其他编译系统操作。local_include_dir
:指向软件包开始位置的路径。srcs
:编译为目标语言的稳定 AIDL 源文件的列表。api_dir
:转储接口的先前版本的 API 定义的路径,该路径用于确保捕捉到破坏 API 的接口更改(请参阅下面介绍的流程)。versions
:冻结在api_dir
下的接口的先前版本。此参数为可选参数。
编写 AIDL 文件
稳定 AIDL 中的接口与传统接口相似,不同之处在于前者不允许使用非结构化的 Parcelable,因为这些 Parcelable 不稳定。稳定 AIDL 的最大不同就在于如何定义 Parcelable。以前,Parcelable 是前向声明的,而在稳定的 AIDL 中,Parcelable 字段和变量是显式定义的。
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
现在支持 boolean
、char
、float
、double
、byte
、int
、long
和 String
的默认值(但不是必需的)。
使用存根库
将存根库作为依赖项添加到模块之后,您可以将这些库添加到您的文件中。下面是编译系统中的存根库的示例(Android.mk
也可用于旧版模块定义):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
static_libs: ["my-module-name-java"],
...
}
采用 C++ 语言的示例:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
采用 Java 语言的示例:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
对接口进行版本编号
如果声明一个名为 foo 的模块,则同时也会在编译系统中创建一个目标,您可以用该目标来管理该模块的 API。编译后,foo-freeze-api 会在 api_dir
下为接口的下一个版本添加新的 API 定义。
要保持接口的稳定性,您可以:
- 在方法的末尾添加新方法(或添加具有显式定义的新序列的方法)
- 在 Parcel 的末尾添加新元素(需要为每个元素添加一个默认值)
不允许执行其他操作。
新增的元接口方法
Android 10 针对稳定的 AIDL 推出了几种新的元接口方法。
查询远程对象的接口版本
客户端可以查询远程对象正在实现的接口版本,并将返回的版本与客户端正在使用的接口版本进行比较。
采用 C++ 语言的示例:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
采用 Java 语言的示例:
IFoo foo = ... // the remote object
int my_ver = IFoo.VERSION;
int remote_ver = foo.getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
对于 Java 语言,远程端必须实现 getInterfaceVersion()
,如下所示:
class MyFoo extends IFoo.Stubs {
@Override
public final int getInterfaceVersion() { return IFoo.VERSION; }
}
这是因为所生成的类(IFoo
、IFoo.Stubs
等)将在客户端和服务器之间共享(例如,这些类可以位于启动类路径下)。共享类时,服务器仍会链接到类的最新版本,即使该版本可能是用接口的旧版本编译的。如果该元接口是在共享类中实现的,则该接口始终会返回最新版本。不过,如果按照上述方式实现该方法,便会将接口的版本号嵌入到服务器的代码中(因为 IFoo.VERSION
是一个在引用时内嵌的 static final int
),从而使该方法能够返回编译该服务器时所用的确切版本。
处理旧版接口
可能会存在以下情况:客户端使用的是较新的 AIDL 接口版本进行更新,而服务器使用的是旧版 AIDL 接口。在这种情况下,客户端便不能调用旧版接口中不存在的新方法。在使用稳定的 AIDL 之前,系统会静默忽略对此类不存在的方法的调用,客户端不会得知是否调用了该方法。
使用稳定的 AIDL,客户端将拥有更多的控制权。在客户端,您可以设置 AIDL 接口的默认实现。对于默认实现中的方法,只有当其没有在远程端实现时,才会被调用。这是因为该方法是使用旧版接口编译的。
采用 C++ 语言的示例:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(std::unique_ptr<IFoo>(MyDefault));
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
采用 Java 语言的示例:
IFoo.Stubs.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
您无需为 AIDL 接口中的所有方法提供默认实现。您也不需要在默认 impl
类中替换一定会在远程端实现的方法(因为您确定远程端是在这些方法位于 AIDL 接口描述中时编译的)。
将现有的 AIDL 转换为结构化/稳定的 AIDL
如果您已经有一个 AIDL 接口以及使用该接口的代码,可以按照以下步骤将该接口转换为稳定的 AIDL 接口。
确定您的接口的所有依赖项。对于该接口所依赖的每个软件包,确定该软件包是否已在稳定的 AIDL 中定义。如果未定义,则必须转换该软件包。
将您的接口中的所有 Parcelable 全部转换为稳定的 Parcelable(接口文件本身可以保持不变)。可通过直接在 AIDL 文件中表示它们的结构来实现这一点。您必须重写管理类以使用这些新类型。可以在创建
aidl_interface
软件包(如下所示)之前完成重写。创建
aidl_interface
软件包(如上所述),其中应包含模块的名称和依赖项以及您需要的任何其他信息。为使其保持稳定(不仅仅是结构化),您还需要指定api_dir
路径。