稳定的 AIDL

Android 10 增加了对稳定 Android 接口定义语言 (AIDL) 的支持,这是一种跟踪 AIDL 接口提供的应用程序编程接口 (API) 和应用程序二进制接口 (ABI) 的新方法。稳定 AIDL 的工作方式与 AIDL 完全相同,但构建系统会跟踪接口兼容性,并且对您可以执行的操作有限制

  • 接口在构建系统中通过 aidl_interfaces 定义。
  • 接口只能包含结构化数据。代表首选类型的 Parcelable 会根据其 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 开始,versionsaidl_api/name 下冻结。如果接口没有冻结版本,则不应指定此项,也不会进行兼容性检查。对于 Android 13 及更高版本,此字段已替换为 versions_with_info
  • versions_with_info:元组列表,每个元组包含一个冻结版本的名称以及一个列表,该列表包含此 AIDL 接口版本导入的其他 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 开始,对于 cppjava 后端,默认值为 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 后端。如果不需要这三个后端中的任何一个,则需要显式禁用它。在 Android 15 之前,Rust 默认禁用。
  • 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_headercpp_header 非常有用。
  • backend.java.sdk_version:可选标志,用于指定 Java 桩库构建所针对的 SDK 版本。默认值为 "system_current"。当 backend.java.platform_apistrue 时,不应设置此项。
  • backend.java.platform_apis:可选标志,当生成的库需要针对平台 API 而不是 SDK 构建时,应设置为 true

对于版本和启用的后端的每种组合,都会创建一个桩库。有关如何引用特定后端的桩库的特定版本,请参阅模块命名规则

编写 AIDL 文件

稳定 AIDL 中的接口类似于传统接口,但例外是它们不允许使用非结构化 Parcelable(因为这些是不稳定的!请参阅结构化与稳定 AIDL)。稳定 AIDL 的主要区别在于 Parcelable 的定义方式。以前,Parcelable 是前向声明的;在稳定(因此也是结构化的)AIDL 中,Parcelable 字段和变量是显式定义的。

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

对于 booleancharfloatdoublebyteintlongString,支持(但不是必需)默认值。在 Android 12 中,还支持用户定义的枚举的默认值。如果未指定默认值,则使用类似 0 或空值的值。即使没有零枚举器,没有默认值的枚举也会初始化为 0。

使用桩库

将桩库作为依赖项添加到模块后,您可以将它们包含到您的文件中。以下是在构建系统中使用桩库的示例(Android.mk 也可用于旧版模块定义)。请注意,在这些示例中,版本不存在,因此它表示使用不稳定的接口,但具有版本的接口的名称包含其他信息,请参阅版本控制接口

cc_... {
    name: ...,
    // use `shared_libs:` to load your library and its transitive dependencies
    // dynamically
    shared_libs: ["my-module-name-cpp"],
    // use `static_libs:` to include the library in this binary and drop
    // transitive dependencies
    static_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // use `static_libs:` to add all jars and classes to this jar
    static_libs: ["my-module-name-java"],
    // use `libs:` to make these classes available during build time, but
    // not add them to the jar, in case the classes are already present on the
    // boot classpath (such as if it's in framework.jar) or another jar.
    libs: ["my-module-name-java"],
    // use `srcs:` with `-java-sources` if you want to add classes in this
    // library jar directly, but you get transitive dependencies from
    // somewhere else, such as the boot classpath or another jar.
    srcs: ["my-module-name-java-source", ...],
    ...
}
# 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_diraidl_api/name 下添加新的 API 定义,具体取决于 Android 版本,并添加一个 .hash 文件,两者都表示接口的新冻结版本。foo-freeze-api 还会更新 versions_with_info 属性以反映额外的版本和版本的 importsversions_with_info 中的 imports 基本上是从 imports 字段复制的。但是,最新的稳定版本在 versions_with_info 中为导入指定的 imports 中,它没有显式版本。在指定 versions_with_info 属性后,构建系统会在冻结版本之间以及树顶 (ToT) 和最新的冻结版本之间运行兼容性检查。

此外,您需要管理 ToT 版本的 API 定义。每当 API 更新时,运行 foo-update-api 以更新 aidl_api/name/current,其中包含 ToT 版本的 API 定义。

为了保持接口的稳定性,所有者可以添加新的

  • 方法到接口的末尾(或具有显式定义的新序列号的方法)
  • 元素到 Parcelable 的末尾(需要为每个元素添加默认值)
  • 常量值
  • 在 Android 11 中,枚举器
  • 在 Android 12 中,字段到联合的末尾

不允许其他操作,并且任何人都不能修改接口(否则他们可能会与所有者所做的更改发生冲突)。

要测试所有接口是否都已冻结以进行发布,您可以使用以下环境变量集进行构建

  • AIDL_FROZEN_REL=true m ... - 构建要求所有没有指定 owner: 字段的稳定 AIDL 接口都必须冻结。
  • AIDL_FROZEN_OWNERS="aosp test" - 构建要求所有 owner: 字段指定为“aosp”或“test”的稳定 AIDL 接口都必须冻结。

导入的稳定性

在稳定 AIDL 层,更新接口的冻结版本的导入版本是向后兼容的。但是,更新这些版本需要更新所有使用先前版本的接口的服务器和客户端,并且当混合使用不同类型的版本时,某些应用可能会感到困惑。通常,对于仅类型或通用软件包,这是安全的,因为代码需要已编写为处理来自 IPC 事务的未知类型。

在 Android 平台代码中,android.hardware.graphics.common 是此类版本升级的最大示例。

使用版本控制的接口

接口方法

在运行时,当尝试在旧服务器上调用新方法时,新客户端会收到错误或异常,具体取决于后端。

  • cpp 后端获取 ::android::UNKNOWN_TRANSACTION
  • ndk 后端获取 STATUS_UNKNOWN_TRANSACTION
  • java 后端获取 android.os.RemoteException,并显示消息说明 API 未实现。

有关处理此问题的策略,请参阅查询版本使用默认值

Parcelable

当新字段添加到 Parcelable 时,旧客户端和服务器会丢弃它们。当新客户端和服务器收到旧的 Parcelable 时,会自动填充新字段的默认值。这意味着需要为 Parcelable 中的所有新字段指定默认值。

客户端不应期望服务器使用新字段,除非他们知道服务器正在实现定义该字段的版本(请参阅查询版本)。

枚举和常量

同样,客户端和服务器应酌情拒绝或忽略无法识别的常量值和枚举器,因为将来可能会添加更多。例如,当服务器收到它不了解的枚举器时,不应中止。服务器应忽略枚举器,或返回某些内容,以便客户端知道此实现中不支持它。

联合

如果接收方是旧的并且不了解该字段,则尝试发送带有新字段的联合会失败。实现永远不会看到带有新字段的联合。如果它是单向事务,则忽略失败;否则,错误为 BAD_VALUE(对于 C++ 或 NDK 后端)或 IllegalArgumentException(对于 Java 后端)。如果客户端正在向旧服务器发送设置为新字段的联合,或者当旧客户端从新服务器接收联合时,会收到错误。

管理多个版本

Android 中的链接器命名空间只能有一个特定 aidl 接口的版本,以避免生成的 aidl 类型具有多个定义的情况。C++ 具有单一定义规则,该规则要求每个符号只有一个定义。

当模块依赖于同一 aidl_interface 库的不同版本时,Android 构建会提供错误。模块可能直接或通过其依赖项的依赖项依赖于这些库。这些错误显示了从失败模块到 aidl_interface 库的冲突版本的依赖关系图。所有依赖项都需要更新为包含这些库的相同版本(通常是最新的)。

如果接口库被许多不同的模块使用,则为任何需要使用相同版本的库和进程组创建 cc_defaultsjava_defaultsrust_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/release/build_flags.bzl 中定义的 RELEASE_AIDL_USE_UNFROZENtrue 表示在运行时使用接口的未冻结版本,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_UNFROZENfalse 时,会使用冻结版本 4。

清单

在 Android 15 中,引入了 libvintf 中的更改,以基于 RELEASE_AIDL_USE_UNFROZEN 的值在构建时修改清单文件。

清单和清单片段声明了服务实现了哪个版本的接口。当使用接口的最新未冻结版本时,必须更新清单以反映此新版本。当 RELEASE_AIDL_USE_UNFROZEN=false 时,清单条目由 libvintf 调整以反映生成的 AIDL 库中的更改。版本从未冻结版本 N 修改为最新的冻结版本 N - 1。因此,用户无需为每个服务管理多个清单或清单片段。

HAL 客户端更改

HAL 客户端代码必须与每个先前的受支持冻结版本向后兼容。当 RELEASE_AIDL_USE_UNFROZENfalse 时,服务始终看起来像最新的冻结版本或更早版本(例如,调用新的未冻结方法会返回 UNKNOWN_TRANSACTION,或者新的 parcelable 字段具有其默认值)。Android 框架客户端需要与额外的先前版本向后兼容,但这对于供应商客户端和合作伙伴拥有的接口的客户端来说是一个新的细节。

HAL 实现更改

基于标志的 HAL 开发中最大的区别是 HAL 实现需要与最新的冻结版本向后兼容,以便在 RELEASE_AIDL_USE_UNFROZENfalse 时工作。在实现和设备代码中考虑向后兼容性是一项新的练习。请参阅使用版本控制的接口

向后兼容性注意事项通常对于客户端和服务器以及框架代码和供应商代码是相同的,但是您需要注意一些细微的差异,因为您现在正在有效地实现使用相同源代码的两个版本(当前的未冻结版本)。

示例:一个接口有三个冻结版本。该接口使用新方法更新。客户端和服务都已更新为使用新的版本 4 库。由于 V4 库基于接口的未冻结版本,因此当 RELEASE_AIDL_USE_UNFROZENfalse 时,它的行为类似于最新的冻结版本版本 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();
  }
}

现有类型(parcelableenumunion)中的新字段可能不存在或包含其默认值,当 RELEASE_AIDL_USE_UNFROZENfalse 时,服务尝试发送的新字段的值会在离开进程时被丢弃。

在此未冻结版本中添加的新类型无法通过接口发送或接收。

RELEASE_AIDL_USE_UNFROZENfalse 时,实现永远不会收到来自任何客户端的新方法的调用。

请注意仅在引入新枚举器的版本中使用它们,而不是在之前的版本中使用。

通常,您使用 foo->getInterfaceVersion() 来查看远程接口正在使用的版本。但是,通过基于标志的版本控制支持,您正在实现两个不同的版本,因此您可能想要获取当前接口的版本。您可以通过获取当前对象的接口版本来做到这一点,例如,this->getInterfaceVersion()my_ver 的其他方法。有关更多信息,请参阅查询远程对象的接口版本

新的 VINTF 稳定接口

当添加新的 AIDL 接口包时,没有最新的冻结版本,因此当 RELEASE_AIDL_USE_UNFROZENfalse 时,没有回退行为。请勿使用这些接口。当 RELEASE_AIDL_USE_UNFROZENfalse 时,Service Manager 不允许服务注册接口,并且客户端找不到它。

您可以根据设备 makefile 中 RELEASE_AIDL_USE_UNFROZEN 标志的值有条件地添加服务

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

如果服务是较大进程的一部分,因此您无法有条件地将其添加到设备,则可以检查是否使用 IServiceManager::isDeclared() 声明了服务。如果已声明但注册失败,则中止进程。如果未声明,则预期注册失败。

新的 VINTF 稳定扩展接口

新的 扩展接口 没有先前的版本可以回退,并且由于它们未在 ServiceManager 中注册或在 VINTF 清单中声明,因此 IServiceManager::isDeclared() 无法用于确定何时将扩展接口附加到另一个接口。

RELEASE_AIDL_USE_UNFROZEN 变量可用于确定是否将新的未冻结扩展接口附加到现有接口,以避免在发布的设备上使用它。接口需要冻结才能在发布的设备上使用。

vts_treble_vintf_vendor_testvts_treble_vintf_framework_test VTS 测试检测何时在发布的设备中使用未冻结的扩展接口并抛出错误。

如果扩展接口不是新的并且具有先前的冻结版本,则它会回退到该先前的冻结版本,并且不需要额外的步骤。

Cuttlefish 作为开发工具

每年在 VINTF 冻结后,我们会调整框架兼容性矩阵 (FCM) target-level 和 Cuttlefish 的 PRODUCT_SHIPPING_API_LEVEL,以便它们反映使用明年版本发布的设备。我们调整 target-levelPRODUCT_SHIPPING_API_LEVEL 以确保有一些发布设备经过测试并满足明年发布的新要求。

RELEASE_AIDL_USE_UNFROZENtrue 时,Cuttlefish 用于未来 Android 版本的开发。它以明年 Android 发布的 FCM 级别和 PRODUCT_SHIPPING_API_LEVEL 为目标,要求它满足下一个版本的供应商软件要求 (VSR)。

RELEASE_AIDL_USE_UNFROZENfalse 时,Cuttlefish 具有先前的 target-levelPRODUCT_SHIPPING_API_LEVEL 以反映发布设备。在 Android 14 及更低版本中,这种区分将通过不采用 FCM target-level、发布 API 级别或任何其他针对下一个版本的代码的不同的 Git 分支来完成。

模块命名规则

在 Android 11 中,对于版本和启用的后端的每种组合,都会自动创建一个桩库模块。要引用特定的桩库模块进行链接,请不要使用 aidl_interface 模块的名称,而是使用桩库模块的名称,即 ifacename-version-backend,其中

  • ifacenameaidl_interface 模块的名称
  • version 是以下之一
    • Vversion-number 用于冻结版本
    • Vlatest-frozen-version-number + 1 用于树顶(尚未冻结)版本
  • backend 是以下之一
    • java 用于 Java 后端,
    • cpp 用于 C++ 后端,
    • ndkndk_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 以避免复制和粘贴错误。@SuppressWarnings("static") 注释可能需要禁用警告,具体取决于 javac 配置)

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

这是因为生成的类(IFooIFoo.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 接口中提供所有方法的默认实现。保证在远程端实现的方法(因为您确定远程端是在 AIDL 接口描述中包含这些方法时构建的)无需在默认的 impl 类中被替换。

将现有的 AIDL 转换为结构化或稳定 AIDL

如果您有现有的 AIDL 接口以及使用它的代码,请使用以下步骤将该接口转换为稳定 AIDL 接口。

  1. 确定接口的所有依赖项。对于接口依赖的每个软件包,确定该软件包是否在稳定 AIDL 中定义。如果未定义,则必须转换该软件包。

  2. 将接口中的所有 parcelable 转换为稳定 parcelable(接口文件本身可以保持不变)。通过在 AIDL 文件中直接表达其结构来完成此操作。管理类必须重写以使用这些新类型。这可以在您创建 aidl_interface 软件包(如下所述)之前完成。

  3. 创建一个 aidl_interface 软件包(如上所述),其中包含模块的名称、其依赖项以及您需要的任何其他信息。要使其稳定(而不仅仅是结构化),还需要对其进行版本控制。有关更多信息,请参阅 版本控制接口