Versionsverwaltung

HIDL erfordert, dass jede in HIDL geschriebene Schnittstelle versioniert wird. Nachdem eine HAL-Schnittstelle veröffentlicht wurde, wird sie eingefroren und alle weiteren Änderungen müssen an einer neuen Version dieser Schnittstelle vorgenommen werden. Während eine bestimmte veröffentlichte Schnittstelle nicht geändert werden darf, kann sie um eine andere Schnittstelle erweitert werden.

HIDL-Codestruktur

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

  • Benutzerdefinierte Typen (UDTs) . HIDL bietet Zugriff auf eine Reihe primitiver Datentypen, die zum Zusammenstellen komplexerer Typen über Strukturen, Unions und Aufzählungen verwendet werden können. UDTs werden an Methoden von Schnittstellen übergeben und können auf der Ebene eines Pakets (gemeinsam für alle Schnittstellen) oder lokal für eine Schnittstelle definiert werden.
  • Schnittstellen . Als Grundbaustein von HIDL besteht eine Schnittstelle aus UDT- und Methodendeklarationen. Schnittstellen können auch von einer anderen Schnittstelle erben.
  • Pakete . Organisiert verwandte HIDL-Schnittstellen und die Datentypen, mit denen sie arbeiten. Ein Paket wird durch einen Namen und eine Version identifiziert und enthält Folgendes:
    • Datentyp-Definitionsdatei mit dem Namen types.hal .
    • Keine oder mehr Schnittstellen, jede in ihrer eigenen .hal Datei.

Die Datentypdefinitionsdatei types.hal enthält nur UDTs (alle UDTs auf Paketebene werden in einer einzigen Datei gespeichert). Darstellungen in der Zielsprache stehen allen Schnittstellen im Paket zur Verfügung.

Versionierungsphilosophie

Ein HIDL-Paket (z. B. android.hardware.nfc ) ist nach der Veröffentlichung für eine bestimmte Version (z. 1.0 ) unveränderlich. es kann nicht geändert werden. Änderungen an den Schnittstellen im Paket oder Änderungen an seinen UDTs können nur in einem anderen Paket erfolgen.

In HIDL gilt die Versionierung auf Paketebene, nicht auf Schnittstellenebene, und alle Schnittstellen und UDTs in einem Paket haben dieselbe Version. Paketversionen folgen der semantischen Versionierung ohne Patch-Level und Build-Metadaten-Komponenten. Innerhalb eines bestimmten Pakets bedeutet eine geringfügige Versionsänderung , dass die neue Version des Pakets mit dem alten Paket abwärtskompatibel ist, und eine größere Versionsänderung bedeutet, dass die neue Version des Pakets nicht mit dem alten Paket abwärtskompatibel ist.

Konzeptionell kann sich ein Paket auf verschiedene Arten auf ein anderes Paket beziehen:

  • Gar nicht .
  • Abwärtskompatible Erweiterbarkeit auf Paketebene . Dies tritt bei neuen Nebenversions-Uprevs (nächste inkrementierte Revision) eines Pakets auf; Das neue Paket hat denselben Namen und dieselbe Hauptversion wie das alte Paket, jedoch eine höhere Nebenversion. Funktionell ist das neue Paket eine Obermenge des alten Pakets, das heißt:
    • Schnittstellen der obersten Ebene des übergeordneten Pakets sind im neuen Paket vorhanden, obwohl die Schnittstellen möglicherweise über neue Methoden, neue schnittstellenlokale UDTs (die unten beschriebene Erweiterung auf Schnittstellenebene) und neue UDTs in types.hal verfügen.
    • Dem neuen Paket können auch neue Schnittstellen hinzugefügt werden.
    • Alle Datentypen des übergeordneten Pakets sind im neuen Paket vorhanden und können von den (ggf. neu implementierten) Methoden aus dem alten Paket verarbeitet werden.
    • Es können auch neue Datentypen hinzugefügt werden, die entweder von neuen Methoden aktualisierter vorhandener Schnittstellen oder von neuen Schnittstellen verwendet werden.
  • Abwärtskompatible Erweiterbarkeit auf Schnittstellenebene . Das neue Paket kann das ursprüngliche Paket auch erweitern, indem es aus logisch getrennten Schnittstellen besteht, die lediglich zusätzliche Funktionalität bereitstellen und nicht die Kernfunktionalität. Zu diesem Zweck kann Folgendes wünschenswert sein:
    • Schnittstellen im neuen Paket müssen auf die Datentypen des alten Pakets zurückgreifen.
    • Schnittstellen in neuen Paketen können Schnittstellen eines oder mehrerer alter Pakete erweitern.
  • Erweitern Sie die ursprüngliche Abwärtsinkompatibilität . Dies ist eine Hauptversion des Pakets und es muss keine Korrelation zwischen den beiden bestehen. Soweit vorhanden, kann es mit einer Kombination von Typen aus der älteren Version des Pakets und der Vererbung einer Teilmenge der Schnittstellen des alten Pakets ausgedrückt werden.

Schnittstellen strukturieren

Für eine gut strukturierte Schnittstelle sollte das Hinzufügen neuer Arten von Funktionen, die nicht Teil des ursprünglichen Designs sind, eine Änderung der HIDL-Schnittstelle erfordern. Wenn Sie umgekehrt eine Änderung auf beiden Seiten der Schnittstelle vornehmen können oder erwarten, die neue Funktionen einführt, ohne die Schnittstelle selbst zu ändern, dann ist die Schnittstelle nicht strukturiert.

Treble unterstützt separat kompilierte Hersteller- 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 gründlich definiert werden, damit sie über viele Jahre hinweg funktionieren können. Dazu gehören viele API-Oberflächen, aber eine wichtige Oberfläche ist der IPC-Mechanismus, den HIDL für die Interprozesskommunikation an der Grenze system.img / vendor.img verwendet.

Anforderungen

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

  • Kann in HIDL direkt (mithilfe von Strukturaufzählungen usw.) mit semantischen Namen und Bedeutung beschrieben werden.
  • Kann durch einen öffentlichen Standard wie ISO/IEC 7816 beschrieben werden.
  • Kann durch einen Hardwarestandard oder ein physisches Hardware-Layout beschrieben werden.
  • Bei Bedarf können undurchsichtige Daten (z. B. öffentliche Schlüssel, IDs usw.) vorliegen.

Wenn undurchsichtige Daten verwendet werden, dürfen diese nur von einer Seite der HIDL-Schnittstelle gelesen werden. Wenn beispielsweise vendor.img Code einer Komponente in system.img eine Zeichenfolgennachricht oder vec<uint8_t> -Daten übergibt, können diese Daten nicht von system.img selbst analysiert werden. Es kann nur zur Interpretation an vendor.img zurückgegeben werden. Bei der Übergabe eines Werts von vendor.img an Vendor-Code auf system.img oder an ein anderes Gerät muss das Format der Daten und deren Interpretation genau beschrieben werden und ist weiterhin Teil der Schnittstelle .

Richtlinien

Sie sollten in der Lage sein, eine Implementierung oder einen Client einer HAL nur mit den .hal-Dateien zu schreiben (dh Sie sollten sich nicht die Android-Quelle oder öffentliche Standards ansehen müssen). Wir empfehlen, das gewünschte Verhalten genau anzugeben. Aussagen wie „Eine Implementierung kann A oder B tun“ fördern die Verflechtung von Implementierungen mit den Kunden, mit denen sie entwickelt werden.

HIDL-Code-Layout

HIDL umfasst Kern- und Anbieterpakete.

Die wichtigsten HIDL-Schnittstellen sind die von Google spezifizierten. Die Pakete, zu denen sie gehören, beginnen mit android.hardware. und werden nach Subsystem benannt, möglicherweise mit verschachtelten Benennungsebenen. Das NFC-Paket heißt beispielsweise android.hardware.nfc und das Kamerapaket heißt android.hardware.camera . Im Allgemeinen hat ein Kernpaket den Namen android.hardware. [ name1 ].[ name2 ]…. HIDL-Pakete haben zusätzlich zu ihrem Namen eine Version. Beispielsweise kann das Paket android.hardware.camera die Version 3.4 haben; Dies ist wichtig, da die Version eines Pakets seine Platzierung im Quellbaum beeinflusst.

Alle Kernpakete werden im Build-System unter hardware/interfaces/ abgelegt. Das Paket android.hardware. [ name1 ].[ name2 ]… bei 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/. Zwischen dem Paketpräfix android.hardware. und der Pfad hardware/interfaces/ .

Nicht zum Kern gehörende Pakete (Anbieterpakete) sind solche, die vom SoC-Anbieter oder ODM erstellt werden. Das Präfix für Nicht-Kernpakete ist vendor.$(VENDOR).hardware. Dabei bezieht sich $(VENDOR) auf einen SoC-Anbieter oder OEM/ODM. Dies wird dem Pfad vendor/$(VENDOR)/interfaces im Baum zugeordnet (diese Zuordnung ist ebenfalls fest codiert).

Vollständig qualifizierte benutzerdefinierte Typnamen

In HIDL hat jeder UDT einen vollständig qualifizierten Namen, der aus dem UDT-Namen, dem Paketnamen, in dem der UDT definiert ist, und der Paketversion besteht. Der vollständig qualifizierte Name wird nur verwendet, wenn Instanzen des Typs deklariert werden und nicht dort, wo der Typ selbst definiert ist. Angenommen, das Paket android.hardware.nfc, Version 1.0 definiert eine Struktur namens NfcData . An der Stelle der Deklaration (sei es in der types.hal oder in der Deklaration einer Schnittstelle) heißt es in der Deklaration einfach:

struct NfcData {
    vec<uint8_t> data;
};

Verwenden Sie beim Deklarieren einer Instanz dieses Typs (sei es innerhalb einer Datenstruktur oder als Methodenparameter) den vollständig qualifizierten Typnamen:

android.hardware.nfc@1.0::NfcData

Die allgemeine Syntax ist 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 Haupt-.Nebenversionsformat des Pakets (z. 1.0 ).
  • UDT ist der durch Punkte getrennte Name eines HIDL-UDT. Da HIDL verschachtelte UDTs unterstützt und HIDL-Schnittstellen UDTs (eine Art verschachtelte Deklaration) enthalten können, werden Punkte verwendet, um auf die Namen zuzugreifen.

Wenn beispielsweise die folgende verschachtelte Deklaration in der Common-Types-Datei im Paket android.hardware.example Version 1.0 definiert wurde:

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

Der vollständig qualifizierte Name für Bar lautet android.hardware.example@1.0::Foo.Bar . Wenn sich die verschachtelte Deklaration nicht nur im obigen Paket befindet, sondern sich 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 Bar nur im Rahmen der Erklärung von Foo als Bar bezeichnet werden. Auf Paket- oder Schnittstellenebene müssen Sie über Foo : Foo.Bar auf Bar verweisen, wie in der Deklaration der Methode doSomething oben. Alternativ können Sie die Methode ausführlicher deklarieren als:

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

Vollständig qualifizierte Aufzählungswerte

Wenn es sich bei einem UDT um einen Aufzählungstyp handelt, verfügt jeder Wert des Aufzählungstyps über einen vollständig qualifizierten Namen, der mit dem vollständig qualifizierten Namen des Aufzählungstyps beginnt, gefolgt von einem Doppelpunkt und dem Namen des Aufzählungswerts. Angenommen, das Paket android.hardware.nfc, Version 1.0 definiert einen Aufzählungstyp NfcStatus :

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

Beim Verweis auf STATUS_OK lautet der vollständig qualifizierte Name:

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

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

  • PACKAGE @ VERSION :: UDT ist genau derselbe vollqualifizierte Name für den Enum-Typ.
  • VALUE ist der Name des Werts.

Autoinferenzregeln

Es muss kein vollständig qualifizierter UDT-Name angegeben werden. In einem UDT-Namen kann Folgendes getrost weggelassen werden:

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

HIDL versucht, den Namen mithilfe von Autointerferenzregeln zu vervollständigen (niedrigere Regelnummer bedeutet höhere Priorität).

Regel 1

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

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

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

Regel 2

Wenn Regel 1 fehlschlägt und eine Komponente des vollständig qualifizierten Namens fehlt (Paket, Version oder Paket und Version), wird die Komponente automatisch mit Informationen aus dem aktuellen Paket gefüllt. Der HIDL-Compiler durchsucht dann die aktuelle Datei (und alle Importe), um den automatisch ausgefüllten, vollständig qualifizierten Namen zu finden. Gehen Sie anhand des obigen Beispiels davon aus, dass die Deklaration von ExtendedNfcData im selben Paket ( android.hardware.nfc ) in derselben Version ( 1.0 ) wie NfcData wie folgt erfolgt ist:

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, es wurde ordnungsgemäß importiert), wird er für die Deklaration verwendet.

Ein Name im aktuellen Paket wird nur importiert, wenn eine der folgenden Bedingungen zutrifft:

  • Der Import erfolgt explizit mit einer import .
  • Es ist in der Datei types.hal im aktuellen Paket definiert

Der gleiche Prozess wird befolgt, 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 keine Übereinstimmung ergibt (der UDT ist im aktuellen Paket nicht definiert), sucht der HIDL-Compiler nach einer Übereinstimmung in allen importierten Paketen. Nehmen wir anhand des obigen Beispiels an, dass ExtendedNfcData in Version 1.1 des Pakets android.hardware.nfc deklariert ist, 1.1 1.0 wie vorgesehen importiert (siehe Erweiterungen auf Paketebene ) und die Definition nur den UDT-Namen angibt:

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

Der Compiler sucht nach einem beliebigen UDT mit dem Namen NfcData und findet einen in android.hardware.nfc in der Version 1.0 , was zu einem vollständig qualifizierten UDT von android.hardware.nfc@1.0::NfcData führt. Wenn mehr als eine Übereinstimmung für einen bestimmten teilweise qualifizierten UDT gefunden wird, gibt der HIDL-Compiler einen Fehler aus.

Beispiel

Mit Regel 2 wird ein importierter Typ, der im aktuellen Paket definiert ist, gegenüber einem importierten Typ aus einem anderen Paket bevorzugt:

// 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 befindet sich in bar/1.0/types.hal ( types.hal automatisch importiert wird).
  • IFooCallback wird mithilfe von Regel 2 als android.hardware.bar@1.0::IFooCallback interpoliert, kann jedoch nicht gefunden werden, da bar/1.0/IFooCallback.hal nicht automatisch importiert wird (wie es bei types.hal der Fall ist). Daher löst Regel 3 es stattdessen in android.hardware.foo@1.0::IFooCallback auf, das über import android.hardware.foo@1.0; importiert wird. ).

Typen.hal

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

types.hal aus UDTs und import . Da types.hal jeder Schnittstelle des Pakets zur Verfügung gestellt wird (es handelt sich um einen impliziten Import), sind diese import per Definition auf Paketebene. UDTs in types.hal können auch so importierte UDTs und Schnittstellen enthalten.

Zum Beispiel für ein 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 Schnittstellen und ihrer types.hal )
  • types.hal von android.hardware.baz@1.0::types (Schnittstellen in android.hardware.baz@1.0 werden nicht importiert)
  • IQux.hal types.hal von android.hardware.qux@1.0
  • Quuz von android.hardware.quuz@1.0 (vorausgesetzt Quuz ist in types.hal definiert, wird die gesamte Datei types.hal analysiert, andere Typen als Quuz werden jedoch nicht importiert).

Versionierung auf Schnittstellenebene

Jede Schnittstelle innerhalb eines Pakets befindet sich in einer eigenen Datei. Das Paket, zu dem die Schnittstelle gehört, wird oben in der Schnittstelle mithilfe der package deklariert. Im Anschluss an die Paketdeklaration können null oder mehr Importe auf Schnittstellenebene (Teil- oder Gesamtpaket) aufgelistet werden. Zum Beispiel:

package android.hardware.nfc@1.0;

In HIDL können Schnittstellen mithilfe des Schlüsselworts extends von anderen Schnittstellen erben. Damit eine Schnittstelle eine andere Schnittstelle erweitern kann, muss sie über eine import Zugriff darauf haben. Der Name der Schnittstelle, die erweitert wird (die Basisschnittstelle), folgt den oben erläuterten Regeln für die Typnamenqualifizierung. Eine Schnittstelle darf nur von einer Schnittstelle erben; HIDL unterstützt keine Mehrfachvererbung.

Die folgenden Uprev-Versionierungsbeispiele verwenden das folgende Paket:

// 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 alles von B wahr sein:

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

Alles Folgende ist wahr:

  1. „Vorherige Nebenversion ist gültig“: package@major.(minor-1) muss definiert sein und der gleichen Regel A (keine von package@major.0 bis package@major.(minor-2) sind definiert) oder Regel B folgen (wenn es ein Uprev von @major.(minor-2) ist);

    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 geerbte Schnittstelle mit einem anderen Namen“: Es darf kein package@major.minor::IBar existieren, das package@major.(minor-1)::IBaz erweitert, wobei IBar und IBaz zwei verschiedene Namen sind. Wenn es eine Schnittstelle mit demselben Namen gibt, muss package@major.minor::IBar package@major.(minor-k)::IBar so erweitern, dass keine IBar mit einem kleineren k existiert.

Wegen Regel A:

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

Regel A wirkt sich jedoch nicht auf ein Paket mit demselben Paketnamen, aber einer anderen Hauptversion aus (z. B. sind für android.hardware.camera.device 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.

Sofern der Paketname unterschiedlich ist, kann package@major.minor::IBar von einer Schnittstelle mit einem anderen Namen ausgehen (z. B. kann android.hardware.bar@1.0::IBar android.hardware.baz@2.2::IBaz erweitern). ). Wenn eine Schnittstelle nicht explizit einen Supertyp mit dem Schlüsselwort extend deklariert, erweitert sie android.hidl.base@1.0::IBase (mit Ausnahme IBase selbst).

B.2 und B.3 müssen gleichzeitig befolgt werden. Selbst wenn beispielsweise android.hardware.foo@1.1::IFoo android.hardware.foo@1.0::IFoo erweitert, um Regel B.2 zu erfüllen, wenn ein android.hardware.foo@1.1::IExtBar android.hardware.foo@1.0::IBar , dies ist immer noch kein gültiges Uprev.

Modernste Schnittstellen

So aktualisieren Sie android.hardware.example@1.0 (oben definiert) auf @1.1 :

// 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 ein import auf Paketebene von Version 1.0 von android.hardware.example types.hal . Während in Version 1.1 des Pakets keine neuen UDTs hinzugefügt werden, sind Verweise auf UDTs in Version 1.0 weiterhin erforderlich, daher der Import auf Paketebene types.hal . (Der gleiche Effekt hätte mit einem Import auf Schnittstellenebene in IQuux.hal erzielt werden können.)

In extends @1.0::IQuux haben wir in der Deklaration von IQuux die Version von IQuux angegeben, die geerbt wird (Begriffsklärung ist erforderlich, da IQuux zum Deklarieren einer Schnittstelle und zum Erben von einer Schnittstelle verwendet wird). Da es sich bei Deklarationen lediglich um Namen handelt, die alle Paket- und Versionsattribute am Ort der Deklaration erben, muss die Begriffsklärung im Namen der Basisschnittstelle erfolgen; Wir hätten auch den vollqualifizierten UDT verwenden können, aber das wäre überflüssig gewesen.

Die neue Schnittstelle IQuux deklariert die Methode fromFooToBar() nicht neu, sie erbt von @1.0::IQuux ; Es listet lediglich die neue Methode auf, die fromBarToFoo() hinzugefügt wird. In HIDL dürfen geerbte Methoden in den untergeordneten Schnittstellen nicht erneut deklariert werden, daher kann die IQuux Schnittstelle die fromFooToBar() Methode nicht explizit deklarieren.

Uprev-Konventionen

Manchmal müssen Schnittstellennamen die Erweiterungsschnittstelle umbenennen. Wir empfehlen, dass Enum-Erweiterungen, Strukturen und Unions denselben Namen haben wie das, was sie erweitern, es sei denn, sie unterscheiden sich ausreichend, um einen neuen Namen zu rechtfertigen. 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 ), wird dies bevorzugt. Andernfalls sollte es ähnlich benannt werden wie das, was es erweitert. Beispielsweise kann die Methode foo_1_1 in @1.1::IFoo die Funktionalität der foo Methode in @1.0::IFoo ersetzen, wenn es keinen besseren alternativen Namen gibt.

Versionierung auf Paketebene

Die HIDL-Versionierung erfolgt auf Paketebene. Nachdem ein Paket veröffentlicht wurde, ist es unveränderlich (sein Satz an Schnittstellen und UDTs kann nicht geändert werden). Pakete können auf verschiedene Arten miteinander in Beziehung stehen, die alle durch eine Kombination aus Vererbung auf Schnittstellenebene und der Erstellung von UDTs durch Zusammensetzung ausgedrückt werden können.

Eine Art von Beziehung ist jedoch streng definiert und muss durchgesetzt werden: abwärtskompatible Vererbung auf Paketebene . In diesem Szenario ist das übergeordnete Paket das Paket, von dem geerbt wird, und das untergeordnete Paket ist dasjenige, das das übergeordnete Paket erweitert. Die abwärtskompatiblen Vererbungsregeln auf Paketebene lauten wie folgt:

  1. Alle Schnittstellen der obersten Ebene des übergeordneten Pakets werden von Schnittstellen im untergeordneten Paket geerbt.
  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. Es können auch neue Datentypen hinzugefügt werden, die entweder von neuen Methoden aktualisierter vorhandener Schnittstellen oder von neuen Schnittstellen verwendet werden.

Diese Regeln können mithilfe der Vererbung auf HIDL-Schnittstellenebene und der UDT-Zusammensetzung implementiert werden, erfordern jedoch Kenntnisse auf Metaebene, um zu wissen, dass diese Beziehungen eine abwärtskompatible Paketerweiterung darstellen. Dieses Wissen wird wie folgt abgeleitet:

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