Implementieren der Konfigurationsdateischema-API

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

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

Das xsdc Tool ist ein XML Schema Document (XSD)-Compiler. 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 Baum von Objekten, von denen jedes ein XML-Tag modelliert. XML-Attribute werden als Felder der Objekte modelliert.

Die Build-Regel xsd_config integriert das xsdc Tool in das Build-System. 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 XSD-konformen XML-Dateien gelesen und verwendet werden. Sie können die Build-Regel für Ihre eigenen XML-Dateien verwenden, die in den system und vendor verwendet werden.

Erstellen einer Konfigurationsdatei-Schema-API

In diesem Abschnitt wird beschrieben, wie Sie die Konfigurationsdateischema-API erstellen.

Konfigurieren der Build-Regel xsd_config in Android.bp

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

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

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

Beispielverzeichnisstruktur:

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

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

Erstellen von API-Listendateien

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

Zu den API-Listendateien gehören:

  • current.txt und removed.txt prüfen, ob sich die APIs geändert haben, indem sie sie mit den generierten API-Dateien zur Erstellungszeit vergleichen.
  • last_current.txt und last_removed.txt prüfen durch Vergleich mit API-Dateien, ob die APIs abwärtskompatibel sind.

So erstellen Sie die API-Listendateien:

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

Verwendung des generierten Parser-Codes

Um den generierten Java-Code zu verwenden, fügen Sie : als Präfix zum xsd_config Modulnamen in der Java- srcs Eigenschaft hinzu. Das Paket des generierten Java-Codes ist dasselbe wie die Eigenschaft package_name .

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

Um den generierten C++-Code zu verwenden, fügen Sie den Modulnamen xsd_config “ zu den Eigenschaften generated_sources “ und generated_headers “ hinzu. Und fügen Sie libxml2 zu static_libs oder shared_libs hinzu, da libxml2 im generierten Parsercode erforderlich ist. Der Namespace des generierten C++-Codes ist derselbe wie die Eigenschaft package_name . Wenn der Name des xsd_config Moduls beispielsweise hal.manifest lautet, lautet der Namespace hal::manifest .

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

Verwendung des Parsers

Um den Java-Parser-Code zu verwenden, 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();
            …
        }
    }
    …
}

Um den C++-Parsercode zu verwenden, schließen Sie zunächst die Header-Datei ein. Der Name der Header-Datei ist der Paketname mit Punkten (.), die in Unterstriche (_) umgewandelt 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 zur Verwendung des Parsers bereitgestellten APIs befinden sich in api/current.txt . Aus Gründen der Einheitlichkeit werden alle Element- und Attributnamen in Kamel-Schreibweise umgewandelt (z. B. ElementName ) und als entsprechende Variable, Methode und Klassenname verwendet. Die Klasse des analysierten Stammelements kann mit der Funktion read{ class-name } abgerufen werden. Wenn nur ein Wurzelelement vorhanden ist, lautet der Funktionsname read . Der Wert eines analysierten Unterelements oder Attributs kann mit der Funktion get{ variable-name } abgerufen werden.

Parser-Code generieren

In den meisten Fällen müssen Sie xsdc nicht direkt ausführen. Verwenden Sie stattdessen die Build-Regel xsd_config , wie unter Konfigurieren der Build-Regel xsd_config in Android.bp beschrieben. In diesem Abschnitt wird der Vollständigkeit halber die xsdc Befehlszeilenschnittstelle erläutert. Dies kann zum Debuggen nützlich sein.

Sie müssen dem xsdc Tool den Pfad zur XSD-Datei und ein Paket geben. Das Paket ist ein Paketname im Java-Code und ein Namespace im C++-Code. Die Optionen zur Bestimmung, 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