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 Apps 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 Stable AIDL.

Versionsverwaltung

Jedem abwärtskompatiblen Snapshot einer AIDL API entspricht eine Version. Führen Sie m <module-name>-freeze-api aus, um einen Snapshot aufzunehmen. Jedes Mal, wenn ein Client oder Server der API veröffentlicht wird (z. B. in einem Mainline-Release), müssen Sie einen Snapshot erstellen und eine neue Version erstellen. Bei System-zu-Anbieter-APIs sollte dies bei der jährlichen Plattformüberprüfung 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 für jede Methode die Semantik, Argumente, Verwendung von vordefinierten Ausnahmen, dienstspezifischen Ausnahmen und Rückgabewert.
  • Dokumentieren Sie die Semantik jeder Schnittstelle.
  • Dokumentieren Sie die semantische Bedeutung von Enums und Konstanten.
  • Dokumentieren Sie alles, was für die Implementierung unklar sein könnte.
  • Geben Sie gegebenenfalls Beispiele an.

2. Gehäuse

Verwenden Sie Camel Case mit Großbuchstaben für Typen und Camel Case mit Kleinbuchstaben für Methoden, Felder und Argumente. Beispiel: MyParcelable für einen Parcelable-Typ und anArgument für ein Argument. Betrachten Sie Akronyme als Wörter (NFC -> Nfc).

[-Wconst-name] Enum-Werte und Konstanten müssen ENUM_VALUE und CONSTANT_NAME sein

Schnittstellen

1. Benennung

[-Winterface-name] Der Name der Benutzeroberfläche muss mit I beginnen, z. B. IFoo.

2. Vermeiden Sie eine große Benutzeroberfläche mit ID-basierten „Objekten“

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

  • Erleichtert das Verständnis von Client- oder Servercode
  • Vereinfacht den Lebenszyklus von Objekten
  • Nutzt die Fälschungssicherheit von Bindungen.

Nicht empfohlen:Eine einzelne große Benutzeroberfläche 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:Individuelle Oberflächen

interface IManager {
    IFoo getFoo();
}

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

3. Unidirektionale und bidirektionale Methoden nicht mischen

[-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 des Clientcodes einer bestimmten Schnittstelle müssen Sie für jede Methode nachsehen, ob sie blockiert wird oder nicht.

4. Statuscodes nicht zurückgeben

Bei Methoden sollten keine Statuscodes als Rückgabewerte verwendet werden, da alle AIDL-Methoden einen impliziten Statusrückgabecode haben. Weitere Informationen finden Sie unter ServiceSpecificException oder EX_SERVICE_SPECIFIC. Diese Werte werden üblicherweise als Konstanten in einer AIDL-Schnittstelle definiert. Weitere Informationen finden Sie im Abschnitt zur Fehlerbehandlung von AIDL-Back-Ends.

5. Arrays als Ausgabeparameter gelten als schädlich

[-Wout-array] Methoden mit Arrayausgabeparametern wie void foo(out String[] ret) sind in der Regel nicht empfehlenswert, da die Größe des Ausgabearrays vom Client in Java deklariert und zugewiesen werden muss. Die Größe der Arrayausgabe kann also nicht vom Server ausgewählt werden. Dieses unerwünschte Verhalten ist auf die Funktionsweise von Arrays in Java zurückzuführen (sie können nicht neu zugewiesen werden). Verwenden Sie stattdessen APIs wie String[] foo().

6. Inout-Parameter vermeiden

[-Winout-parameter] Das kann Kunden verwirren, da auch in-Parameter wie out-Parameter aussehen.

7. Nicht-Array-Parameter vom Typ „out“ und „inout“ mit dem Attribut „@nullable“ vermeiden

[-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-Backends können beispielsweise einen @nullable-Parameter auf null setzen (in C++ als std::nullopt), der Java-Client kann ihn jedoch nicht als null lesen.

Strukturierte Pakete

1. Anwendung

Verwenden Sie strukturierte Parcelables, wenn Sie mehrere Datentypen senden möchten.

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

parcelable User {
    String username;
}

So können Sie ihn später so 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 Pakete

1. Anwendung

Unstrukturierte Parcelable-Objekte sind in Java mit @JavaOnlyStableParcelable und im NDK-Backend mit @NdkOnlyStableParcelable verfügbar. In der Regel handelt es sich dabei um alte und vorhandene Parcelable-Objekte, die nicht strukturiert werden können.

Konstanten und enums

1. Bitfelder sollten konstante Felder verwenden

Bitfelder sollten konstante Felder verwenden (z. B. const int FOO = 3; in einer Schnittstelle).

2. Enums sollten geschlossene Mengen sein.

Enums sollten geschlossene Mengen sein. Hinweis: Nur der Inhaber der Benutzeroberfläche 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 Anbieters vorgeschaltet werden. In einigen Fällen sind benutzerdefinierte Anbieterwerte jedoch zulässig. Anbieter sollten jedoch einen Mechanismus zur Versionierung haben, möglicherweise AIDL selbst. Die Werte dürfen nicht miteinander in Konflikt stehen und dürfen nicht für Drittanbieter-Apps freigegeben werden.

3. Vermeiden Sie Werte wie „NUM_ELEMENTS“.

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

Nicht empfohlen:Verwendung von nummerierten Werten

@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 Enumeratoren.

Nicht empfohlen: Verwendung eines redundanten Präfixes

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Empfohlen:Enum direkt benennen

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

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

Bei nativen Backends sind Sie auf der sicheren Seite, da FileDescriptor auf unique_fd umgestellt wird, das automatisch geschlossen werden kann. Unabhängig von der verwendeten Backend-Sprache ist es jedoch ratsam, FileDescriptor NICHT zu verwenden, da dies Ihre Möglichkeiten einschränkt, die Backend-Sprache in Zukunft zu ändern.

Verwenden Sie stattdessen ParcelFileDescriptor, das sich automatisch schließen lässt.

Variable Einheiten

Achten Sie darauf, dass die Einheiten der Variablen im Namen enthalten sind, damit sie klar definiert und verständlich sind, ohne dass auf die Dokumentation verwiesen werden muss.

Beispiele

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

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

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

Zeitstempel müssen ihre Referenz angeben

Zeitstempel (und tatsächlich alle Einheiten) müssen ihre Einheiten und Referenzpunkte klar angeben.

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;