Config File Schema API implementieren

Die Android-Plattform enthält viele XML-Dateien zum Speichern von Konfigurationsdaten (z. B. Audiokonfiguration). Viele der XML-Dateien befinden sich in der Partition vendor, werden aber in der Partition system gelesen. In diesem Fall dient das Schema der XML-Datei als Schnittstelle zwischen den beiden Partitionen. Daher muss das Schema explizit angegeben und auf rückwärtskompatible Weise weiterentwickelt werden.

Vor Android 10 bot die Plattform keine Mechanismen, die die Angabe und Verwendung des XML-Schemas erfordern oder inkompatible Änderungen am Schema verhindern konnten. Android 10 bietet diesen Mechanismus, die sogenannte Config File Schema API. Dieser Mechanismus besteht aus einem Tool namens xsdc und einer Build-Regel namens xsd_config.

Das xsdc-Tool ist ein XSD-Compiler (XML Schema Document). Es analysiert eine XSD-Datei, die das Schema einer XML-Datei beschreibt, und generiert Java- und C++-Code. Der generierte Code analysiert XML-Dateien, die dem XSD-Schema entsprechen, in einen Objektbaum, der jeweils ein XML-Tag modelliert. XML-Attribute werden als Felder der Objekte modelliert.

Mit der xsd_config-Build-Regel wird das xsdc-Tool in das Build-System eingebunden. Für eine bestimmte XSD-Eingabedatei generiert die Build-Regel Java- und C++-Bibliotheken. Sie können die Bibliotheken mit den Modulen verknüpfen, in denen die XML-Dateien gelesen und verwendet werden, die der XSD entsprechen. Sie können die Build-Regel für Ihre eigenen XML-Dateien verwenden, die in den Partitionen system und vendor verwendet werden.

Build Config File Schema API

In diesem Abschnitt wird beschrieben, wie Sie die Config File Schema API erstellen.

Build-Regel „xsd_config“ in Android.bp konfigurieren

Die xsd_config-Build-Regel generiert den Parsercode mit dem xsdc-Tool. Die Property package_name der Build-Regel xsd_config bestimmt den Paketnamen des generierten Java-Codes.

Beispiel für eine xsd_config-Build-Regel in Android.bp:

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

Beispiel für eine Verzeichnisstruktur:

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

Das Build-System generiert eine API-Liste mit dem generierten Java-Code und prüft die API anhand dieser Liste. Diese API-Prüfung wird DroidCore hinzugefügt und bei m -j ausgeführt.

API-Listendateien erstellen

Für die API-Prüfungen sind API-Listendateien im Quellcode erforderlich.

Die API-Listen enthalten Dateien mit folgenden Informationen:

  • current.txt und removed.txt prüfen, ob die APIs geändert wurden, indem sie mit generierten API-Dateien zum Zeitpunkt der Build-Erstellung verglichen werden.
  • last_current.txt und last_removed.txt prüfen, ob die APIs abwärtskompatibel sind, indem sie sie mit API-Dateien vergleichen.

So erstellen Sie die API-Listendateien:

  1. Erstellen Sie leere Listendateien.
  2. Führen Sie den Befehl make update-api aus.

Generierten Parsercode verwenden

Wenn Sie den generierten Java-Code verwenden möchten, fügen Sie in der Java-Eigenschaft srcs dem Namen des xsd_config-Moduls das Präfix : hinzu. Das Paket des generierten Java-Codes ist mit der package_name-Property identisch.

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

Wenn Sie den generierten C++-Code verwenden möchten, fügen Sie den Namen des xsd_config-Moduls den Eigenschaften generated_sources und generated_headers hinzu. Fügen Sie libxml2 zu static_libs oder shared_libs hinzu, da libxml2 im generierten Parsercode erforderlich ist. Der Namespace des generierten C++-Codes entspricht dem Attribut package_name. Beispiel: Wenn der Name des Moduls xsd_config hal.manifest lautet, ist der Namespace hal::manifest.

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

Parser verwenden

Wenn Sie den Java-Parsercode verwenden möchten, verwenden Sie die Methode XmlParser#read oder read{class-name}, um die Klasse des Stammelements zurückzugeben. Das Parsen erfolgt zu diesem Zeitpunkt.

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();
            …
        }
    }
    …
}

Fügen Sie zur Verwendung des C++-Parsercodes zuerst die Headerdatei ein. Der Name der Kopfdatei ist der Paketname, wobei Punkte (.) durch Unterstriche (_) ersetzt werden. Verwenden Sie dann die Methode read oder read{class-name}, um die Klasse des Stammelements zurückzugeben. Das Parsen erfolgt zu diesem Zeitpunkt. Der Rückgabewert ist ein 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();
        …
    }
    …
}

Alle APIs, die für die Verwendung des Parsers bereitgestellt werden, finden Sie unter api/current.txt. Zur Einheitlichkeit werden alle Element- und Attributnamen in die Camel-Case-Schreibweise konvertiert (z. B. ElementName) und als entsprechende Variable, Methode und Klassenname verwendet. Die Klasse des geparsten Stammelements kann mit der Funktion read{class-name} abgerufen werden. Wenn es nur ein Stammelement gibt, lautet der Funktionsname read. Der Wert eines geparsten Unterelements oder Attributs kann mit der Funktion get{variable-name} abgerufen werden.

Parsercode generieren

In den meisten Fällen müssen Sie xsdc nicht direkt ausführen. Verwenden Sie stattdessen die Build-Regel xsd_config, wie unter xsd_config-Build-Regel in Android.bp konfigurieren beschrieben. In diesem Abschnitt wird die xsdc-Befehlszeile beschrieben. Das kann bei der Fehlerbehebung hilfreich sein.

Sie müssen dem xsdc-Tool den Pfad zur XSD-Datei und ein Paket angeben. Das Paket ist ein Paketname im Java-Code und ein Namespace im C++-Code. Die Optionen, um zu bestimmen, ob der generierte Code Java oder C ist, sind -j bzw. -c. Die Option -o ist der Pfad des Ausgabeverzeichnisses.

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

Beispielbefehl:

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