Android 10 添加了对稳定的 Android 接口定义语言 (AIDL) 的支持,这是一种跟踪由 AIDL 接口提供的应用编程接口 (API) 和应用二进制接口 (ABI) 的新方法。稳定 AIDL 的运作方式与 AIDL 完全相同,但构建系统会跟踪接口兼容性,并且会对您可以执行的操作施加限制:
- 在构建系统中使用
aidl_interfaces
定义接口。 - 接口只能包含结构化数据。对于代表所需类型的 Parcelable,系统会根据其 AIDL 定义自动创建,并自动对其进行编组和解组。
- 可以将接口声明为“稳定”接口(向后兼容)。声明之后,会在 AIDL 接口旁的一个文件中对这些接口的 API 进行跟踪和版本编号。
结构化 AIDL 与稳定的 AIDL
结构化 AIDL 是指完全使用 AIDL 定义的类型。例如,Parcelable 声明(自定义 Parcelable)不是结构化 AIDL。通过用其字段在 AIDL 中定义的 Parcelable 称为结构化 Parcelable。
稳定的 AIDL 需要结构化 AIDL,以便构建系统和编译器确定对 Parcelable 所做的更改是否向后兼容。但是,并非所有结构化接口都是稳定的。为了保持稳定性,接口只能使用结构化类型,同时必须使用以下版本控制功能。相反,如果使用核心构建系统来构建接口或设置了 unstable:true
,接口则会不稳定。
定义 AIDL 接口
aidl_interface
的定义如下:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions_with_info: [
{
version: "1",
imports: ["other-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
name
:AIDL 接口模块的名称,能唯一标识 AIDL 接口。srcs
:组成接口的 AIDL 源文件的列表。软件包com.acme
中定义的 AIDL 类型Foo
的路径应为<base_path>/com/acme/Foo.aidl
,其中<base_path>
可以是与Android.bp
所在目录相关的任何目录。在以上示例中,<base_path>
为srcs/aidl
。local_include_dir
:软件包名称以此路径开头。此路径与上述<base_path>
相对应。imports
:此接口使用的aidl_interface
模块的列表。如果您的其中一个 AIDL 接口使用另一个aidl_interface
中的接口或 Parcelable,请在此处输入其名称。这可以是名称本身(用于指最新版本),也可以是带有版本后缀的名称(例如-V1
,用于指特定版本)。从 Android 12 开始已支持指定版本versions
:冻结在api_dir
下的接口的先前版本。从 Android 11 开始,versions
冻结在aidl_api/name
下。如果没有已冻结的接口版本,就不应指定此属性,且不会进行兼容性检查。对于 Android 13 及更高版本,此字段已替换为versions_with_info
。versions_with_info
:元组列表,每个元组包含一个冻结版本的名称和一个列表,其中包含此版本的 aidl_interface 导入的其他 aidl_interface 模块的版本导入。AIDL 接口 IFACE 版本 V 的定义位于aidl_api/IFACE/V
。此字段是在 Android 13 中引入的,不支持直接在Android.bp
中修改。通过调用*-update-api
或*-freeze-api
可添加或更新此字段。此外,当用户调用*-update-api
或*-freeze-api
时,versions
字段会自动迁移到versions_with_info
。stability
:可选标志,用于承诺此接口的稳定性。仅支持"vintf"
。如果未设置stability
,除非指定unstable
,否则构建系统会检查接口是否向后兼容。如果未设置此属性,则意味着接口在此编译环境(所有系统元素,例如system.img
和相关分区中的元素,或所有供应商元素,例如vendor.img
和相关分区中的元素)下具有稳定性。如果将stability
设为"vintf"
,这表示做出了稳定性承诺:只要有代码使用此接口,此接口就必须保持稳定。gen_trace
:可选标志,用于开启或关闭跟踪功能。从 Android 14 开始,cpp
和java
后端的默认值为true
。host_supported
:可选标志,设置为true
时,可将生成的库提供给主机环境。unstable
:可选标志,用于标记此接口不需要稳定。如果将此标志设置为true
,构建系统既不会为接口创建 API 转储,也不需要更新接口。frozen
:可选标志,设置为true
表示接口自上一个接口版本以来没有任何更改。这样可以启用更多构建时检查。设置为false
表示接口处于开发阶段且包含新更改,因此运行foo-freeze-api
将会生成新版本并自动将值更改为true
。在 Android 14 中引入。backend.<type>.enabled
:这些标志用于切换后端的启用状态,AIDL 编译器会为这些后端生成代码。支持四个后端:Java、C++、NDK 和 Rust。Java、C++ 和 NDK 后端默认处于启用状态。如果这三个后端中有任何一个不是必需的,则需要明确将其停用。Rust 默认处于停用状态,直到 Android 15。backend.<type>.apex_available
:生成的桩库支持的 APEX 名称的列表。backend.[cpp|java].gen_log
:可选标志,用于控制是否生成其他代码以收集有关事务的信息。backend.[cpp|java].vndk.enabled
:可选标记,用于将此接口变为 VNDK 的一部分。默认值为false
。backend.[cpp|ndk].additional_shared_libraries
:在 Android 14 中引入,此标志用于向原生库添加依赖项。此标志对ndk_header
和cpp_header
非常有用。backend.java.sdk_version
:可选标志,用于指定构建 Java 桩库所基于的 SDK 版本。默认设置为"system_current"
。如果backend.java.platform_apis
为true
,就不应设置此标志。backend.java.platform_apis
:可选标志,当生成的库需要针对平台 API 而非 SDK 进行构建时,应将此标志设置为true
。
对于版本与已启用后端的每个组合,系统都会创建一个桩库。如需了解如何针对特定的后端引用特定版本的桩库,请参阅模块命名规则。
编写 AIDL 文件
稳定 AIDL 中的接口与传统接口相似,不同之处在于前者不允许使用非结构化的 Parcelable(因为这些 Parcelable 不稳定!请参阅结构化 AIDL 与稳定的 AIDL)。稳定 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 12 中,还支持用户定义的枚举的默认值。如果未指定默认值,系统会使用类似 0 的值或空值。即使没有零枚举器,没有默认值的枚举也会初始化为 0。
使用桩库
将桩库作为依赖项添加到模块之后,您可以将这些库添加到您的文件中。下面是构建系统中的桩库的示例(Android.mk
也可用于旧版模块定义):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if your preference is to load a library and share
// it among multiple users or if you only need access to constants
static_libs: ["my-module-name-java"],
...
}
# or
rust_... {
name: ...,
rustlibs: ["my-module-name-rust"],
...
}
采用 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
采用 Rust 语言的示例:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
对接口进行版本编号
如果声明一个名为 foo 的模块,则同时也会在构建系统中创建一个目标,您可以用该目标管理该模块的 API。构建后,foo-freeze-api 会在 api_dir
或 aidl_api/name
下(因 Android 版本而异)添加新的 API 定义,并添加一个 .hash
文件,二者均表示接口的新冻结版本。foo-freeze-api 还会更新 versions_with_info
属性以反映其他版本以及该版本的 imports
。基本上,versions_with_info
中的 imports
是从 imports
字段复制的。不过,最新稳定版本是在 versions_with_info
的 imports
中指定的,用于没有明确版本的导入。指定 versions_with_info
属性之后,构建系统会在冻结版本之间以及树顶部 (ToT) 与最新冻结版本之间运行兼容性检查。
此外,您还需要管理 ToT 版本的 API 定义。每当 API 更新时,运行 foo-update-api,以更新包含 ToT 版本的 API 定义的 aidl_api/name/current
。
为了保持接口的稳定性,所有者可以:
- 在接口的末尾添加新方法(或添加具有显式定义的新序列的方法)
- 在 Parcelable 的末尾添加新元素(需要为每个元素添加一个默认值)
- 添加新常量值
- 在 Android 11 中,添加新枚举器
- 在 Android 12 中,在联合体末尾添加新字段
不允许进行其他任何操作,其他任何人都不能修改接口,否则可能会与所有者进行的更改发生冲突。
如需测试所有接口是否都冻结以供发布,您可以在构建时设置下列环境变量:
AIDL_FROZEN_REL=true m ...
- build 需要冻结所有稳定的 AIDL 接口,这些接口未指定owner:
字段。AIDL_FROZEN_OWNERS="aosp test"
- build 需要冻结所有稳定的 AIDL 接口,并将owner:
字段指定为“aosp”或“test”。
导入的稳定性
为接口冻结版本更新导入版本的操作在稳定 AIDL 层上保持向后兼容。但是,更新它们需要更新使用旧版接口的所有服务器和客户端,一些应用可能会混淆不同类型的版本。通常,对于仅类型软件包或通用软件包,这是安全的,因为需要编写代码来处理来自 IPC 事务的未知类型。
在 Android 平台代码中,android.hardware.graphics.common
是此类版本升级的重要示例。
使用版本化接口
接口方法
在运行时,如果尝试在旧服务器上调用新方法,新客户端便会遇到错误或异常,具体取决于后端。
cpp
后端会获取::android::UNKNOWN_TRANSACTION
。ndk
后端会获取STATUS_UNKNOWN_TRANSACTION
。java
后端会获取android.os.RemoteException
,并显示一条消息,提示该 API 未实现。
Parcelables
如果将新字段添加到 Parcelable,旧客户端和服务器会删除这些字段。如果新客户端和服务器收到旧 Parcelable,系统会自动填入新字段的默认值。这意味着,您需要为 Parcelable 中的所有新字段指定默认值。
客户端不应期望服务器会使用新字段,除非他们知道服务器正在实现已定义相应字段的版本(请参阅查询版本)。
枚举和常量
同样,客户端和服务器应根据需要拒绝或忽略无法识别的常量值和枚举器,因为将来可能会进一步添加。例如,当服务器收到其不知道的枚举器时,它不应中止,服务器应忽略枚举器或返回一些内容,以便客户端知道它在此实现中不受支持。
联合体
如果接收方是旧的并且不了解某个新字段,尝试发送包含该字段的联合体会失败。此实现绝不会看到包含新字段的联合体。如果是单向事务,系统会忽略失败情况;否则,错误为 BAD_VALUE
(对于 C++ 或 NDK 后端)或 IllegalArgumentException
(对于 Java 后端)。如果客户端在向旧服务器发送设为新字段的联合体,或者是从新服务器接收联合体的旧客户端,则会收到此错误。
管理多个版本
Android 中的链接器命名空间只能包含特定 aidl
接口的 1 个版本,以免生成的 aidl
类型具有多个定义。C++ 具有单一定义规则,该规则要求每个符号只有一个定义。
如果某个模块依赖于同一 aidl_interface
库的不同版本,Android build 会显示错误。该模块可能直接或间接(通过其依赖项的依赖项)依赖于这些库。这些错误会显示从失败的模块到 aidl_interface
库的冲突版本的依赖关系图。所有这些依赖项都需要更新,以包含这些库的相同(通常是最新)版本。
如果许多不同的模块使用接口库,那么为需要使用同一版本的任何一组库和进程创建 cc_defaults
、java_defaults
和 rust_defaults
会很有帮助。引入新版接口时,这些默认值可以更新,并且使用这些默认值的所有模块也会一同更新,确保它们不会使用不同版本的接口。
cc_defaults {
name: "my.aidl.my-process-group-ndk-shared",
shared_libs: ["my.aidl-V3-ndk"],
...
}
cc_library {
name: "foo",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
cc_binary {
name: "bar",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
当 aidl_interface
模块导入其他 aidl_interface
模块时,这会创建其他依赖项,这些依赖项需要特定版本才能一起使用。如果在同一进程中一起使用的多个 aidl_interface
模块中导入了通用的 aidl_interface
模块,这种情况可能会变得难以管理。
aidl_interfaces_defaults
可用于保留 aidl_interface
的最新版本依赖项的单一定义,该接口可在一个位置进行更新,并供想要导入该通用接口的所有 aidl_interface
模块使用。
aidl_interface_defaults {
name: "android.popular.common-latest-defaults",
imports: ["android.popular.common-V3"],
...
}
aidl_interface {
name: "android.foo",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
aidl_interface {
name: "android.bar",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
基于标志的开发
处于开发阶段(未冻结)的接口无法在已发布设备上使用,因为它们不能保证具有向后兼容性。
对于这些未冻结的接口库,AIDL 支持运行时回退,以便针对最新未冻结版本编写代码,并继续在已发布设备上使用。客户端的向后兼容行为类似于现有行为,在支持回退的情况下,实现也需要遵循这些行为。请参阅使用版本化接口。
AIDL build 标志
build/release/build_flags.bzl
中定义的 RELEASE_AIDL_USE_UNFROZEN
标志用于控制此行为。true
表示在运行时使用的是接口的未冻结版本,false
表示未冻结版本的库的行为都与上一个冻结版本的行为类似。对于本地开发,您可以将该标志替换为 true
,但在发布之前必须将其还原为 false
。通常,您将通过将标志设置为 true
的配置来完成开发。
兼容性矩阵和清单
供应商接口对象(VINTF 对象)定义了预期的版本以及在供应商接口的任一端提供的版本。
大多数非 Cuttlefish 设备仅在接口冻结后,才会以最新的兼容性矩阵为目标,因此基于 RELEASE_AIDL_USE_UNFROZEN
的 AIDL 库并无差别。
矩阵
合作伙伴所有的接口会添加到设备专用或产品专用的兼容性矩阵,在开发过程中设备会以此矩阵为目标。因此,将新的未冻结版本接口添加到兼容性矩阵时,RELEASE_AIDL_USE_UNFROZEN=false
需要保留先前的冻结版本。您可以通过以下方式完成此操作:为不同的 RELEASE_AIDL_USE_UNFROZEN
配置使用不同的兼容性矩阵文件,或允许在单个兼容性矩阵文件(用于所有配置)中使用两个版本。
例如,添加未冻结的版本 4 时,请使用 <version>3-4</version>
。
当版本 4 被冻结时,您可以从兼容性矩阵中移除版本 3,因为当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,系统会使用冻结版本 4。
清单
在 Android 15 中,引入了 libvintf
的更改,以便根据 RELEASE_AIDL_USE_UNFROZEN
的值在构建时修改清单文件。
清单和清单 fragment 会声明服务实现的接口版本。使用接口的最新未冻结版本时,必须更新清单以反映此新版本。RELEASE_AIDL_USE_UNFROZEN=false
时,清单条目会由 libvintf
进行调整,以反映生成的 AIDL 库中的更改。版本会从未冻结的版本 N
修改为上一个冻结的版本 N - 1
。因此,用户无需为每项服务管理多个清单或清单 fragment。
HAL 客户端更改
HAL 客户端代码必须向后兼容之前支持的所有冻结版本。当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,服务始终类似于上一个冻结版本或更早版本(例如,调用新的未冻结方法会返回 UNKNOWN_TRANSACTION
,或新的 parcelable
字段具有默认值)。Android 框架客户端必须向后兼容先前的其他版本,但对于供应商客户端和合作伙伴自有接口的客户端而言,这是新的详细信息。
HAL 实现变更
在 HAL 开发与基于标志的开发中,最大的区别在于 HAL 实现必须向后兼容最后一个冻结版本,才能在 RELEASE_AIDL_USE_UNFROZEN
为 false
时正常运行。考虑实现和设备代码中的向后兼容性是一项新的实践。请参阅使用版本化接口。
对于客户端和服务器,以及框架代码和供应商代码,向后兼容性注意事项通常是相同的,但您需要注意一些细微的差异,因为您现在相当于要实现使用相同源代码的两个版本(当前版本和未冻结版本)。
示例:某个接口有三个冻结版本。该接口使用新方法进行了更新。客户端和服务均已更新为使用新版本 4 库。由于 V4 库基于接口的未冻结版本,因此当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,其行为类似于上一个冻结版本 3,并会阻止使用新方法。
当接口被冻结时,RELEASE_AIDL_USE_UNFROZEN
的所有值都会使用该冻结版本,并且可移除用于处理向后兼容的代码。
调用回调上的方法时,您必须妥善处理返回 UNKNOWN_TRANSACTION
的情况。客户端可能会根据版本配置,实现两个不同版本的回调,因此您不能假设客户端将发送最新版本,且新方法可能会返回最新版本。这类似于稳定的 AIDL 客户端保持与服务器向后兼容(如使用版本化接口中所述)。
// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
mMyCallback = cb;
// Get the version of the callback for later when we call methods on it
auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
return status;
}
// Example of using the callback later
void NotifyCallbackLater() {
// From the latest frozen version (V2)
mMyCallback->foo();
// Call this method from the unfrozen V3 only if the callback is at least V3
if (mMyCallbackVersion >= 3) {
mMyCallback->bar();
}
}
当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,现有类型(parcelable
、enum
、union
)中的新字段可能不存在或包含其默认值。此外,系统会在退出进程时丢弃服务尝试发送的新字段的值。
无法通过接口发送或接收添加到此未冻结版本的新类型。
当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,实现绝不会调用任何客户端中的新方法。
请务必只对引入新枚举器的版本(而非之前的版本)使用新枚举器。
通常,您可以使用 foo->getInterfaceVersion()
查看远程接口所用的版本。不过,借助基于标志的版本控制支持,您将实现两个不同的版本,因此您可能希望获取当前接口的版本。您可以通过获取当前对象的接口版本(例如 this->getInterfaceVersion()
或 my_ver
的其他方法)来实现此目标。如需了解更多信息,请参阅查询远程对象的接口版本。
新 VINTF 稳定接口
由于添加新 AIDL 接口包时,没有上一个冻结版本,因此当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,没有可回退到的行为。请勿使用这些接口。如果 RELEASE_AIDL_USE_UNFROZEN
为 false
,Service Manager 将不允许服务注册接口,客户端也找不到接口。
您可以根据设备 makefile 中 RELEASE_AIDL_USE_UNFROZEN
标志的值有条件地添加服务:
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
如果服务是较大进程的一部分,因此您无法有条件地将其添加到设备,可以检查该服务是否声明了 IServiceManager::isDeclared()
。如果已声明但注册仍失败,则相应进程将会中止。如果未声明,则预计注册会失败。
将 Cuttlefish 用作开发工具
VINTF 被冻结后,每年我们都会调整框架兼容性矩阵 (FCM) target-level
和 Cuttlefish 的 PRODUCT_SHIPPING_API_LEVEL
,以反映搭载明年版本的设备。我们调整了 target-level
和 PRODUCT_SHIPPING_API_LEVEL
,以确保一些发布的设备通过了测试,并且符合明年版本的新要求。
当 RELEASE_AIDL_USE_UNFROZEN
为 true
时,Cuttlefish 将用于未来的 Android 版本开发。它以明年 Android 版本的 FCM 级别和 PRODUCT_SHIPPING_API_LEVEL
为目标,要求满足下一版本的供应商软件要求 (VSR)。
当 RELEASE_AIDL_USE_UNFROZEN
为 false
时,Cuttlefish 具有之前的 target-level
和 PRODUCT_SHIPPING_API_LEVEL
,以反映发布设备。在 Android 14 及更低版本中,系统将通过不同的 Git 分支实现这种差异化,这些分支不会接受对 FCM target-level
、出厂 API 级别或以下一版本为目标平台的任何其他代码的更改。
模块命名规则
在 Android 11 中,对于每个版本与已启用后端的组合,系统都会自动创建一个桩库模块。如需引用特定的桩库模块以用于链接,请不要使用 aidl_interface
模块的名称,而要使用桩库模块的名称,即 ifacename-version-backend,其中
ifacename
:aidl_interface
模块的名称version
为以下值之一:Vversion-number
,适用于已冻结版本Vlatest-frozen-version-number + 1
,适用于最新(尚未冻结)版本
backend
为以下值之一:java
,适用于 Java 后端;cpp
,适用于 C++ 后端;ndk
或ndk_platform
,适用于 NDK 后端。前者适用于应用,后者适用于平台使用(直到 Android 13)。在 Android 13 及更高版本中,请仅使用ndk
。rust
适用于 Rust 后端。
假设有一个名为 foo 的模块,其最新版本为 2,且同时支持 NDK 和 C++。在这种情况下,AIDL 会生成以下模块:
- 基于版本 1
foo-V1-(java|cpp|ndk|ndk_platform|rust)
- 基于版本 2(最新的稳定版本)
foo-V2-(java|cpp|ndk|ndk_platform|rust)
- 基于 ToT 版本
foo-V3-(java|cpp|ndk|ndk_platform|rust)
与 Android 11 相比:
foo-backend
(引用了最新稳定版)会变为foo-V2-backend
foo-unstable-backend
(引用了 ToT 版本)会变为foo-V3-backend
输出文件名始终与模块名称相同。
- 基于版本 1:
foo-V1-(cpp|ndk|ndk_platform|rust).so
- 基于版本 2:
foo-V2-(cpp|ndk|ndk_platform|rust).so
- 基于 ToT 版本:
foo-V3-(cpp|ndk|ndk_platform|rust).so
请注意,AIDL 编译器不会为稳定的 AIDL 接口创建 unstable
版本模块或非版本化模块。从 Android 12 开始,从稳定的 AIDL 接口生成的模块名称始终包含其版本。
新增的元接口方法
Android 10 针对稳定的 AIDL 推出了几种新的元接口方法。
查询远程对象的接口版本
客户端可以查询远程对象正在实现的接口版本和哈希,并将返回的值与客户端正在使用的接口值进行比较。
使用 cpp
后端的示例:
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
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
使用 ndk
(和 ndk_platform
)后端的示例:
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
使用 java
后端的示例:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
对于 Java 语言,远程端必须按照以下方式实现 getInterfaceVersion()
和 getInterfaceHash()
(使用 super
而不是 IFoo
,以避免复制和粘贴错误)。根据 javac
配置,可能需要使用注解 @SuppressWarnings("static")
来停用警告:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
这是因为生成的类(IFoo
、IFoo.Stub
等)将在客户端和服务器之间共享(例如,这些类可以位于启动类路径下)。共享类时,服务器仍会链接到类的最新版本,即使该版本可能是用接口的旧版本构建的。如果该元接口是在共享类中实现的,该接口始终会返回最新版本。不过,如果按照上述方式实现该方法,便会将接口的版本号嵌入到服务器的代码中(因为 IFoo.VERSION
是一个在引用时内嵌的 static final int
),从而使该方法能够返回构建该服务器时所用的确切版本。
处理旧版接口
可能会存在以下情况:客户端更新了较新的 AIDL 接口版本,而服务器使用的是旧版 AIDL 接口。在这种情况下,在旧接口上调用方法会返回 UNKNOWN_TRANSACTION
。
使用稳定的 AIDL,客户端将拥有更多的控制权。在客户端,您可以设置 AIDL 接口的默认实现。对于默认实现中的方法,只有当其没有在远程端实现时,才会被调用。这是因为此方法是使用旧版接口构建的。由于默认值是全局设置的,因此不应该在可能会共享的上下文中使用。
Android 13 及更高版本中的 C++ 示例:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
采用 Java 语言的示例:
IFoo.Stub.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
软件包(如上所述),其中应包含模块的名称和依赖项以及您需要的任何其他信息。为使其保持稳定(不仅仅是结构化),还需要对其进行版本编号。如需了解详情,请参阅对接口进行版本编号。