Versionierung der Benutzeroberfläche

Bei HIDL muss jede in HIDL geschriebene Schnittstelle versioniert sein. Nachdem eine HAL-Benutzeroberfläche veröffentlicht wurde, ist sie eingefroren und alle weiteren Änderungen müssen an einer neuen Version dieser Benutzeroberfläche vorgenommen werden. Eine veröffentlichte Benutzeroberfläche kann nicht geändert, aber durch eine andere erweitert werden.

HIDL-Codestruktur

HIDL-Code ist in benutzerdefinierten Typen, Schnittstellen und Paketen organisiert:

  • Nutzerdefinierte Typen (User-Defined Types, UDTs) HIDL bietet Zugriff auf eine Reihe von primitiven Datentypen, mit denen sich komplexere Typen über Strukturen, Unionen und Enumerationen zusammenstellen lassen. Benutzerdefinierte Typen werden an Methoden von Schnittstellen übergeben und können auf Paketebene (für alle Schnittstellen gemeinsam) oder lokal für eine Schnittstelle definiert werden.
  • Benutzeroberflächen: Als grundlegender Baustein von HIDL besteht eine Schnittstelle aus UDT- und Methodendeklarationen. Schnittstellen können auch von einer anderen Schnittstelle abgeleitet werden.
  • Pakete Hier werden zugehörige HIDL-Schnittstellen und die Datentypen organisiert, mit denen sie arbeiten. Ein Paket wird durch einen Namen und eine Version identifiziert und enthält Folgendes:
    • Datei mit Datentypdefinitionen namens types.hal
    • Null oder mehr Schnittstellen, jeweils in einer eigenen .hal-Datei.

Die Datei mit der Datentypdefinition types.hal enthält nur UDTs. Alle UDTs auf Paketebene werden in einer einzigen Datei aufbewahrt. Darstellungen in der Zielsprache sind für alle Schnittstellen im Paket verfügbar.

Versionierungsphilosophie

Ein HIDL-Paket (z. B. android.hardware.nfc), das für eine bestimmte Version (z. B. 1.0) veröffentlicht wurde, ist unveränderlich und kann nicht geändert werden. Änderungen an den Schnittstellen im Paket oder an den UDTs können nur in einem anderen Paket vorgenommen werden.

In HIDL gilt die Versionierung auf Paketebene und nicht auf Schnittstellenebene. Alle Schnittstellen und UDTs in einem Paket haben dieselbe Version. Paketversionen folgen dem semantischen Versionierungsschema ohne die Komponenten „Patchebene“ und „Build-Metadaten“. Innerhalb eines bestimmten Pakets bedeutet eine Erhöhung der Nebenversion, dass die neue Version des Pakets abwärtskompatibel mit der alten Version ist. Eine Erhöhung der Hauptversion bedeutet, dass die neue Version des Pakets nicht abwärtskompatibel mit der alten Version ist.

Ein Paket kann auf verschiedene Arten mit einem anderen Paket in Beziehung stehen:

  • Überhaupt nicht.
  • Abwärtskompatible Erweiterbarkeit auf Paketebene Das tritt bei neuen Nebenversionen (nächste inkrementelle Überarbeitung) eines Pakets auf. Das neue Paket hat denselben Namen und dieselbe Hauptversion wie das alte Paket, aber eine höhere Nebenversion. Funktionell ist das neue Paket eine Obermenge des alten Pakets. Das bedeutet:
    • Die Schnittstellen der obersten Ebene des übergeordneten Pakets sind im neuen Paket vorhanden. Die Schnittstellen können jedoch neue Methoden, neue schnittstellenlokale UDTs (die unten beschriebene Erweiterung auf Schnittstellenebene) und neue UDTs in types.hal enthalten.
    • Dem neuen Paket können auch neue Oberflächen hinzugefügt werden.
    • Alle Datentypen des übergeordneten Pakets sind im neuen Paket vorhanden und können von den (möglicherweise neu implementierten) Methoden aus dem alten Paket verarbeitet werden.
    • Neue Datentypen können auch für die Verwendung durch neue Methoden aktualisierter vorhandener oder neuer Schnittstellen hinzugefügt werden.
  • Abwärtskompatible Erweiterbarkeit auf Benutzeroberflächenebene Das neue Paket kann auch das ursprüngliche Paket erweitern, indem es aus logisch getrennten Schnittstellen besteht, die lediglich zusätzliche Funktionen und nicht die Hauptfunktion bieten. Dazu kann Folgendes sinnvoll sein:
    • Schnittstellen im neuen Paket müssen auf die Datentypen des alten Pakets zurückgreifen.
    • Schnittstellen im neuen Paket können Schnittstellen eines oder mehrerer alter Pakete erweitern.
  • Erweitern Sie die ursprüngliche Abwärtsinkompatibilität. Dies ist eine Major-Version des Pakets und es muss keine Korrelation zwischen den beiden geben. Falls ja, kann sie mit einer Kombination von Typen aus der älteren Version des Pakets und der Vererbung einer Teilmenge der Schnittstellen des alten Pakets ausgedrückt werden.

Benutzeroberflächenstruktur

Bei einer gut strukturierten Benutzeroberfläche sollte das Hinzufügen neuer Funktionen, die nicht Teil des ursprünglichen Designs sind, eine Änderung der HIDL-Benutzeroberfläche erfordern. Umgekehrt ist die Benutzeroberfläche nicht strukturiert, wenn Sie auf beiden Seiten der Benutzeroberfläche eine Änderung vornehmen können oder erwarten, dass neue Funktionen eingeführt werden, ohne dass sich die Benutzeroberfläche selbst ändert.

Treble unterstützt separat kompilierte Anbieter- und Systemkomponenten, bei denen die vendor.img auf einem Gerät und die system.img separat kompiliert werden können. Alle Interaktionen zwischen vendor.img und system.img müssen explizit und umfassend definiert werden, damit sie noch viele Jahre lang funktionieren. Dazu gehören viele API-Oberflächen, aber ein wichtiger Aspekt ist der IPC-Mechanismus, den HIDL für die prozessübergreifende Kommunikation an der system.img/vendor.img-Grenze verwendet.

Voraussetzungen

Alle über HIDL übergebenen Daten müssen explizit definiert sein. Damit eine Implementierung und ein Client auch dann zusammenarbeiten können, wenn sie separat kompiliert oder unabhängig voneinander entwickelt wurden, müssen die Daten die folgenden Anforderungen erfüllen:

  • Kann direkt in HIDL mithilfe von Strukturen, Enumerationen usw. mit semantischen Namen und Bedeutung beschrieben werden.
  • Kann durch einen öffentlichen Standard wie ISO/IEC 7816 beschrieben werden.
  • Kann durch einen Hardwarestandard oder das physische Layout der Hardware beschrieben werden.
  • Kann bei Bedarf undurchsichtige Daten (z. B. öffentliche Schlüssel, IDs usw.) enthalten.

Wenn opake Daten verwendet werden, dürfen sie nur von einer Seite der HIDL-Schnittstelle gelesen werden. Wenn beispielsweise vendor.img-Code einer Komponente auf der system.img eine Stringnachricht oder vec<uint8_t>-Daten sendet, können diese Daten nicht von der system.img selbst geparst werden, sondern nur zur Interpretation an vendor.img zurückgegeben werden. Wenn ein Wert von vendor.img an den Anbietercode auf system.img oder an ein anderes Gerät übergeben wird, müssen das Format der Daten und die Art ihrer Interpretation genau beschrieben werden. Diese Informationen sind weiterhin Teil der Benutzeroberfläche.

Richtlinien

Sie sollten eine Implementierung oder einen Client einer HAL nur mit den .hal-Dateien schreiben können. Sie müssen sich also nicht die Android-Quellcodes oder öffentlichen Standards ansehen. Wir empfehlen, das genaue erforderliche Verhalten anzugeben. Aussagen wie „Eine Implementierung könnte A oder B tun“ fördern die Verzahnung von Implementierungen mit den Kunden, für die sie entwickelt werden.

HIDL-Codelayout

HIDL umfasst Kern- und Anbieterpakete.

Die von Google festgelegten HIDL-Schnittstellen sind die wichtigsten. Die zugehörigen Pakete beginnen mit android.hardware. und werden nach Subsystem benannt, möglicherweise mit verschachtelten Benennungsebenen. Angenommen, das NFC-Paket heißt android.hardware.nfc und das Kamerapaket android.hardware.camera. Im Allgemeinen hat ein Kernpaket den Namen android.hardware.[name1].[name2]…. HIDL-Pakete haben zusätzlich zu ihrem Namen eine Version. Das Paket android.hardware.camera hat beispielsweise die Version 3.4. Das ist wichtig, da sich die Version eines Pakets auf seine Platzierung im Stammbaum auswirkt.

Alle Kernpakete werden im Build-System unter hardware/interfaces/ abgelegt. Das Paket android.hardware.[name1].[name2]… mit der Version $m.$n befindet sich unter hardware/interfaces/name1/name2//$m.$n/; das Paket android.hardware.camera Version 3.4 befindet sich im Verzeichnis hardware/interfaces/camera/3.4/.. Es gibt eine hartcodierte Zuordnung zwischen dem Paketpräfix android.hardware. und dem Pfad hardware/interfaces/.

Nicht zum Kern gehörende (Anbieter-)Pakete werden vom SoC-Anbieter oder ODM erstellt. Das Präfix für nicht ‑Kernpakete ist vendor.$(VENDOR).hardware., wobei $(VENDOR) sich auf einen SoC-Anbieter oder OEM/ODM bezieht. Dies entspricht dem Pfad vendor/$(VENDOR)/interfaces im Baum (diese Zuordnung ist ebenfalls hartcodiert).

Vollständig qualifizierte Namen von benutzerdefinierten Typen

In HIDL hat jede UDT einen voll qualifizierten Namen, der aus dem Namen der UDT, dem Paketnamen, in dem die UDT definiert ist, und der Paketversion besteht. Der voll qualifizierte Name wird nur verwendet, wenn Instanzen des Typs deklariert werden, nicht aber, wenn der Typ selbst definiert wird. Angenommen, das Paket android.hardware.nfc, Version 1.0 definiert eine Struktur namens NfcData. An der Stelle der Erklärung (sei es in types.hal oder in der Erklärung einer Benutzeroberfläche) wird einfach Folgendes angegeben:

struct NfcData {
    vec<uint8_t> data;
};

Verwenden Sie bei der Deklarierung einer Instanz dieses Typs (sei es innerhalb einer Datenstruktur oder als Methodenparameter) den voll qualifizierten Typnamen:

android.hardware.nfc@1.0::NfcData

Die allgemeine Syntax lautet PACKAGE@VERSION::UDT, wobei:

  • PACKAGE ist der durch Punkte getrennte Name eines HIDL-Pakets (z.B. android.hardware.nfc).
  • VERSION ist das durch Punkte getrennte Format „major.minor“ der Version des Pakets (z.B. 1.0).
  • UDT ist der durch Punkte getrennte Name einer HIDL-UDT. Da HIDL verschachtelte UDTs unterstützt und HIDL-Schnittstellen UDTs (eine Art verschachtelte Deklaration) enthalten können, werden Punkte für den Zugriff auf die Namen verwendet.

Angenommen, die folgende verschachtelte Deklaration wurde in der Datei „common types“ im Paket android.hardware.example Version 1.0 definiert:

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

Der vollständig qualifizierte Name für Bar ist android.hardware.example@1.0::Foo.Bar. Wenn sich die verschachtelte Deklaration nicht nur im obigen Paket, sondern auch in einer Schnittstelle namens IQuux befindet:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

Der vollständig qualifizierte Name für Bar ist android.hardware.example@1.0::IQuux.Foo.Bar.

In beiden Fällen kann auf Bar nur im Rahmen der Deklaration von Foo als Bar verwiesen werden. Auf Paket- oder Schnittstellenebene müssen Sie auf Bar über Foo:Foo.Bar verweisen, wie in der Deklaration der Methode doSomething oben. Alternativ können Sie die Methode ausführlicher deklarieren:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

Voll qualifizierte Aufzählungswerte

Wenn es sich bei einem UDT um einen enum-Typ handelt, hat jeder Wert des enum-Typs einen voll qualifizierten Namen, der mit dem voll qualifizierten Namen des enum-Typs beginnt, gefolgt von einem Doppelpunkt und dem Namen des enum-Werts. Angenommen, das Paket android.hardware.nfc, Version 1.0 definiert einen Enum-Typ NfcStatus:

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

Der vollständig qualifizierte Name für STATUS_OK lautet:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

Die allgemeine Syntax lautet PACKAGE@VERSION::UDT:VALUE, wobei:

  • PACKAGE@VERSION::UDT ist der exakteselbe voll qualifizierte Name für den Enumerationstyp.
  • VALUE ist der Name des Werts.

Regeln für die automatische Inferenz

Ein voll qualifizierter UDT-Name muss nicht angegeben werden. Folgendes kann in einem UDT-Namen problemlos weggelassen werden:

  • Das Paket, z.B. @1.0::IFoo.Type
  • Sowohl Paket als auch Version, z.B. IFoo.Type

HIDL versucht, den Namen mithilfe von Regeln zur automatischen Behebung von Interferenzen zu vervollständigen. Je niedriger die Regelnummer, desto höher die Priorität.

Regel 1

Wenn kein Paket und keine Version angegeben werden, wird eine lokale Namenssuche versucht. Beispiel:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

NfcErrorMessage wird lokal abgerufen und die darüber liegende typedef wird gefunden. NfcData wird auch lokal abgerufen, da es jedoch nicht lokal definiert ist, werden Regel 2 und 3 verwendet. @1.0::NfcStatus gibt eine Version an, daher gilt Regel 1 nicht.

Regel 2

Wenn Regel 1 fehlschlägt und eine Komponente des voll qualifizierten Namens fehlt (Paket, Version oder Paket und Version), wird die Komponente automatisch mit Informationen aus dem aktuellen Paket ausgefüllt. Der HIDL-Compiler sucht dann in der aktuellen Datei (und in allen Importen) nach dem automatisch ausgefüllten voll qualifizierten Namen. Angenommen, die Deklaration von ExtendedNfcData wurde im selben Paket (android.hardware.nfc) in derselben Version (1.0) wie NfcData vorgenommen:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

Der HIDL-Compiler füllt den Paketnamen und den Versionsnamen aus dem aktuellen Paket aus, um den vollständig qualifizierten UDT-Namen android.hardware.nfc@1.0::NfcData zu erstellen. Da der Name im aktuellen Paket vorhanden ist (vorausgesetzt, er wurde richtig importiert), wird er für die Deklaration verwendet.

Ein Name im aktuellen Paket wird nur importiert, wenn eines der folgenden Kriterien erfüllt ist:

  • Es wird explizit mit einer import-Anweisung importiert.
  • Sie wird im aktuellen Paket in types.hal definiert.

Das gleiche Verfahren wird angewendet, wenn NfcData nur durch die Versionsnummer qualifiziert wurde:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

Regel 3

Wenn Regel 2 nicht zu einer Übereinstimmung führt (die UDT ist nicht im aktuellen Paket definiert), sucht der HIDL-Compiler in allen importierten Paketen nach einer Übereinstimmung. Angenommen, ExtendedNfcData ist in Version 1.1 des Pakets android.hardware.nfc deklariert, 1.1 importiert 1.0 wie vorgesehen (siehe Erweiterungen auf Paketebene) und die Definition gibt nur den Namen der UDT an:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

Der Compiler sucht nach einer UDT mit dem Namen NfcData und findet eine in android.hardware.nfc in Version 1.0. Dies führt zu einer vollständig qualifizierten UDT von android.hardware.nfc@1.0::NfcData. Wenn für eine bestimmte teilweise qualifizierte UDT mehr als eine Übereinstimmung gefunden wird, löst der HIDL-Compiler einen Fehler aus.

Verwendungsbeispiele

Gemäß Regel 2 wird ein im aktuellen Paket definierter importierter Typ einem importierten Typ aus einem anderen Paket vorgezogen:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S wird als android.hardware.bar@1.0::S interpoliert und in bar/1.0/types.hal gefunden, da types.hal automatisch importiert wird.
  • IFooCallback wird gemäß Regel 2 als android.hardware.bar@1.0::IFooCallback interpoliert, kann aber nicht gefunden werden, da bar/1.0/IFooCallback.hal nicht automatisch importiert wird (wie types.hal). Daher wird es in Regel 3 stattdessen in android.hardware.foo@1.0::IFooCallback aufgelöst, das über import android.hardware.foo@1.0; importiert wird.

types.hal

Jedes HIDL-Paket enthält eine types.hal-Datei mit UDTs, die für alle an diesem Paket beteiligten Schnittstellen freigegeben werden. HIDL-Typen sind immer öffentlich. Unabhängig davon, ob eine UDT in types.hal oder in einer Schnittstellendeklaration deklariert ist, sind diese Typen außerhalb des Gültigkeitsbereichs, in dem sie definiert sind, zugänglich. types.hal dient nicht dazu, die öffentliche API eines Pakets zu beschreiben, sondern UDTs zu hosten, die von allen Schnittstellen innerhalb des Pakets verwendet werden. Aufgrund der Natur von HIDL sind alle UDTs Teil der Schnittstelle.

types.hal besteht aus UDTs und import-Anweisungen. Da types.hal für jede Schnittstelle des Pakets verfügbar ist (es ist ein impliziter Import), sind diese import-Anweisungen per Definition auf Paketebene. Benutzerdefinierte Typen in types.hal können auch so importierte benutzerdefinierte Typen und Schnittstellen enthalten.

Beispiel für eine IFoo.hal:

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

Folgendes wird importiert:

  • android.hidl.base@1.0::IBase (implizit)
  • android.hardware.foo@1.0::types (implizit)
  • Alles in android.hardware.bar@1.0 (einschließlich aller Benutzeroberflächen und types.hal)
  • types.hal aus android.hardware.baz@1.0::types (Benutzeroberflächen in android.hardware.baz@1.0 werden nicht importiert)
  • IQux.hal und types.hal von android.hardware.qux@1.0
  • Quuz aus android.hardware.quuz@1.0 (vorausgesetzt, Quuz ist in types.hal definiert, wird die gesamte types.hal-Datei geparst, aber andere Typen als Quuz werden nicht importiert).

Versionierung auf Benutzeroberflächenebene

Jede Benutzeroberfläche in einem Paket befindet sich in einer eigenen Datei. Das Paket, zu dem die Benutzeroberfläche gehört, wird oben in der Benutzeroberfläche mit der Anweisung package deklariert. Nach der Paketdeklaration können null oder mehrere Importe auf Schnittstellenebene (Teil- oder Vollpaket) aufgeführt sein. Beispiel:

package android.hardware.nfc@1.0;

In HIDL können Schnittstellen mit dem Schlüsselwort extends von anderen Schnittstellen abgeleitet werden. Damit eine Schnittstelle eine andere erweitern kann, muss sie über eine import-Anweisung darauf zugreifen können. Der Name der erweiterten Schnittstelle (Basisschnittstelle) folgt den oben beschriebenen Regeln für die Qualifizierung von Typennamen. Eine Schnittstelle kann nur von einer anderen Schnittstelle abgeleitet werden. In HIDL wird keine Mehrfachvererbung unterstützt.

In den folgenden Beispielen für die uprev-Versionierung wird das folgende Paket verwendet:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

Uprev-Regeln

Um ein Paket package@major.minor zu definieren, muss entweder A oder B erfüllt sein:

Regel A „Ist eine Start-Nebenversion“: Alle vorherigen Nebenversionen, package@major.0, package@major.1, …, package@major.(minor-1) dürfen nicht definiert sein.
OR
Regel B

Folgendes ist richtig:

  1. „Vorherige Minor-Version ist gültig“: package@major.(minor-1) muss definiert sein und der Regel A folgen (keine der Versionen package@major.0 bis package@major.(minor-2) ist definiert) oder Regel B (wenn es sich um eine Uprev von @major.(minor-2) handelt);

    UND

  2. „Mindestens eine Schnittstelle mit demselben Namen erben“: Es gibt eine Schnittstelle package@major.minor::IFoo, die package@major.(minor-1)::IFoo erweitert (wenn das vorherige Paket eine Schnittstelle hat);

    UND

  3. „Keine abgeleitete Benutzeroberfläche mit einem anderen Namen“: Es darf keine package@major.minor::IBar geben, die package@major.(minor-1)::IBaz erweitert, wobei IBar und IBaz zwei verschiedene Namen sind. Wenn es eine Benutzeroberfläche mit demselben Namen gibt, muss package@major.minor::IBar package@major.(minor-k)::IBar so erweitern, dass keine IBar mit einem kleineren k vorhanden ist.

Aufgrund von Regel A:

  • Das Paket kann mit einer beliebigen Nebenversionsnummer beginnen (z. B. beginnt android.hardware.biometrics.fingerprint bei @2.1).
  • Die Anforderung „android.hardware.foo@1.0 ist nicht definiert“ bedeutet, dass das Verzeichnis hardware/interfaces/foo/1.0 nicht vorhanden sein darf.

Regel A wirkt sich jedoch nicht auf ein Paket mit demselben Paketnamen, aber einer anderen Hauptversion aus. Beispiel: Für android.hardware.camera.device sind sowohl @1.0 als auch @3.2 definiert. @3.2 muss nicht mit @1.0 interagieren. Daher kann @3.2::IExtFoo @1.0::IFoo erweitern.

Vorausgesetzt, der Paketname ist unterschiedlich, kann package@major.minor::IBar von einer Schnittstelle mit einem anderen Namen erweitert werden (z. B. kann android.hardware.bar@1.0::IBar android.hardware.baz@2.2::IBaz erweitern). Wenn in einer Schnittstelle kein übergeordneter Typ mit dem Keyword extend explizit deklariert wird, wird android.hidl.base@1.0::IBase erweitert (außer IBase selbst).

B.2 und B.3 müssen gleichzeitig eingehalten werden. Auch wenn android.hardware.foo@1.1::IFoo android.hardware.foo@1.0::IFoo erweitert, um Regel B.2 zu erfüllen, ist android.hardware.foo@1.1::IExtBar, das android.hardware.foo@1.0::IBar erweitert, keine gültige Uprev.

Uprev-Schnittstellen

So führen Sie ein Uprev für android.hardware.example@1.0 (oben definiert) auf @1.1 aus:

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

Dies ist eine import auf Paketebene der Version 1.0 von android.hardware.example in types.hal. In Version 1.1 des Pakets werden zwar keine neuen UDTs hinzugefügt, aber Verweise auf UDTs in Version 1.0 sind weiterhin erforderlich. Daher ist in types.hal ein Import auf Paketebene erforderlich. Derselbe Effekt hätte auch mit einem Import auf Benutzeroberflächenebene in IQuux.hal erreicht werden können.

In extends @1.0::IQuux in der Deklaration von IQuux haben wir die Version von IQuux angegeben, die übernommen wird. Eine Unterscheidung ist erforderlich, da IQuux verwendet wird, um eine Schnittstelle zu deklarieren und von einer Schnittstelle zu übernehmen. Da Deklarationen einfach Namen sind, die alle Paket- und Versionsattribute an der Stelle der Deklaration übernehmen, muss die Entfernung von Mehrdeutigkeiten im Namen der Basisschnittstelle erfolgen. Wir hätten auch die vollständig qualifizierte UDT verwenden können, was aber redundant gewesen wäre.

Die neue Schnittstelle IQuux deklariert die Methode fromFooToBar(), die sie von @1.0::IQuux erbt, nicht noch einmal. Sie listet einfach die neue Methode auf, die sie hinzufügt: fromBarToFoo(). In HIDL können vererbte Methoden nicht in den untergeordneten Schnittstellen noch einmal deklariert werden. Daher kann die fromFooToBar()-Methode in der IQuux-Schnittstelle nicht explizit deklariert werden.

Uprev-Konventionen

Manchmal müssen die Schnittstellennamen der erweiterten Schnittstelle umbenannt werden. Wir empfehlen, dass Enumerationserweiterungen, Strukturen und Unionen denselben Namen wie das Element haben, das sie erweitern, es sei denn, sie unterscheiden sich so stark, dass ein neuer Name erforderlich ist. Beispiele:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

Wenn eine Methode einen neuen semantischen Namen haben kann (z. B. fooWithLocation), ist das vorzuziehen. Andernfalls sollte der Name ähnlich wie der Name des erweiterten Elements lauten. Beispielsweise kann die Methode foo_1_1 in @1.1::IFoo die Funktionalität der Methode foo in @1.0::IFoo ersetzen, wenn es keinen besseren alternativen Namen gibt.

Versionierung auf Paketebene

Die HIDL-Versionierung erfolgt auf Paketebene. Nach der Veröffentlichung eines Pakets ist es unveränderlich (die Schnittstellen und UDTs können nicht geändert werden). Pakete können auf verschiedene Arten miteinander in Beziehung stehen, die sich alle durch eine Kombination aus Vererbung auf Schnittstellenebene und Zusammensetzung von UDTs durch Komposition ausdrücken lassen.

Eine Art von Beziehung ist jedoch genau definiert und muss erzwungen werden: die rückwärtskompatible Vererbung auf Paketebene. In diesem Szenario ist das übergeordnete Paket das Paket, von dem die Elemente übernommen werden, und das untergeordnete Paket ist das Paket, das das übergeordnete Paket erweitert. Für die abwärtskompatiblen Vererbungsregeln auf Paketebene gelten folgende Regeln:

  1. Alle Oberflächen der obersten Ebene des übergeordneten Pakets werden von Oberflächen im untergeordneten Paket übernommen.
  2. Dem neuen Paket können auch neue Schnittstellen hinzugefügt werden (keine Einschränkungen hinsichtlich der Beziehungen zu anderen Schnittstellen in anderen Paketen).
  3. Neue Datentypen können auch für die Verwendung durch neue Methoden aktualisierter vorhandener oder neuer Schnittstellen hinzugefügt werden.

Diese Regeln können mithilfe der HIDL-Erbschaft auf Schnittstellenebene und der UDT-Zusammensetzung implementiert werden. Sie erfordern jedoch Metadaten, um zu wissen, dass diese Beziehungen eine abwärtskompatible Paketerweiterung darstellen. So wird dieses Wissen abgeleitet:

Wenn ein Paket diese Anforderung erfüllt, erzwingt hidl-gen Regeln zur Abwärtskompatibilität.