AIDL-Styleguide

Die hier beschriebenen Best Practices dienen als Leitfaden für die effektive Entwicklung von AIDL-Schnittstellen mit besonderer Berücksichtigung der Flexibilität der Schnittstelle, insbesondere wenn AIDL verwendet wird, um eine API zu definieren oder mit API-Oberflächen zu interagieren.

Mit AIDL können Sie eine API definieren, wenn Apps in einem Hintergrundprozess oder mit dem System interagieren 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 unter Versionsverwaltung – Schnittstellen.

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 Enumerationen und Konstanten.
  • Dokumentieren Sie alles, was für einen Implementierer 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“

Verwenden Sie Unterschnittstellen, wenn es viele Aufrufe im Zusammenhang mit einer bestimmten API gibt. 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:Einzelne 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] Verwenden Sie keine One-Way-Methoden zusammen mit anderen Methoden, da dies das Verständnis des Thread-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. Eingabeparameter 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-Backend die @nullable-Anmerkung nicht verarbeitet, während andere Backends dies tun, kann @nullable zu inkonsistentem Verhalten bei verschiedenen Backends führen.out/inout @nullable T 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 Parcelable, z. B.:

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 Pakete, die nicht strukturiert werden können.

Konstanten und enums

1. Für Bitfelder sollten Konstantenfelder verwendet werden

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:Verwenden von nummerierten Werten

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

4. Vermeiden Sie redundante Präfixe und Suffixe.

[-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 Back-Ends 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

Bei Zeitstempeln (und tatsächlich bei allen Einheiten) müssen die Einheiten und Referenzpunkte klar 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;