Platforma Android zawiera wiele plików XML do przechowywania danych konfiguracyjnych (np. konfiguracji dźwięku). Wiele plików XML znajduje się w partycji vendor, ale są one odczytywane w partycji system. W tym przypadku schemat pliku XML służy jako interfejs między tymi 2 partycjami, dlatego schemat musi być wyraźnie określony i musi ewoluować w sposób zapewniający zgodność wsteczną.
Przed Androidem 10 platforma nie udostępniała mechanizmów wymagających określenia i używania schematu XML ani zapobiegających niezgodnym zmianom w schemacie. Android 10 udostępnia taki mechanizm, który nazywa się Config File Schema API. Ten mechanizm składa się z narzędzia o nazwie xsdc i reguły kompilacji o nazwie xsd_config.
Narzędzie xsdc to kompilator dokumentów schematu XML (XSD). Analizuje plik XSD opisujący schemat pliku XML i generuje kod w językach Java i C++. Wygenerowany kod analizuje pliki XML zgodne ze schematem XSD i tworzy drzewo obiektów, z których każdy modeluje tag XML. Atrybuty XML są modelowane jako pola obiektów.
Reguła kompilacji xsd_config integruje narzędzie xsdc z systemem kompilacji.
W przypadku danego wejściowego pliku XSD reguła kompilacji generuje biblioteki w językach Java i C++. Możesz połączyć biblioteki z modułami, w których odczytywane i używane są pliki XML zgodne ze schematem XSD. Możesz użyć reguły kompilacji w przypadku własnych plików XML używanych w partycjach system i vendor.
Kompilowanie Config File Schema API
W tej sekcji opisujemy, jak skompilować Config File Schema API.
Konfigurowanie reguły kompilacji xsd_config w pliku Android.bp
Reguła kompilacji xsd_config generuje kod analizatora za pomocą narzędzia xsdc. Właściwość package_name reguły kompilacji xsd_config określa nazwę pakietu wygenerowanego kodu w języku Java.
Przykład reguły kompilacji xsd_config w pliku Android.bp:
xsd_config {
name: "hal_manifest",
srcs: ["hal_manifest.xsd"],
package_name: "hal.manifest",
}
Przykładowa struktura katalogu:
├── Android.bp
├── api
│ ├── current.txt
│ ├── last_current.txt
│ ├── last_removed.txt
│ └── removed.txt
└── hal_manifest.xsd
System kompilacji generuje listę interfejsów API na podstawie wygenerowanego kodu w języku Java i sprawdza, czy interfejs API jest z nią zgodny. To sprawdzenie interfejsu API jest dodawane do DroidCore i wykonywane w m -j.
Tworzenie plików list interfejsów API
Sprawdzanie interfejsów API wymaga plików list interfejsów API w kodzie źródłowym.
Pliki list interfejsów API obejmują:
current.txtiremoved.txtsprawdzają, czy interfejsy API zostały zmienione, porównując je z wygenerowanymi plikami interfejsów API w czasie kompilacji.last_current.txtilast_removed.txtsprawdzają, czy interfejsy API są zgodne wstecznie, porównując je z plikami interfejsów API.
Aby utworzyć pliki list interfejsów API:
- Utwórz puste pliki list.
- Uruchom polecenie
make update-api.
Używanie wygenerowanego kodu analizatora
Aby użyć wygenerowanego kodu w języku Java, dodaj : jako prefiks do nazwy modułu xsd_config we właściwości srcs w języku Java. Pakiet wygenerowanego kodu w języku Java jest taki sam jak właściwość package_name.
java_library {
name: "vintf_test_java",
srcs: [
"srcs/**/*.java"
":hal_manifest"
],
}
Aby użyć wygenerowanego kodu w języku C++, dodaj nazwę modułu xsd_config do właściwości generated_sources i generated_headers. Dodaj też libxml2 do static_libs lub shared_libs, ponieważ libxml2 jest wymagana w wygenerowanym kodzie analizatora. Przestrzeń nazw wygenerowanego kodu w języku C++ jest taka sama jak właściwość package_name. Jeśli na przykład nazwa modułu xsd_config to hal.manifest, przestrzeń nazw to hal::manifest.
cc_library{
name: "vintf_test_cpp",
srcs: ["main.cpp"],
generated_sources: ["hal_manifest"],
generated_headers: ["hal_manifest"],
shared_libs: ["libxml2"],
}
Używanie analizatora
Aby użyć kodu analizatora w języku Java, użyj metody XmlParser#read lub
read{class-name}, aby zwrócić klasę elementu głównego. W tym momencie następuje analiza.
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();
…
}
}
…
}
Aby użyć kodu analizatora w języku C++, najpierw dołącz plik nagłówkowy. Nazwa pliku nagłówkowego to nazwa pakietu z kropkami (.) zamienionymi na podkreślenia (_).
Następnie użyj metody read lub read{class-name}, aby zwrócić
klasę elementu głównego. W tym momencie następuje analiza. Wartość zwracana to
a 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();
…
}
…
}
Wszystkie interfejsy API udostępniane do używania analizatora znajdują się w api/current.txt. Dla zachowania spójności wszystkie nazwy elementów i atrybutów są przekształcane na notację camel case (np. ElementName) i używane jako odpowiednia nazwa zmiennej, metody i klasy. Klasę przeanalizowanego elementu głównego można uzyskać za pomocą
read{class-name} funkcji. Jeśli jest tylko 1 element główny, nazwa funkcji to read. Wartość przeanalizowanego podelementu lub
atrybutu można uzyskać za pomocą funkcji get{variable-name}.
Generowanie kodu analizatora
W większości przypadków nie musisz bezpośrednio uruchamiać narzędzia xsdc. Zamiast tego użyj reguły kompilacji xsd_config, jak opisano w sekcji Konfigurowanie reguły kompilacji xsd_config w pliku Android.bp. W tej sekcji opisujemy interfejs wiersza poleceń xsdc tylko dla pełności informacji. Może to być przydatne podczas debugowania.
Musisz podać narzędziu xsdc ścieżkę do pliku XSD i pakiet. Pakiet to nazwa pakietu w kodzie w języku Java i przestrzeń nazw w kodzie w języku C++. Opcje określające, czy wygenerowany kod ma być w języku Java czy C, to odpowiednio -j lub -c. Opcja -o to ścieżka katalogu wyjściowego.
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
Przykładowe polecenie:
$ xsdc audio_policy_configuration.xsd -p audio.policy -j