实现配置文件架构 API

Android 平台包含许多 XML 文件,用于存储配置数据(例如,音频配置)。许多 XML 文件位于 vendor 分区中,但它们在 system 分区中读取。在这种情况下,XML 文件的架构充当跨两个分区的接口,因此必须显式指定架构,并且必须以向后兼容的方式演变。

在 Android 10 之前,该平台没有提供任何机制来要求指定和使用 XML 架构,或防止架构中出现不兼容的更改。Android 10 提供了这种机制,称为配置文件架构 API。此机制包括一个名为 xsdc 的工具和一个名为 xsd_config 的构建规则。

xsdc 工具是一个 XML 架构文档 (XSD) 编译器。它解析一个 XSD 文件,该文件描述 XML 文件的架构,并生成 Java 和 C++ 代码。生成的代码将符合 XSD 架构的 XML 文件解析为对象树,每个对象都对 XML 标记建模。XML 属性被建模为对象的字段。

xsd_config 构建规则将 xsdc 工具集成到构建系统中。对于给定的 XSD 输入文件,构建规则会生成 Java 和 C++ 库。您可以将这些库链接到读取和使用符合 XSD 的 XML 文件的模块。您可以为自己的 XML 文件使用构建规则,这些文件跨 systemvendor 分区使用。

构建配置文件架构 API

本节介绍如何构建配置文件架构 API。

在 Android.bp 中配置 xsd_config 构建规则

xsd_config 构建规则使用 xsdc 工具生成解析器代码。xsd_config 构建规则的 package_name 属性确定生成的 Java 代码的软件包名称。

Android.bp 中的 xsd_config 构建规则示例

xsd_config {
    name: "hal_manifest",
    srcs: ["hal_manifest.xsd"],
    package_name: "hal.manifest",
}

目录结构示例

├── Android.bp
├── api
│   ├── current.txt
│   ├── last_current.txt
│   ├── last_removed.txt
│   └── removed.txt
└── hal_manifest.xsd

构建系统使用生成的 Java 代码生成 API 列表,并将 API 与其进行检查。此 API 检查已添加到 DroidCore,并在 m -j 时执行。

创建 API 列表文件

API 检查需要在源代码中使用 API 列表文件。

API 列表文件包括

  • current.txtremoved.txt 检查 API 是否已更改,方法是将 API 与构建时生成的 API 文件进行比较。
  • last_current.txtlast_removed.txt 检查 API 是否向后兼容,方法是将 API 与 API 文件进行比较。

要创建 API 列表文件,请执行以下操作:

  1. 创建空列表文件。
  2. 运行命令 make update-api

使用生成的解析器代码

要使用生成的 Java 代码,请在 Java srcs 属性中,将 : 作为 xsd_config 模块名称的前缀。生成的 Java 代码的软件包与 package_name 属性相同。

java_library {
    name: "vintf_test_java",
    srcs: [
        "srcs/**/*.java"
        ":hal_manifest"
    ],
}

要使用生成的 C++ 代码,请将 xsd_config 模块名称添加到 generated_sourcesgenerated_headers 属性。并将 libxml2 添加到 static_libsshared_libs,因为生成的解析器代码中需要 libxml2。生成的 C++ 代码的命名空间与 package_name 属性相同。例如,如果 xsd_config 模块名称为 hal.manifest,则命名空间为 hal::manifest

cc_library{
    name: "vintf_test_cpp",
    srcs: ["main.cpp"],
    generated_sources: ["hal_manifest"],
    generated_headers: ["hal_manifest"],
    shared_libs: ["libxml2"],
}

使用解析器

要使用 Java 解析器代码,请使用 XmlParser#readread{class-name} 方法来返回根元素的类。解析在此刻进行。

import hal.manifest.*;



class HalInfo {
    public String name;
    public String format;
    public String optional;
    
}

void readHalManifestFromXml(File file) {
    
    try (InputStream str = new BufferedInputStream(new FileInputStream(file))) {
        Manifest manifest = XmlParser.read(str);
        for (Hal hal : manifest.getHal()) {
            HalInfo halinfo;
            HalInfo.name = hal.getName();
            HalInfo.format = hal.getFormat();
            HalInfo.optional = hal.getOptional();
            
        }
    }
    
}

要使用 C++ 解析器代码,首先包含头文件。头文件的名称是包名称,句点 (.) 转换为下划线 (_)。然后使用 readread{class-name} 方法来返回根元素的类。解析在此刻进行。返回值是 std::optional<>

include "hal_manifest.h"


using namespace hal::manifest

struct HalInfo {
    public std::string name;
    public std::string format;
    public std::string optional;
    
};

void readHalManifestFromXml(std::string file_name) {
    
    Manifest manifest = *read(file_name.c_str());
    for (Hal hal : manifest.getHal()) {
        struct HalInfo halinfo;
        HalInfo.name = hal.getName();
        HalInfo.format = hal.getFormat();
        HalInfo.optional = hal.getOptional();
        
    }
    
}

所有为使用解析器而提供的 API 都位于 api/current.txt 中。为了保持一致性,所有元素和属性名称都转换为驼峰式大小写(例如,ElementName)并用作相应的变量、方法和类名称。可以使用 read{class-name} 函数获取已解析根元素的类。如果只有一个根元素,则函数名称为 read。可以使用 get{variable-name} 函数获取已解析子元素或属性的值。

生成解析器代码

在大多数情况下,您不需要直接运行 xsdc。请改用 xsd_config 构建规则,如在 Android.bp 中配置 xsd_config 构建规则中所述。本节仅为了完整性而介绍 xsdc 命令行界面。这可能对调试很有用。

您必须为 xsdc 工具提供 XSD 文件的路径和一个软件包。该软件包是 Java 代码中的包名称和 C++ 代码中的命名空间。用于确定生成的代码是 Java 还是 C 的选项分别是 -j-c-o 选项是输出目录的路径。

usage: xsdc path/to/xsd_file.xsd [-c] [-j] [-o <arg>] [-p]
 -c,--cpp           Generate C++ code.
 -j,--java          Generate Java code.
 -o,--outDir <arg>  Out Directory
 -p,--package       Package name of the generated java file. file name of
                    generated C++ file and header

示例命令

$ xsdc audio_policy_configuration.xsd -p audio.policy -j