AIDL-Styleguide

Die hier beschriebenen Best Practices dienen als Leitfaden für die effektive Entwicklung von AIDL-Schnittstellen und unter Berücksichtigung der Flexibilität der Schnittstelle, insbesondere wenn AIDL zur Definition einer API oder zur Interaktion mit API-Oberflächen verwendet wird.

AIDL kann zum Definieren einer API verwendet werden, wenn Apps in einem Hintergrundprozess miteinander oder mit dem 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 .

Versionierung

Jeder abwärtskompatible Snapshot einer AIDL-API entspricht einer Version. Um einen Snapshot zu erstellen, führen Sie m <module-name>-freeze-api aus. Immer wenn ein Client oder Server der API freigegeben wird (z. B. in einem Fernzug), müssen Sie einen Snapshot erstellen und eine neue Version erstellen. Bei System-zu-Anbieter-APIs sollte dies mit der jährlichen Plattformrevision geschehen.

Weitere Details und Informationen über die Art der zulässigen Änderungen finden Sie unter Versionierungsschnittstellen .

API-Designrichtlinien

Allgemein

1. Dokumentieren Sie alles

  • Dokumentieren Sie jede Methode hinsichtlich ihrer Semantik, Argumente, Verwendung integrierter Ausnahmen, dienstspezifischer Ausnahmen und Rückgabewert.
  • Dokumentieren Sie jede Schnittstelle hinsichtlich ihrer Semantik.
  • Dokumentieren Sie die semantische Bedeutung von Aufzählungen und Konstanten.
  • Dokumentieren Sie alles, was einem Implementierer unklar sein könnte.
  • Geben Sie gegebenenfalls Beispiele an.

2. Gehäuse

Verwenden Sie die obere Kamel-Schreibweise für Typen und die untere Kamel-Schreibweise für Methoden, Felder und Argumente. Zum Beispiel MyParcelable für einen parzellierbaren Typ und anArgument für ein Argument. Betrachten Sie bei Akronymen das Akronym als 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 like IFoo beginnen.

2. Vermeiden Sie große Schnittstellen mit ID-basierten „Objekten“

Bevorzugen Sie Unterschnittstellen, wenn es viele Aufrufe im Zusammenhang mit einer bestimmten API gibt. Dies bietet die folgenden Vorteile: - Erleichtert das Verständnis des Client-/Server-Codes - Vereinfacht den Lebenszyklus von Objekten - Nutzt die Unfälschbarkeit von Bindern.

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: Individuelle Subschnittstellen

interface IManager {
    IFoo getFoo();
}

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

3. Mischen Sie nicht einseitige mit zweiseitigen Methoden

[-Wmixed-oneway] Mischen Sie nicht Oneway-Methoden mit Nicht-Oneway-Methoden, da dies das Verständnis des Threading-Modells für Clients und Server erschwert. Insbesondere müssen Sie beim Lesen des Client-Codes einer bestimmten Schnittstelle für jede Methode nachschlagen, ob diese Methode blockiert oder nicht.

4. Vermeiden Sie die Rückgabe von Statuscodes

Methoden sollten Statuscodes als Rückgabewerte vermeiden, da alle AIDL-Methoden einen impliziten Status-Rückgabecode haben. Siehe ServiceSpecificException oder EX_SERVICE_SPECIFIC . Konventionell werden diese Werte als Konstanten in einer AIDL-Schnittstelle definiert. Ausführlichere Informationen finden Sie im Abschnitt zur Fehlerbehandlung von AIDL Backends .

5. Arrays gelten als schädliche Ausgabeparameter

[-Wout-array] Methoden mit Array-Ausgabeparametern wie void foo(out String[] ret) sind normalerweise schlecht, da die Größe des Ausgabe-Arrays vom Client in Java deklariert und zugewiesen werden muss und die Größe der Array-Ausgabe daher nicht möglich ist 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). Bevorzugen Sie stattdessen APIs wie String[] foo() .

6. Vermeiden Sie Inout-Parameter

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

7. Vermeiden Sie out/inout @nullable-Nicht-Array-Parameter

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

Strukturierte Parzellen

1. Wann zu verwenden

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

Oder wenn Sie derzeit über einen einzigen Datentyp verfügen, aber davon ausgehen, dass Sie ihn in Zukunft erweitern müssen. Verwenden Sie beispielsweise nicht String username . Verwenden Sie eine ausziehbare Parzelle wie die folgende:

parcelable User {
    String username;
}

Damit Sie es in Zukunft wie folgt erweitern können:

parcelable User {
    String username;
    int id;
}

2. Geben Sie Standardwerte explizit an

[-Wexplicit-default, -Wenum-explicit-default] Geben Sie explizite Standardeinstellungen für Felder an.

Nicht strukturierte Parzellen

1. Wann zu verwenden

Nicht strukturierte Parcelables sind derzeit in Java mit @JavaOnlyStableParcelable und im NDK-Backend mit @NdkOnlyStableParcelable verfügbar. In der Regel handelt es sich dabei um alte und bestehende Parzellen, die sich nicht einfach strukturieren lassen.

Konstanten und Aufzählungen

1. Bitfelder sollten konstante Felder verwenden

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

2. Aufzählungen sollten geschlossene Mengen sein.

Aufzählungen sollten geschlossene Mengen sein. Hinweis: Nur der Schnittstelleneigentümer kann Aufzählungselemente hinzufügen. Wenn Anbieter oder OEMs diese Bereiche erweitern müssen, ist ein alternativer Mechanismus erforderlich. Wann immer möglich, sollten Funktionen von Upstream-Anbietern bevorzugt werden. In einigen Fällen können jedoch benutzerdefinierte Anbieterwerte zugelassen werden (obwohl Anbieter über einen Mechanismus zur Versionierung dieser Werte verfügen sollten, möglicherweise AIDL selbst, sollten sie nicht in der Lage sein, miteinander in Konflikt zu geraten, und diese Werte sollten dies auch nicht tun). nicht den Apps von Drittanbietern ausgesetzt sind).

3. Vermeiden Sie Werte wie „NUM_ELEMENTS“

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

Nicht empfohlen: Verwendung nummerierter Werte

@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: Direkte Benennung der Enumeration

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] Von der Verwendung von FileDescriptor als Argument oder Rückgabewert einer AIDL-Schnittstellenmethode wird dringend abgeraten. Insbesondere wenn AIDL in Java implementiert ist, kann dies zu Dateideskriptorlecks führen, wenn nicht sorgfältig damit umgegangen wird. Wenn Sie einen FileDescriptor akzeptieren, müssen Sie ihn grundsätzlich manuell schließen, wenn er nicht mehr verwendet wird.

Bei nativen Backends sind Sie auf der sicheren Seite, da FileDescriptor auf unique_fd abgebildet wird, das automatisch geschlossen werden kann. Aber unabhängig von der Backend-Sprache, die Sie verwenden würden, ist es ratsam, FileDescriptor überhaupt NICHT zu verwenden, da dies Ihre Freiheit, die Backend-Sprache in Zukunft zu ändern, einschränkt.

Verwenden Sie stattdessen ParcelFileDescriptor , das automatisch geschlossen werden kann.

Variable Einheiten

Stellen Sie sicher, dass variable Einheiten im Namen enthalten sind, damit ihre Einheiten 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 (eigentlich alle Einheiten!) müssen eindeutig ihre Einheiten und Referenzpunkte 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;