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 gegebene veröffentlichte Schnittstelle nicht modifiziert werden kann, kann sie durch 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 verwendet werden können, um komplexere Typen über Strukturen, Vereinigungen und Aufzählungen zusammenzusetzen. 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, auf denen sie arbeiten. Ein Paket wird durch einen Namen und eine Version identifiziert und enthält Folgendes:
    • Datentyp-Definitionsdatei namens types.hal .
    • Null oder mehr Schnittstellen, jede in ihrer eigenen .hal -Datei.

Die Datentyp-Definitionsdatei types.hal enthält nur UDTs (alle UDTs auf Paketebene werden in einer einzigen Datei gespeichert). Darstellungen in der Zielsprache sind für alle Schnittstellen im Paket verfügbar.

Versionierungsphilosophie

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

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 die Patch-Level- und Build-Metadaten-Komponenten. Innerhalb eines bestimmten Pakets impliziert ein Minor-Versionsstoß , dass die neue Version des Pakets mit dem alten Paket abwärtskompatibel ist, und ein Major-Versionsstoß impliziert, dass die neue Version des Pakets nicht mit dem alten Paket abwärtskompatibel ist.

Konzeptionell kann ein Paket auf verschiedene Weise mit einem anderen Paket in Beziehung stehen:

  • Überhaupt nicht .
  • Abwärtskompatible Erweiterbarkeit auf Paketebene . Dies tritt bei neuen Unterversions-Uprevs (nächste inkrementierte Revision) eines Pakets auf; Das neue Paket hat denselben Namen und dieselbe Hauptversion wie das alte Paket, aber eine höhere Nebenversion. Funktional ist das neue Paket eine Obermenge des alten Pakets, was bedeutet:
    • Schnittstellen der obersten Ebene des übergeordneten Pakets sind in dem neuen Paket vorhanden, obwohl die Schnittstellen möglicherweise neue Methoden, neue schnittstellenlokale UDTs (die unten beschriebene Erweiterung auf Schnittstellenebene) und neue UDTs in types.hal .
    • 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 (möglicherweise neu implementierten) Methoden aus dem alten Paket behandelt werden.
    • Es können auch neue Datentypen zur Verwendung entweder durch neue Verfahren von verbesserten bestehenden Schnittstellen oder durch neue Schnittstellen hinzugefügt werden.
  • Abwärtskompatible Erweiterbarkeit auf Schnittstellenebene . Das neue Paket kann das ursprüngliche Paket auch erweitern, indem es aus logisch getrennten Schnittstellen besteht, die einfach zusätzliche Funktionalität und nicht die Kernfunktionalität bereitstellen. 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 von einem oder mehreren alten Paketen erweitern.
  • Erweitern Sie die ursprüngliche Abwärtsinkompatibilität . Dies ist eine Hauptversion des Pakets, und es muss keine Korrelation zwischen den beiden geben. Soweit dies der Fall ist, kann dies mit einer Kombination von Typen aus der älteren Version des Pakets und der Vererbung einer Teilmenge von Schnittstellen des alten Pakets ausgedrückt werden.

Schnittstellen strukturieren

Für eine gut strukturierte Schnittstelle sollte das Hinzufügen neuer Arten von Funktionalität, die nicht Teil des ursprünglichen Designs sind, eine Modifikation der HIDL-Schnittstelle erfordern. Wenn Sie dagegen auf beiden Seiten der Schnittstelle eine Änderung 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 Datei " 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 viele Jahre lang funktionieren können. Dazu gehören viele API-Oberflächen, aber eine Hauptoberfläche ist der IPC-Mechanismus, den HIDL für die Kommunikation zwischen Prozessen an der Grenze von system.img / vendor.img verwendet.

Anforderungen

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

  • Kann in HIDL direkt (unter Verwendung von Structs-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 ein physikalisches Hardwarelayout beschrieben werden.
  • Können bei Bedarf undurchsichtige Daten (wie öffentliche Schlüssel, IDs usw.) sein.

Wenn undurchsichtige Daten verwendet werden, dürfen sie nur von einer Seite der HIDL-Schnittstelle gelesen werden. Wenn beispielsweise der Code von system.img vendor.img Zeichenfolgennachricht oder vec<uint8_t> gibt, können diese Daten nicht von system.img selbst geparst werden; Es kann nur zur Interpretation an die vendor.img zurückgegeben werden. Bei der Übergabe eines Werts aus dem Anbieter.img an system.img vendor.img an ein anderes Gerät muss das Format der Daten und wie sie interpretiert werden sollen, genau beschrieben werden und ist immer noch 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 genaue erforderliche Verhalten anzugeben. Aussagen wie „eine Implementierung kann A oder B tun“ ermutigen Implementierungen, sich mit den Kunden zu verflechten, mit denen sie entwickelt wurden.

HIDL-Code-Layout

HIDL umfasst Core- und Vendor-Pakete.

Kern-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. Beispielsweise heißt das NFC-Paket 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. 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 ist unter hardware/interfaces/name1/name2//$m.$n/ ; Paket android.hardware.camera Version 3.4 befindet sich im Verzeichnis hardware/interfaces/camera/3.4/. Zwischen dem android.hardware. und den Pfad hardware/interfaces/ .

Non-Core-Pakete (Anbieter) sind solche, die vom SoC-Anbieter oder ODM produziert werden. Das Präfix für Nicht-Kernpakete ist vendor.$(VENDOR).hardware. wobei sich $(VENDOR) auf einen SoC-Anbieter oder OEM/ODM bezieht. Dies wird dem Pfad vendor/$(VENDOR)/interfaces im Baum zugeordnet (diese Zuordnung ist auch fest codiert).

Vollqualifizierte Namen benutzerdefinierter Typen

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, wenn der Typ selbst definiert ist. Angenommen, das Paket android.hardware.nfc, Version 1.0 , definiert eine Struktur namens NfcData . An der Stelle der Deklaration (ob in 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 Major.Minor-Version-Format des Pakets (z. B. 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 auch in einer Schnittstelle namens IQuux :

// 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 lautet 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önnten 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);

Vollqualifizierte Aufzählungswerte

Wenn ein UDT ein Aufzählungstyp ist, dann hat jeder Wert des Aufzählungstyps einen vollqualifizierten Namen, der mit dem vollqualifizierten Namen des Aufzählungstyps beginnt, gefolgt von einem Doppelpunkt, gefolgt vom Namen des Aufzählungswerts. Angenommen, das Paket android.hardware.nfc, Version 1.0 , definiert einen Aufzählungstyp NfcStatus :

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

Wenn auf STATUS_OK wird, lautet der vollständig qualifizierte Name:

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

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

  • PACKAGE @ VERSION :: UDT ist genau derselbe vollständig qualifizierte Name für den Aufzählungstyp.
  • VALUE ist der Name des Werts.

Auto-Inferenz-Regeln

Ein vollständig qualifizierter UDT-Name muss nicht angegeben werden. Bei einem UDT-Namen kann Folgendes getrost weggelassen werden:

  • Das Paket, zB @1.0::IFoo.Type
  • Sowohl Paket als auch Version, zB 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 bereitgestellt 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 nachgeschlagen und die typedef darüber gefunden. NfcData wird ebenfalls 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 ausgefüllt. Der HIDL-Compiler sucht dann in der aktuellen Datei (und allen Importen) nach dem automatisch ausgefüllten vollqualifizierten Namen. Nehmen Sie im obigen Beispiel an, dass die Deklaration von ExtendedNfcData im selben Paket ( android.hardware.nfc ) in derselben Version ( 1.0 ) wie NfcData wie folgt vorgenommen wurde:

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 erzeugen. Da der Name im aktuellen Paket vorhanden ist (vorausgesetzt, er wird ordnungsgemäß importiert), wird er für die Deklaration verwendet.

Ein Name im aktuellen Paket wird nur importiert, wenn einer der folgenden Punkte zutrifft:

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

Derselbe Vorgang wird durchgeführt, 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 in allen importierten Paketen nach einer Übereinstimmung. Nehmen Sie anhand des obigen Beispiels an, ExtendedNfcData ist in Version 1.1 des Pakets android.hardware.nfc , 1.1 importiert 1.0 wie es sollte (siehe Erweiterungen auf Paketebene) und die Definition gibt nur den UDT-Namen an:

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

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

Beispiel

Mit Regel 2 wird ein im aktuellen Paket definierter importierter Typ 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 (weil types.hal automatisch importiert wird).
  • IFooCallback wird unter Verwendung von 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 es types.hal ist). Daher löst Regel 3 es stattdessen in android.hardware.foo@1.0::IFooCallback auf, das über import android.hardware.foo@1.0; ).

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 wird, sind diese Typen außerhalb des Bereichs zugänglich, in dem sie definiert sind. 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 besteht aus UDTs und import -Anweisungen. Da types.hal für jede Schnittstelle des Pakets verfügbar gemacht 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;

Importiert werden:

  • android.hidl.base@1.0::IBase (implizit)
  • android.hardware.foo@1.0::types (implizit)
  • Alles in android.hardware.bar@1.0 (einschließlich aller Interfaces 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 und types.hal von android.hardware.qux@1.0
  • Quuz von android.hardware.quuz@1.0 (unter der Annahme, dass Quuz in types.hal definiert ist, wird die gesamte Datei types.hal geparst, aber andere Typen als Quuz werden 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 mit der package am Anfang der Schnittstelle deklariert. Nach der Paketdeklaration können null oder mehr Importe auf Schnittstellenebene (Teil- oder Gesamtpaket) aufgelistet werden. Zum Beispiel:

package android.hardware.nfc@1.0;

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

Die folgenden Beispiele für die Uprev-Versionierung 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 B vollständig 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 derselben 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 gleichnamige Schnittstelle erben“: Es existiert eine Schnittstelle package@major.minor::IFoo , die package@major.(minor-1)::IFoo (falls das vorherige Paket eine Schnittstelle hat);

    UND

  3. "Keine geerbte Schnittstelle mit anderem Namen": Es darf kein package@major.minor::IBar , das package@major.(minor-1)::IBaz , 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 kein IBar mit einem kleineren k existiert.

Wegen 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 gar nicht existieren sollte.

Regel A wirkt sich jedoch nicht auf ein Paket mit demselben Paketnamen, aber einer anderen Hauptversion aus (z. B. hat 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 .

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

B.2 und B.3 müssen gleichzeitig befolgt werden. Zum Beispiel, selbst wenn android.hardware.foo@1.1::IFoo android.hardware.foo@1.0::IFoo erweitert, um Regel B.2 zu bestehen, wenn eine android.hardware.foo@1.1::IExtBar erweitert android.hardware.foo@1.0::IBar , dies ist immer noch kein gültiger Uprev.

Schnittstellen verbessern

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 in types.hal . Während in Version 1.1 des Pakets keine neuen UDTs hinzugefügt werden, sind Verweise auf UDTs in Version 1.0 immer noch erforderlich, daher der Import auf Paketebene in types.hal . (Derselbe Effekt hätte mit einem Import auf Schnittstellenebene in IQuux.hal werden können.)

In extends @1.0::IQuux in der Deklaration von IQuux haben wir die Version von IQuux angegeben, die geerbt wird (eine Begriffsklärung ist erforderlich, da IQuux verwendet wird, um eine Schnittstelle zu deklarieren und von einer Schnittstelle zu erben). Da Deklarationen einfach Namen sind, die alle Paket- und Versionsattribute am Standort 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 erneut, sie erbt von @1.0::IQuux ; es listet einfach die neue Methode auf, die es fromBarToFoo() hinzufügt. In HIDL dürfen geerbte Methoden in den untergeordneten Schnittstellen nicht erneut deklariert werden, daher kann die IQuux Schnittstelle die Methode fromFooToBar() nicht explizit deklarieren.

Upprev-Konventionen

Manchmal müssen Schnittstellennamen die erweiternde Schnittstelle umbenennen. Wir empfehlen, dass Aufzählungserweiterungen, Strukturen und Vereinigungen 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 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 von Schnittstellen und UDTs kann nicht geändert werden). Pakete können auf verschiedene Weise miteinander in Beziehung stehen, die alle über eine Kombination aus Vererbung auf Schnittstellenebene und Erstellung von UDTs durch Zusammensetzung ausgedrückt werden können.

Ein Beziehungstyp ist jedoch streng definiert und muss erzwungen 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. Abwärtskompatible Vererbungsregeln auf Paketebene lauten wie folgt:

  1. Alle Schnittstellen der obersten Ebene des übergeordneten Pakets werden von Schnittstellen im untergeordneten Paket geerbt.
  2. Neue Schnittstellen können auch dem neuen Paket hinzugefügt werden (keine Einschränkungen bezüglich Beziehungen zu anderen Schnittstellen in anderen Paketen).
  3. Es können auch neue Datentypen zur Verwendung entweder durch neue Verfahren von verbesserten bestehenden Schnittstellen oder durch neue Schnittstellen hinzugefügt werden.

Diese Regeln können mithilfe der Vererbung auf HIDL-Schnittstellenebene und der UDT-Komposition 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.