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.
- Datentyp-Definitionsdatei namens
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.
- 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
- 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 alsandroid.hardware.bar@1.0::S
interpoliert und befindet sich inbar/1.0/types.hal
(weiltypes.hal
automatisch importiert wird). -
IFooCallback
wird unter Verwendung von Regel 2 alsandroid.hardware.bar@1.0::IFooCallback
interpoliert, kann aber nicht gefunden werden, dabar/1.0/IFooCallback.hal
nicht automatisch importiert wird (wie estypes.hal
ist). Daher löst Regel 3 es stattdessen inandroid.hardware.foo@1.0::IFooCallback
auf, das überimport 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 ihrertypes.hal
) -
types.hal
vonandroid.hardware.baz@1.0::types
(Schnittstellen inandroid.hardware.baz@1.0
werden nicht importiert) -
IQux.hal
undtypes.hal
vonandroid.hardware.qux@1.0
-
Quuz
vonandroid.hardware.quuz@1.0
(unter der Annahme, dassQuuz
intypes.hal
definiert ist, wird die gesamte Dateitypes.hal
geparst, aber andere Typen alsQuuz
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. |
---|
Regel B | Alles folgende ist wahr:
|
---|
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 Verzeichnishardware/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:
- Alle Schnittstellen der obersten Ebene des übergeordneten Pakets werden von Schnittstellen im untergeordneten Paket geerbt.
- Neue Schnittstellen können auch dem neuen Paket hinzugefügt werden (keine Einschränkungen bezüglich Beziehungen zu anderen Schnittstellen in anderen Paketen).
- 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.