AIDL-Styleguide

Die hier beschriebenen Best Practices dienen als Leitfaden für die effektive Entwicklung von AIDL-Schnittstellen. Dabei wird auf die Flexibilität der Schnittstelle geachtet, insbesondere wenn AIDL zum Definieren einer API oder zur Interaktion mit API-Oberflächen verwendet wird.

AIDL kann zum Definieren einer API verwendet werden, wenn Anwendungen in einem Hintergrundprozess oder das System kommunizieren müssen. Weitere Informationen zum Entwickeln von Programmierschnittstellen in Anwendungen mit AIDL finden Sie unter Android Interface Definition Language (AIDL). Beispiele für AIDL in der Praxis finden Sie unter AIDL für HALs und Stabile AIDL.

Versionsverwaltung

Jeder abwärtskompatible Snapshot einer AIDL API entspricht einer Version. Führen Sie m <module-name>-freeze-api aus, um einen Snapshot zu erstellen. Wenn ein Client oder Server der API freigegeben wird (z. B. in einem Mainline-Training), müssen Sie einen Snapshot und eine neue Version erstellen. Bei APIs zwischen System und Anbieter sollte dies mit der jährlichen Plattformüberarbeitung geschehen.

Weitere Informationen und Informationen zu den zulässigen Änderungen finden Sie im Artikel zur Versionsverwaltung von Oberflächen.

API-Designrichtlinien

Allgemein

1. Alles dokumentieren

  • Dokumentieren Sie jede Methode bezüglich ihrer Semantik, Argumente, Verwendung integrierter Ausnahmen, dienstspezifischer Ausnahmen und des Rückgabewerts.
  • Dokumentieren Sie jede Schnittstelle für ihre Semantik.
  • Dokumentieren Sie die semantische Bedeutung von Enums und Konstanten.
  • Dokumentieren Sie alles, was für die Implementierung unklar sein könnte.
  • Gib gegebenenfalls Beispiele an.

2. Gehäuse

Verwenden Sie die Camel-Groß-/Kleinschreibung für Typen und die Camel-Case-Schreibweise für Methoden, Felder und Argumente. Beispiel: MyParcelable für einen Pakettyp und anArgument für ein Argument. Bei Akronymen ist das Akronym ein Wort (NFC -> Nfc).

[-Wconst-name] Enum-Werte und -Konstanten sollten ENUM_VALUE und CONSTANT_NAME sein.

Schnittstellen

1. Benennung

[-Winterface-name] Ein Schnittstellenname sollte mit I beginnen, z. B. IFoo.

2. Große Oberfläche mit ID-basierten „Objekten“ vermeiden

Bevorzugen Sie Subschnittstellen, wenn viele Aufrufe für eine bestimmte API vorhanden sind. Dies bietet folgende Vorteile:

  • Verständlicher Client- oder Servercode
  • Vereinfacht den Lebenszyklus von Objekten
  • Nutzt die Vorteile von Bindern, die unfälschbar sind.

Nicht empfohlen:Eine einzelne, große Schnittstelle mit ID-basierten Objekten

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Empfohlen:Einzelne Benutzeroberflächen

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Vermischen Sie nicht die Einbahnstraße mit der Zwei-Wege-Methode.

[-Wmixed-oneway] Vermischen Sie keine Einwegmethoden mit Methoden, die nicht in eine Richtung erfolgen, da dies das Verständnis des Threading-Modells für Clients und Server erschwert. Insbesondere beim Lesen von Clientcode einer bestimmten Schnittstelle müssen Sie nach jeder Methode suchen, ob diese Methode blockiert oder nicht.

4. Rückgabe von Statuscodes vermeiden

Methoden sollten keine Statuscodes als Rückgabewerte verwenden, da alle AIDL-Methoden einen impliziten Statuscode zurückgeben. Siehe ServiceSpecificException oder EX_SERVICE_SPECIFIC. Per Konvention werden diese Werte als Konstanten in einer AIDL-Schnittstelle definiert. Weitere Informationen finden Sie im Abschnitt zur Fehlerbehandlung von AIDL-Back-Ends.

5. Arrays als Ausgabeparameter, die als schädlich eingestuft werden

[-Wout-array] Methoden mit Array-Ausgabeparametern wie void foo(out String[] ret) sind in der Regel fehlerhaft, da die Größe des Ausgabearrays vom Client in Java deklariert und zugewiesen werden muss. Daher kann die Größe der Arrayausgabe nicht vom Server ausgewählt werden. Dieses unerwünschte Verhalten entsteht aufgrund der Funktionsweise von Arrays in Java (sie können nicht neu zugewiesen werden). Bevorzugen Sie stattdessen APIs wie String[] foo().

6. Inout-Parameter vermeiden

[-Winout-parameter] Dies kann Clients verwirren, da selbst in-Parameter wie out-Parameter aussehen.

7. Out- und Inout-Parameter, die keine @nullable-Werte sind

[-Wout-nullable] Da das Java-Back-End im Gegensatz zu anderen Back-Ends die Annotation @nullable nicht verarbeitet, kann out/inout @nullable T zu einem inkonsistenten Verhalten über Back-Ends führen. Nicht-Java-Back-Ends können beispielsweise einen @nullable-Parameter auf null setzen (in C++ mit std::nullopt festlegen), aber der Java-Client kann ihn nicht als null lesen.

Strukturierte Parcelables

1. Anwendung

Verwenden Sie strukturierte Parcelables, wenn Sie mehrere Datentypen senden müssen.

Oder wenn Sie einen einzelnen Datentyp haben, aber davon ausgehen, dass Sie ihn in Zukunft erweitern müssen. Nutze zum Beispiel nicht String username. Verwenden Sie ein erweiterbares Paket wie hier gezeigt:

parcelable User {
    String username;
}

In Zukunft können Sie die Erweiterung wie folgt erweitern:

parcelable User {
    String username;
    int id;
}

2. Standardwerte explizit angeben

[-Wexplicit-default, -Wenum-explicit-default] Gibt explizite Standardwerte für Felder an.

Nicht strukturierte Parcelables

1. Anwendung

Nicht strukturierte Parcelables sind in Java mit @JavaOnlyStableParcelable und im NDK-Back-End mit @NdkOnlyStableParcelable verfügbar. In der Regel sind dies alte und vorhandene Parzellen, die nicht strukturiert werden können.

Konstanten und Enums

1. Bitfelder sollten konstante Felder verwenden

Für Bitfelder sollten konstante Felder verwendet werden (z. B. const int FOO = 3; in einer Schnittstelle).

2. Enums sollten geschlossene Sätze sein.

Enums sollten geschlossene Sätze sein. Hinweis: Nur der Inhaber der Schnittstelle kann enum-Elemente hinzufügen. Wenn Anbieter oder OEMs diese Felder erweitern müssen, ist ein alternativer Mechanismus erforderlich. Nach Möglichkeit sollten die Funktionen des Upstreaming-Anbieters bevorzugt werden. In einigen Fällen sind jedoch benutzerdefinierte Anbieterwerte zulässig. Anbieter sollten jedoch einen Mechanismus zur Versionsausführung implementieren, z. B. AIDL selbst. Die Werte dürfen jedoch nicht miteinander in Konflikt stehen und diese Werte sollten nicht für Drittanbieteranwendungen freigegeben werden.

3. Vermeiden Sie Werte wie "NUM_ELEMENTS".

Da Enums versioniert sind, sollten Werte, die angeben, wie viele Werte vorhanden sind, vermieden werden. In C++ kann das mit enum_range<> umgangen werden. Verwenden Sie für Rost enum_values(). In Java gibt es noch keine Lösung.

Nicht empfohlen:Nummerierte Werte verwenden

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Redundante Präfixe und Suffixe vermeiden

[-Wredundant-name] Vermeiden Sie redundante oder sich wiederholende Präfixe und Suffixe in Konstanten und Zählern.

Nicht empfohlen: Verwendung eines redundanten Präfixes

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Empfohlen:Enum direkt benennen

enum MyStatus {
    GOOD,
    BAD
}

FileDeskriptor

[-Wfile-descriptor] Von der Verwendung von FileDescriptor als Argument oder als Rückgabewert einer AIDL-Schnittstelle wird dringend abgeraten. Insbesondere wenn die AIDL in Java implementiert ist, kann dies zu einem Dateideskriptorleck führen, sofern es nicht sorgfältig umgegangen wird. Wenn Sie eine FileDescriptor akzeptieren, müssen Sie sie manuell schließen, wenn sie nicht mehr verwendet wird.

Bei nativen Back-Ends sind Sie sicher, da FileDescriptor der unique_fd zugeordnet ist, die automatisch geschlossen werden kann. Unabhängig von der Back-End-Sprache, die Sie verwenden, ist es jedoch ratsam, FileDescriptor NICHT zu verwenden, da Sie dadurch die Back-End-Sprache in Zukunft nicht mehr ändern können.

Verwenden Sie stattdessen ParcelFileDescriptor, das automatisch geschlossen werden kann.

Variable Einheiten

Achten Sie darauf, dass variable Einheiten im Namen enthalten sind, damit ihre Einheiten klar definiert und verstanden werden, ohne auf die Dokumentation verweisen zu müssen.

Beispiele

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Die Zeitstempel müssen ihren Verweis angeben

In Zeitstempeln (genau genommen alle Einheiten!) müssen die Einheiten und Bezugspunkte eindeutig angegeben sein.

Beispiele

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;