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.
- Datentyp-Definitionsdatei mit dem Namen
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.
- 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
- 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 alsandroid.hardware.bar@1.0::S
interpoliert und befindet sich inbar/1.0/types.hal
(types.hal
automatisch importiert wird). -
IFooCallback
wird mithilfe von Regel 2 alsandroid.hardware.bar@1.0::IFooCallback
interpoliert, kann jedoch nicht gefunden werden, dabar/1.0/IFooCallback.hal
nicht automatisch importiert wird (wie es beitypes.hal
der Fall ist). Daher löst Regel 3 es stattdessen inandroid.hardware.foo@1.0::IFooCallback
auf, das überimport 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 ihrertypes.hal
) -
types.hal
vonandroid.hardware.baz@1.0::types
(Schnittstellen inandroid.hardware.baz@1.0
werden nicht importiert) -
IQux.hal
types.hal
vonandroid.hardware.qux@1.0
-
Quuz
vonandroid.hardware.quuz@1.0
(vorausgesetztQuuz
ist intypes.hal
definiert, wird die gesamte Dateitypes.hal
analysiert, andere Typen alsQuuz
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. |
---|
Regel B | Alles Folgende ist wahr:
|
---|
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 Verzeichnishardware/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:
- Alle Schnittstellen der obersten Ebene des übergeordneten Pakets werden von Schnittstellen im untergeordneten Paket geerbt.
- Dem neuen Paket können auch neue Schnittstellen hinzugefügt werden (keine Einschränkungen hinsichtlich der Beziehungen zu anderen Schnittstellen in anderen Paketen).
- 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.