Платформа Android содержит множество XML-файлов для хранения данных конфигурации (например, конфигурации звука). Многие из этих XML-файлов находятся в разделе vendor , но считываются в system разделе. В этом случае схема XML-файла служит интерфейсом между двумя разделами, поэтому схема должна быть явно указана и должна развиваться с обеспечением обратной совместимости.
До Android 10 платформа не предоставляла механизмов, позволяющих указывать и использовать XML-схему или предотвращать несовместимые изменения в схеме. Android 10 предоставляет такой механизм, называемый Config File Schema API. Этот механизм состоит из инструмента xsdc и правила сборки xsd_config .
Инструмент xsdc — это компилятор XML-схем (XSD). Он анализирует XSD-файл, описывающий схему XML-файла, и генерирует код на Java и C++. Сгенерированный код преобразует XML-файлы, соответствующие схеме XSD, в дерево объектов, каждый из которых моделирует XML-тег. Атрибуты XML моделируются как поля объектов.
Правило сборки xsd_config интегрирует инструмент xsdc в систему сборки. Для заданного входного файла XSD правило сборки генерирует библиотеки Java и C++. Вы можете связать эти библиотеки с модулями, где считываются и используются XML-файлы, соответствующие XSD. Вы можете использовать это правило сборки для собственных XML-файлов, используемых в system и vendor разделах.
API для создания схемы файла конфигурации
В этом разделе описывается, как создать API для схемы конфигурационного файла.
Настройте правило сборки xsd_config в файле Android.bp.
Правило сборки xsd_config генерирует код парсера с помощью инструмента xsdc . Свойство package_name правила сборки xsd_config определяет имя пакета генерируемого Java-кода.
Пример правила сборки xsd_config в Android.bp :
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
Система сборки генерирует список API, используя сгенерированный Java-код, и проверяет соответствие API этому списку. Эта проверка API добавляется в DroidCore и выполняется при выполнении команды m -j .
Создание файлов списков API
Для проверки API требуются файлы со списками API в исходном коде.
В список файлов, доступных через API, входят:
-
current.txtиremoved.txtпроверяют, были ли изменены API, сравнивая их с файлами API, сгенерированными во время сборки. -
last_current.txtиlast_removed.txtпроверяют обратную совместимость API путем сравнения с файлами API.
Для создания файлов списков API:
- Создайте пустые файлы списков.
- Выполните команду
make update-api.`
Используйте сгенерированный код парсера
Чтобы использовать сгенерированный Java-код, добавьте двоеточие : в качестве префикса к имени модуля xsd_config в свойстве Java srcs . Пакет сгенерированного Java-кода совпадает со свойством package_name .
java_library {
name: "vintf_test_java",
srcs: [
"srcs/**/*.java"
":hal_manifest"
],
}
Чтобы использовать сгенерированный код C++, добавьте имя модуля xsd_config в свойства generated_sources и generated_headers . И добавьте libxml2 в static_libs или shared_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#read или read{ 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++ сначала подключите заголовочный файл. Имя заголовочного файла — это имя пакета, в котором точки (.) заменены на подчеркивания (_). Затем используйте метод read или read{ 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 , как описано в разделе «Настройка правила сборки xsd_config в Android.bp» . В этом разделе для полноты картины объясняется интерфейс командной строки 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