Google 致力于为黑人社区推动种族平等。查看具体举措

稳定的 AIDL

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-javamy-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;
}

现在支持 booleancharfloatdoublebyteintlongString 的默认值(但不是必需的)。

使用存根库

将存根库作为依赖项添加到模块之后,您可以将这些库添加到您的文件中。下面是编译系统中的存根库的示例(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; }
}

这是因为所生成的类(IFooIFoo.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 接口。

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

  2. 将您的接口中的所有 Parcelable 全部转换为稳定的 Parcelable(接口文件本身可以保持不变)。可通过直接在 AIDL 文件中表示它们的结构来实现这一点。您必须重写管理类以使用这些新类型。可以在创建 aidl_interface 软件包(如下所示)之前完成重写。

  3. 创建 aidl_interface 软件包(如上所述),其中应包含模块的名称和依赖项以及您需要的任何其他信息。为使其保持稳定(不仅仅是结构化),您还需要指定 api_dir 路径。