Richtlinien für Android-APIs

Diese Seite soll Entwicklern als Leitfaden dienen, um die allgemeinen Prinzipien zu verstehen, die der API-Konsens bei API-Überprüfungen durchsetzt.

Neben diesen Richtlinien sollten Entwickler beim Erstellen von APIs das Tool API Lint ausführen. Dieses Tool codiert viele dieser Regeln in Prüfungen, die an APIs ausgeführt werden.

Sie können diesen Artikel als Leitfaden für die Regeln betrachten, die von diesem Lint-Tool befolgt werden, sowie als allgemeine Hinweise zu Regeln, die in diesem Tool nicht mit hoher Genauigkeit codiert werden können.

API Lint-Tool

API Lint ist in das statische Analysetool Metalava eingebunden und wird während der Validierung in CI automatisch ausgeführt. Sie können ihn manuell über eine lokale Plattform-Checkout-Datei mit m checkapi oder eine lokale AndroidX-Checkout-Datei mit ./gradlew :path:to:project:checkApi ausführen.

API-Regeln

Die Android-Plattform und viele Jetpack-Bibliotheken gab es schon vor der Erstellung dieser Richtlinien. Die Richtlinien, die weiter unten auf dieser Seite aufgeführt sind, werden ständig weiterentwickelt, um den Anforderungen des Android-Ökosystems gerecht zu werden.

Daher entsprechen einige vorhandene APIs möglicherweise nicht den Richtlinien. In anderen Fällen kann es für App-Entwickler eine bessere Nutzererfahrung bieten, wenn eine neue API mit vorhandenen APIs übereinstimmt, anstatt sich strikt an die Richtlinien zu halten.

Wenn Sie schwierige Fragen zu einer API haben, die geklärt werden müssen, oder Richtlinien aktualisiert werden müssen, wenden Sie sich an den API-Rat.

API-Grundlagen

Diese Kategorie bezieht sich auf die Hauptaspekte einer Android-API.

Alle APIs müssen implementiert sein

Unabhängig von der Zielgruppe einer API (z. B. öffentlich oder @SystemApi) müssen alle API-Oberflächen implementiert werden, wenn sie zusammengeführt oder als API freigegeben werden. Fügen Sie API-Stubs nicht zu einer Implementierung zusammen, die erst später erfolgt.

API-Oberflächen ohne Implementierungen haben mehrere Probleme:

  • Es kann nicht garantiert werden, dass eine korrekte oder vollständige Oberfläche freigelegt wurde. Solange eine API nicht von Kunden getestet oder verwendet wird, gibt es keine Möglichkeit, zu überprüfen, ob ein Kunde die entsprechenden APIs hat, um die Funktion nutzen zu können.
  • APIs ohne Implementierung können in Entwicklervorschauen nicht getestet werden.
  • APIs ohne Implementierung können nicht in CTS getestet werden.

Alle APIs müssen getestet werden

Dies entspricht den CTS-Anforderungen der Plattform, den AndroidX-Richtlinien und der allgemeinen Vorstellung, dass APIs implementiert werden müssen.

Durch das Testen von API-Oberflächen können wir sicherstellen, dass die API-Oberfläche nutzbar ist und wir die erwarteten Anwendungsfälle berücksichtigt haben. Es reicht nicht aus, nur die Existenz zu prüfen. Das Verhalten der API selbst muss getestet werden.

Eine Änderung, durch die eine neue API hinzugefügt wird, sollte entsprechende Tests im selben CL- oder Gerrit-Thema enthalten.

APIs sollten außerdem testbar sein. Sie sollten die Frage beantworten können: „Wie testen App-Entwickler Code, der Ihre API verwendet?“

Alle APIs müssen dokumentiert sein

Die Dokumentation ist ein wichtiger Bestandteil der API-Usability. Die Syntax einer API-Oberfläche mag zwar offensichtlich erscheinen, aber neue Clients verstehen die Semantik, das Verhalten oder den Kontext der API nicht.

Alle generierten APIs müssen den Richtlinien entsprechen.

Von Tools generierte APIs müssen denselben API-Richtlinien wie handgeschriebener Code entsprechen.

Von der Verwendung der folgenden Tools für die Generierung von APIs wird abgeraten:

  • AutoValue: Verstößt auf verschiedene Weise gegen die Richtlinien. Beispielsweise gibt es keine Möglichkeit, Klassen mit endgültigen Werten oder endgültige Builder mit der Funktionsweise von AutoValue zu implementieren.

Codestil

Diese Kategorie bezieht sich auf den allgemeinen Codestil, den Entwickler verwenden sollten, insbesondere beim Schreiben öffentlicher APIs.

Beachten Sie die standardmäßigen Codierungskonventionen, sofern nicht anders angegeben.

Die Android-Programmierkonventionen für externe Mitwirkende sind hier dokumentiert:

https://source.android.com/source/code-style.html

Im Allgemeinen folgen wir den Standard-Java- und Kotlin-Programmierkonventionen.

Akronyme dürfen in Methodennamen nicht großgeschrieben werden.

Beispiel: Der Methodenname sollte runCtsTests und nicht runCTSTests lauten.

Namen dürfen nicht auf „Impl“ enden.

Dadurch werden Implementierungsdetails offengelegt. Das sollten Sie vermeiden.

Klassen

In diesem Abschnitt werden Regeln zu Klassen, Schnittstellen und Vererbung beschrieben.

Neue öffentliche Klassen von der entsprechenden Basisklasse erben

Durch die Vererbung werden API-Elemente in Ihrer Unterklasse freigegeben, die möglicherweise nicht geeignet sind. Eine neue öffentliche Unterklasse von FrameLayout sieht beispielsweise aus wie FrameLayout, aber mit den neuen Verhaltensweisen und API-Elementen. Wenn diese übergeordnete API für Ihren Anwendungsfall nicht geeignet ist, können Sie von einer Klasse weiter oben im Stammbaum ableiten, z. B. ViewGroup oder View.

Wenn Sie versucht sind, Methoden aus der Basisklasse zu überschreiben, um UnsupportedOperationException zu werfen, sollten Sie die verwendete Basisklasse noch einmal überdenken.

Basissammlungsklassen verwenden

Verwenden Sie immer die Basisklasse anstelle der spezifischen Implementierung, wenn Sie eine Sammlung als Argument übergeben oder als Wert zurückgeben (z. B. List<Foo> statt ArrayList<Foo> zurückgeben).

Verwenden Sie eine Basisklasse, die die entsprechenden Einschränkungen für die API enthält. Verwenden Sie beispielsweise List für eine API, deren Sammlung sortiert werden muss, und Set für eine API, deren Sammlung aus eindeutigen Elementen bestehen muss.

Verwenden Sie in Kotlin unveränderliche Sammlungen. Weitere Informationen finden Sie unter Veränderbarkeit von Sammlungen.

Abstrakte Klassen und Schnittstellen im Vergleich

Java 8 unterstützt Standardmethoden für Schnittstellen. So können API-Entwickler Schnittstellen Methoden hinzufügen und gleichzeitig die Binärkompatibilität beibehalten. Plattformcode und alle Jetpack-Bibliotheken sollten auf Java 8 oder höher ausgerichtet sein.

Wenn die Standardimplementierung zustandslos ist, sollten API-Designer Schnittstellen abstrakten Klassen vorziehen. Das bedeutet, dass Standardschnittstellenmethoden als Aufrufe anderer Schnittstellenmethoden implementiert werden können.

Wenn für die Standardimplementierung ein Konstruktor oder ein interner Zustand erforderlich ist, müssen abstrakte Klassen verwendet werden.

In beiden Fällen können API-Designer eine einzelne Methode abstrakt lassen, um die Verwendung als Lambda zu vereinfachen:

public interface AnimationEndCallback {
  // Always called, must be implemented.
  public void onFinished(Animation anim);
  // Optional callbacks.
  public default void onStopped(Animation anim) { }
  public default void onCanceled(Animation anim) { }
}

Klassennamen sollten widerspiegeln, was sie erweitern

Klassen, die Service erweitern, sollten beispielsweise FooService heißen, um für Klarheit zu sorgen:

public class IntentHelper extends Service {}
public class IntentService extends Service {}

Generische Suffixe

Verwenden Sie keine generischen Klassennamenendungen wie Helper und Util für Sammlungen von Dienstmethoden. Platzieren Sie die Methoden stattdessen direkt in den zugehörigen Klassen oder in Kotlin-Erweiterungsfunktionen.

Wenn Methoden mehrere Klassen verbinden, geben Sie der enthaltenden Klasse einen aussagekräftigen Namen, der ihre Funktion beschreibt.

In sehr begrenzten Fällen kann das Suffix Helper geeignet sein:

  • Wird für die Zusammensetzung des Standardverhaltens verwendet
  • Kann die Delegierung bestehender Verhaltensweisen an neue Klassen beinhalten
  • Möglicherweise ist ein dauerhafter Status erforderlich.
  • Umfasst in der Regel View

Wenn für das Backporten von Kurzinfos beispielsweise der mit einer View verknüpfte Status beibehalten und mehrere Methoden auf der View aufgerufen werden müssen, um den Backport zu installieren, wäre TooltipHelper ein zulässiger Klassenname.

Stellen Sie IDL-generierten Code nicht direkt als öffentliche APIs bereit.

Behalten Sie den von IDL generierten Code als Implementierungsdetails. Dazu gehören Protobuf, Sockets, FlatBuffers oder andere nicht-Java- und nicht-NDK-API-Oberflächen. Die meisten IDL-Dateien in Android sind jedoch in AIDL geschrieben. Daher liegt der Schwerpunkt dieser Seite auf AIDL.

Die generierten AIDL-Klassen erfüllen nicht die Anforderungen des API-Styleguides (z. B. kann keine Überladung verwendet werden) und das AIDL-Tool ist nicht explizit für die Kompatibilität mit der Sprach-API konzipiert. Sie können sie also nicht in eine öffentliche API einbetten.

Fügen Sie stattdessen eine öffentliche API-Ebene über der AIDL-Schnittstelle hinzu, auch wenn es sich anfangs um einen einfachen Wrapper handelt.

Binder-Schnittstellen

Wenn die Binder-Schnittstelle ein Implementierungsdetail ist, kann sie in Zukunft frei geändert werden. Die öffentliche Schicht ermöglicht die erforderliche Abwärtskompatibilität. Möglicherweise müssen Sie den internen Aufrufen beispielsweise neue Argumente hinzufügen oder den IPC-Traffic durch Batching oder Streaming, durch gemeinsamen Speicher oder ähnliches optimieren. Keines dieser Dinge ist möglich, wenn Ihre AIDL-Schnittstelle auch die öffentliche API ist.

Stellen Sie FooService beispielsweise nicht direkt als öffentliche API bereit:

// BAD: Public API generated from IFooService.aidl
public class IFooService {
   public void doFoo(String foo);
}

Wickeln Sie die Binder-Schnittstelle stattdessen in einen Manager oder eine andere Klasse ein:

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo);
}

public IFooManager {
   public void doFoo(String foo) {
      mFooService.doFoo(foo);
   }
}

Wenn später ein neues Argument für diesen Aufruf erforderlich ist, kann die interne Schnittstelle minimiert und der öffentlichen API praktische Überladungen hinzugefügt werden. Sie können die Ummantelungsebene auch verwenden, um andere Probleme mit der Abwärtskompatibilität zu beheben, die sich im Laufe der Implementierung ergeben:

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo, int flags);
}

public IFooManager {
   public void doFoo(String foo) {
      if (mAppTargetSdkLevel < 26) {
         useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
         mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
      } else {
         mFooService.doFoo(foo, 0);
      }
   }

   public void doFoo(String foo, int flags) {
      mFooService.doFoo(foo, flags);
   }
}

Bei Binder-Schnittstellen, die nicht Teil der Android-Plattform sind (z. B. eine Dienstschnittstelle, die von Google Play-Diensten für Apps exportiert wird), ist es aufgrund der Anforderung an eine stabile, veröffentlichte und versionierte IPC-Schnittstelle viel schwieriger, die Schnittstelle selbst weiterzuentwickeln. Es ist jedoch sinnvoll, eine Wrapper-Ebene um sie herum zu haben, um anderen API-Richtlinien zu entsprechen und die Verwendung derselben öffentlichen API für eine neue Version der IPC-Schnittstelle zu erleichtern, falls dies erforderlich wird.

Keine Roh-Binder-Objekte in der öffentlichen API verwenden

Ein Binder-Objekt hat für sich genommen keine Bedeutung und sollte daher nicht in der öffentlichen API verwendet werden. Ein häufiger Anwendungsfall ist die Verwendung von Binder oder IBinder als Token, da sie Identitätssemantik haben. Verwende stattdessen eine Wrapper-Token-Klasse, anstatt ein rohes Binder-Objekt zu verwenden.

public final class IdentifiableObject {
  public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
  /**
   * @hide
   */
  public Binder getRawValue() {...}

  /**
   * @hide
   */
  public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}

public final class IdentifiableObject {
  public IdentifiableObjectToken getToken() {...}
}

Managerklassen müssen final sein

Managerklassen sollten als final deklariert werden. Managerklassen kommunizieren mit Systemdiensten und sind der zentrale Interaktionspunkt. Eine Anpassung ist nicht erforderlich. Geben Sie daher final an.

Verwenden Sie keine CompletableFuture- oder Future-Objekte.

java.util.concurrent.CompletableFuture hat eine große API-Oberfläche, die eine beliebige Änderung des Werts in der Zukunft ermöglicht, und fehleranfällige Standardeinstellungen.

Umgekehrt fehlt java.util.concurrent.Future die nicht blockierende Überwachung, was die Verwendung mit asynchronem Code erschwert.

Verwenden Sie in Plattformcode und Low-Level-Bibliotheks-APIs, die sowohl von Kotlin als auch von Java verwendet werden, eine Kombination aus einem Abschluss-Callback, Executor, und, sofern die API die Stornierung unterstützt, CancellationSignal.

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor,
    android.os.OutcomeReceiver<FooResult, Throwable> callback);

Wenn Sie Ihre App auf Kotlin ausrichten, sollten Sie suspend-Funktionen verwenden.

suspend fun asyncLoadFoo(): Foo

In Java-spezifischen Integrationsbibliotheken können Sie die ListenableFuture von Guava verwenden.

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

Verwenden Sie nicht „Optional“.

Optional kann zwar auf einigen API-Oberflächen Vorteile haben, ist aber nicht mit der vorhandenen Android API-Oberfläche kompatibel. @Nullable und @NonNull bieten Toolunterstützung für die null-Sicherheit und Kotlin erzwingt Nullbarkeitsverträge auf Compilerebene, wodurch Optional unnötig wird.

Verwenden Sie für optionale Primitive die gekoppelten Methoden has und get. Wenn der Wert nicht festgelegt ist (has gibt false zurück), sollte die get-Methode eine IllegalStateException auslösen.

public boolean hasAzimuth() { ... }
public int getAzimuth() {
  if (!hasAzimuth()) {
    throw new IllegalStateException("azimuth is not set");
  }
  return azimuth;
}

Private Konstruktoren für nicht instantiierbare Klassen verwenden

Klassen, die nur von Builders erstellt werden können, Klassen, die nur Konstanten oder statische Methoden enthalten, oder Klassen, die anderweitig nicht instantiiert werden können, sollten mindestens einen privaten Konstruktor enthalten, um die Instanziierung mit dem standardmäßigen Konstruktor ohne Argumente zu verhindern.

public final class Log {
  // Not instantiable.
  private Log() {}
}

Singletons

Singletons werden nicht empfohlen, da sie die folgenden testbezogenen Nachteile haben:

  1. Die Erstellung wird von der Klasse verwaltet, um die Verwendung von gefälschten Daten zu verhindern.
  2. Tests können aufgrund der statischen Natur eines Singletons nicht hermetisch sein
  3. Um diese Probleme zu umgehen, müssen Entwickler entweder die internen Details des Singletons kennen oder einen Wrapper darum erstellen.

Verwenden Sie das Muster der einzelnen Instanz, das auf einer abstrakten Basisklasse basiert, um diese Probleme zu beheben.

Einzelne Instanz

Klassen mit einer einzelnen Instanz verwenden eine abstrakte Basisklasse mit einem private- oder internal-Konstruktor und stellen eine statische getInstance()-Methode bereit, um eine Instanz abzurufen. Die getInstance()-Methode muss bei nachfolgenden Aufrufen dasselbe Objekt zurückgeben.

Das von getInstance() zurückgegebene Objekt sollte eine private Implementierung der abstrakten Basisklasse sein.

class Singleton private constructor(...) {
  companion object {
    private val _instance: Singleton by lazy { Singleton(...) }

    fun getInstance(): Singleton {
      return _instance
    }
  }
}
abstract class SingleInstance private constructor(...) {
  companion object {
    private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
    fun getInstance(): SingleInstance {
      return _instance
    }
  }
}

Eine einzelne Instanz unterscheidet sich von einem Singleton dadurch, dass Entwickler eine gefälschte Version von SingleInstance erstellen und ihr eigenes Dependency Injection-Framework verwenden können, um die Implementierung zu verwalten, ohne einen Wrapper erstellen zu müssen. Alternativ kann die Bibliothek auch einen eigenen Fake in einem -testing-Artefakt bereitstellen.

Klassen, die Ressourcen freigeben, sollten AutoCloseable implementieren.

Klassen, die Ressourcen über close, release, destroy oder ähnliche Methoden freigeben, sollten java.lang.AutoCloseable implementieren, damit Entwickler diese Ressourcen automatisch bereinigen können, wenn sie einen try-with-resources-Block verwenden.

Vermeiden Sie die Einführung neuer View-Unterklassen in Android.*

Füge der öffentlichen API der Plattform (android.*) keine neuen Klassen hinzu, die direkt oder indirekt von android.view.View abgeleitet sind.

Das Android-UI-Toolkit ist jetzt Compose-first. Neue von der Plattform bereitgestellte UI-Funktionen sollten als APIs niedrigerer Ebene bereitgestellt werden, mit denen Entwickler Jetpack Compose und optional datenbankbasierte UI-Komponenten in Jetpack-Bibliotheken implementieren können. Wenn Sie diese Komponenten in Bibliotheken anbieten, können Sie Backport-Implementierungen verwenden, wenn Plattformfunktionen nicht verfügbar sind.

Felder

Diese Regeln beziehen sich auf öffentliche Felder in Klassen.

Rohfelder nicht freigeben

Java-Klassen sollten keine Felder direkt freigeben. Felder sollten privat sein und nur über öffentliche Getter und Setter zugänglich sein, unabhängig davon, ob diese Felder final sind oder nicht.

Seltene Ausnahmen sind einfache Datenstrukturen, bei denen das Verhalten beim Angeben oder Abrufen eines Felds nicht verbessert werden muss. In solchen Fällen sollten die Felder gemäß den Standardkonventionen für Variablennamen benannt werden, z. B. Point.x und Point.y.

Kotlin-Klassen können Eigenschaften freigeben.

Freigegebene Felder sollten als „Endgültig“ gekennzeichnet sein.

Von der Verwendung von Rohdatenfeldern wird dringend abgeraten (siehe Rohdatenfelder nicht freigeben). In seltenen Fällen, in denen ein Feld als öffentliches Feld freigegeben wird, markieren Sie es mit final.

Interne Felder sollten nicht freigegeben werden.

Verweisen Sie nicht auf interne Feldnamen in der öffentlichen API.

public int mFlags;

„Öffentlich“ anstelle von „Geschützt“ verwenden

@see Öffentliche statt geschützte Streams verwenden

Konstanten

Dies sind Regeln für öffentliche Konstanten.

Flagkonstanten dürfen sich nicht mit Int- oder Long-Werten überschneiden.

Flags sind Bits, die zu einem Union-Wert kombiniert werden können. Andernfalls sollten Sie die Variable oder Konstante nicht flag nennen.

public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;

Weitere Informationen zum Definieren öffentlicher Flagkonstanten finden Sie unter @IntDef für Bitmasken-Flags.

Für statische finale Konstanten sollte die Namenskonvention in Großbuchstaben mit Unterstrichen verwendet werden.

Alle Wörter in der Konstante müssen großgeschrieben werden und mehrere Wörter müssen durch _ getrennt werden. Beispiel:

public static final int fooThing = 5
public static final int FOO_THING = 5

Standardpräfixe für Konstanten verwenden

Viele der in Android verwendeten Konstanten beziehen sich auf Standardelemente wie Flags, Schlüssel und Aktionen. Diese Konstanten sollten Standardpräfixe haben, damit sie leichter als solche erkannt werden.

Intent-Extras sollten beispielsweise mit EXTRA_ beginnen. Intent-Aktionen müssen mit ACTION_ beginnen. Konstanten, die mit Context.bindService() verwendet werden, sollten mit BIND_ beginnen.

Namen und Bereiche von Schlüsselkonstanten

Die Werte von Stringkonstanten sollten mit dem Namen der Konstante übereinstimmen und sollten im Allgemeinen auf das Paket oder die Domain beschränkt sein. Beispiel:

public static final String FOO_THING = "foo"

weder einheitlich benannt noch angemessen eingegrenzt ist. Stattdessen sollten Sie Folgendes beachten:

public static final String FOO_THING = "android.fooservice.FOO_THING"

Präfixe von android in Stringkonstanten mit Bereichsbeschränkung sind für das Android Open Source Project reserviert.

Intent-Aktionen und Extras sowie Bundle-Einträge sollten den Namen des Pakets haben, in dem sie definiert sind.

package android.foo.bar {
  public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
  public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}

„Öffentlich“ anstelle von „Geschützt“ verwenden

@see Öffentliche statt geschützte Streams verwenden

Verwenden Sie einheitliche Präfixe.

Verwandte Konstanten sollten alle mit demselben Präfix beginnen. Beispiel für eine Reihe von Konstanten, die mit Flag-Werten verwendet werden sollen:

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see Standardpräfixe für Konstanten verwenden

Konsistente Ressourcennamen verwenden

Öffentliche IDs, Attribute und Werte müssen gemäß der CamelCase-Namenskonvention benannt werden, z. B. @id/accessibilityActionPageUp oder @attr/textAppearance, ähnlich wie öffentliche Felder in Java.

In einigen Fällen enthält eine öffentliche Kennung oder ein öffentliches Attribut ein gemeinsames Präfix, das durch einen Unterstrich getrennt ist:

  • Plattformkonfigurationswerte wie @string/config_recentsComponentName in config.xml
  • Layoutspezifische Ansichtsattribute wie @attr/layout_marginStart in attrs.xml

Öffentliche Themen und Stile müssen der hierarchischen PascalCase-Benennungskonvention folgen, z. B. @style/Theme.Material.Light.DarkActionBar oder @style/Widget.Material.SearchView.ActionBar, ähnlich wie verschachtelte Klassen in Java.

Layout- und drawable-Ressourcen sollten nicht als öffentliche APIs freigegeben werden. Wenn sie jedoch freigegeben werden müssen, müssen öffentliche Layouts und Drawables gemäß der Namenskonvention „unterstrichen“ benannt werden, z. B. layout/simple_list_item_1.xml oder drawable/title_bar_tall.xml.

Konstanten, die sich ändern können, dynamisch gestalten

Der Compiler kann Konstantenwerte inline einfügen. Daher gilt es als Teil des API-Vertrags, die Werte unverändert zu lassen. Wenn sich der Wert einer MIN_FOO- oder MAX_FOO-Konstante in Zukunft ändern könnte, sollten Sie stattdessen dynamische Methoden verwenden.

CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()

Berücksichtigen Sie die Zukunftssicherheit für Callbacks

In zukünftigen API-Versionen definierte Konstanten sind für Apps, die auf ältere APIs ausgerichtet sind, nicht bekannt. Aus diesem Grund sollten an Apps übermittelte Konstanten die Ziel-API-Version der App berücksichtigen und neuere Konstanten einem einheitlichen Wert zuordnen. Stellen Sie sich folgendes Szenario vor:

Hypothetische SDK-Quelle:

// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;

Hypothetische App mit targetSdkVersion="22":

if (result == STATUS_FAILURE) {
  // Oh no!
} else {
  // Success!
}

In diesem Fall wurde die App unter Berücksichtigung der Einschränkungen von API-Level 22 entwickelt und es wurde davon ausgegangen, dass es nur zwei mögliche Status gibt. Wenn die App jedoch die neu hinzugefügte STATUS_FAILURE_RETRY empfängt, wird dies als Erfolg interpretiert.

Bei Methoden, die Konstanten zurückgeben, können solche Fälle sicher behandelt werden, indem die Ausgabe auf das API-Level beschränkt wird, auf das die App ausgerichtet ist:

private int mapResultForTargetSdk(Context context, int result) {
  int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  if (targetSdkVersion < 26) {
    if (result == STATUS_FAILURE_ABORT) {
      return STATUS_FAILURE;
    }
    if (targetSdkVersion < 23) {
      if (result == STATUS_FAILURE_RETRY) {
        return STATUS_FAILURE;
      }
    }
  }
  return result;
}

Entwickler können nicht vorhersehen, ob sich eine Liste von Konstanten in Zukunft ändern könnte. Wenn Sie eine API mit einer UNKNOWN- oder UNSPECIFIED-Konstante definieren, die wie ein Allrounder aussieht, gehen Entwickler davon aus, dass die veröffentlichten Konstanten beim Erstellen ihrer App vollständig sind. Wenn Sie diese Erwartung nicht festlegen möchten, sollten Sie noch einmal darüber nachdenken, ob eine Allzweckkonstante für Ihre API sinnvoll ist.

Außerdem können Bibliotheken keine eigene targetSdkVersion getrennt von der App angeben. Die Verarbeitung von targetSdkVersion-Verhaltensänderungen aus Bibliothekscode ist kompliziert und fehleranfällig.

Ganzzahl oder Stringkonstante

Verwenden Sie Ganzzahlkonstanten und @IntDef, wenn der Namespace für Werte nicht außerhalb Ihres Pakets erweitert werden kann. Verwenden Sie Stringkonstanten, wenn der Namespace gemeinsam genutzt wird oder durch Code außerhalb Ihres Pakets erweitert werden kann.

Datenklassen

Datenklassen stellen eine Reihe unveränderlicher Eigenschaften dar und bieten eine kleine und gut definierte Reihe von Dienstprogrammfunktionen zur Interaktion mit diesen Daten.

Verwenden Sie data class nicht in öffentlichen Kotlin-APIs, da der Kotlin-Compiler keine Sprach-API- oder Binärkompatibilität für generierten Code garantiert. Implementieren Sie stattdessen die erforderlichen Funktionen manuell.

Instanziierung

In Java sollten Datenklassen einen Konstruktor bereitstellen, wenn es nur wenige Eigenschaften gibt, oder das Builder-Muster verwenden, wenn es viele Eigenschaften gibt.

In Kotlin sollten Datenklassen unabhängig von der Anzahl der Eigenschaften einen Konstruktor mit Standardargumenten bereitstellen. Bei in Kotlin definierten Datenklassen kann es auch von Vorteil sein, einen Builder bereitzustellen, wenn Sie Java-Clients anvisieren.

Änderungen und Kopieren

Wenn Daten geändert werden müssen, geben Sie entweder eine Builder-Klasse mit einem Kopierkonstruktor (Java) oder eine copy()-Mitgliedsfunktion (Kotlin) an, die ein neues Objekt zurückgibt.

Wenn Sie in Kotlin eine copy()-Funktion angeben, müssen die Argumente mit dem Konstruktor der Klasse übereinstimmen und die Standardwerte müssen mit den aktuellen Werten des Objekts ausgefüllt werden:

class Typography(
  val labelMedium: TextStyle = TypographyTokens.LabelMedium,
  val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
    fun copy(
      labelMedium: TextStyle = this.labelMedium,
      labelSmall: TextStyle = this.labelSmall
    ): Typography = Typography(
      labelMedium = labelMedium,
      labelSmall = labelSmall
    )
}

Zusätzliche Verhaltensweisen

Datenklassen sollten sowohl equals() als auch hashCode() implementieren. Bei der Implementierung dieser Methoden muss jede Property berücksichtigt werden.

Datenklassen können toString() in einem empfohlenen Format implementieren, das der Implementierung der Datenklasse in Kotlin entspricht, z. B. User(var1=Alex, var2=42).

Methoden

Dies sind Regeln zu verschiedenen Details in Methoden, zu Parametern, Methodennamen, Rückgabetypen und Zugriffsspezifizierern.

Uhrzeit

Diese Regeln decken ab, wie Zeitkonzepte wie Datumsangaben und Zeiträume in APIs ausgedrückt werden sollten.

Verwenden Sie nach Möglichkeit java.time.*-Typen.

java.time.Duration, java.time.Instant und viele andere java.time.*-Typen sind auf allen Plattformversionen durch Entfernen von Zucker verfügbar und sollten bevorzugt verwendet werden, wenn Zeit in API-Parametern oder Rückgabewerten angegeben wird.

Stellen Sie vorzugsweise nur Varianten einer API bereit, die java.time.Duration oder java.time.Instant akzeptieren oder zurückgeben, und lassen Sie primitive Varianten mit demselben Anwendungsfall aus, es sei denn, die API-Domain ist eine, bei der die Objektzuweisung bei den beabsichtigten Nutzungsmustern eine unzumutbare Leistungsbeeinträchtigung zur Folge hätte.

Methoden, die Zeiträume ausdrücken, sollten den Namen „duration“ haben.

Wenn ein Zeitwert die Dauer der Zeit angibt, nennen Sie den Parameter „duration“ (Dauer), nicht „time“ (Uhrzeit).

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

Ausnahmen:

„timeout“ ist geeignet, wenn sich die Dauer speziell auf einen Zeitüberschreitungswert bezieht.

„time“ mit dem Typ java.time.Instant ist geeignet, wenn es sich um einen bestimmten Zeitpunkt handelt, nicht um eine Dauer.

Methoden, die Zeiträume oder Zeit als Primitive ausdrücken, sollten mit ihrer Zeiteinheit benannt werden und „long“ verwenden.

Bei Methoden, die Zeiträume als primitiven Typ akzeptieren oder zurückgeben, sollte der Methodenname mit den zugehörigen Zeiteinheiten (z. B. Millis, Nanos, Seconds) enden, um den unverzierten Namen für die Verwendung mit java.time.Duration zu reservieren. Siehe Uhrzeit.

Methoden sollten auch mit ihrer Einheit und Zeitbasis gekennzeichnet sein:

  • @CurrentTimeMillisLong: Der Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Millisekunden seit dem 01.01.1970 00:00:00 UTC.
  • @CurrentTimeSecondsLong: Der Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Sekunden seit dem 01.01.1970 00:00:00 UTC.
  • @DurationMillisLong: Der Wert ist eine nicht negative Dauer in Millisekunden.
  • @ElapsedRealtimeLong: Der Wert ist ein nicht negativer Zeitstempel in der Zeitbasis SystemClock.elapsedRealtime().
  • @UptimeMillisLong: Der Wert ist ein positiver Zeitstempel in der Zeitbasis SystemClock.uptimeMillis().

Für primitive Zeitparameter oder Rückgabewerte sollte long und nicht int verwendet werden.

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

Bei Methoden, die Zeiteinheiten ausdrücken, sollte die Langform der Einheit verwendet werden.

public void setIntervalNs(long intervalNs);

public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);

public void setTimeoutMicros(long timeoutMicros);

Argumente für lange Zeiträume annotieren

Die Plattform enthält mehrere Anmerkungen, um die Typisierung von Zeiteinheiten vom Typ long zu verbessern:

  • @CurrentTimeMillisLong: Der Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Millisekunden seit 1970-01-01T00:00:00Z, also in der Zeitbasis System.currentTimeMillis().
  • @CurrentTimeSecondsLong: Wert ist ein nicht negativer Zeitstempel, gemessen als Anzahl der Sekunden seit 1970-01-01T00:00:00Z.
  • @DurationMillisLong: Der Wert ist eine nicht negative Dauer in Millisekunden.
  • @ElapsedRealtimeLong: Der Wert ist ein nicht negativer Zeitstempel in der Zeitbasis SystemClock#elapsedRealtime().
  • @UptimeMillisLong: Der Wert ist ein nicht negativer Zeitstempel in der Zeitbasis SystemClock#uptimeMillis().

Maßeinheiten

Verwenden Sie für alle Methoden, die eine andere Maßeinheit als Zeit ausdrücken, vorzugsweise SI-Einheiten mit Bindestrichschreibweise.

public  long[] getFrequenciesKhz();

public  float getStreamVolumeDb();

Optionale Parameter am Ende von Überladungen platzieren

Wenn Sie Überladungen einer Methode mit optionalen Parametern haben, sollten Sie diese Parameter am Ende und in der gleichen Reihenfolge wie die anderen Parameter angeben:

public int doFoo(boolean flag);

public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);

public int doFoo(boolean flag, int id);

Wenn Sie Überladungen für optionale Argumente hinzufügen, sollte sich das Verhalten der einfacheren Methoden genau so verhalten, als wären den komplexeren Methoden Standardargumente angegeben worden.

Folgerung: Überladen Sie Methoden nur, um optionale Argumente hinzuzufügen oder verschiedene Arten von Argumenten zu akzeptieren, wenn die Methode polymorph ist. Wenn die überladene Methode etwas grundlegend anderes tut, geben Sie ihr einen neuen Namen.

Methoden mit Standardparametern müssen mit @JvmOverloads annotiert sein (nur Kotlin)

Methoden und Konstruktoren mit Standardparametern müssen mit @JvmOverloads annotiert werden, um die Binärkompatibilität beizubehalten.

Weitere Informationen finden Sie im offiziellen Leitfaden zur Kotlin-Java-Interoperabilität unter Funktionsüberladungen für Standardwerte.

class Greeting @JvmOverloads constructor(
  loudness: Int = 5
) {
  @JvmOverloads
  fun sayHello(prefix: String = "Dr.", name: String) = // ...
}

Standardparameterwerte nicht entfernen (nur Kotlin)

Wenn eine Methode mit einem Parameter mit einem Standardwert ausgeliefert wurde, ist das Entfernen des Standardwerts eine änderung, die die Funktionsweise der Quelle beeinträchtigt.

Die prägnantesten und aussagekräftigsten Methodenparameter sollten zuerst stehen.

Wenn eine Methode mehrere Parameter hat, sollten Sie die relevantesten zuerst nennen. Parameter, die Flags und andere Optionen angeben, sind weniger wichtig als diejenigen, die das Objekt beschreiben, auf das eine Aktion angewendet wird. Wenn es einen Abschluss-Callback gibt, platzieren Sie ihn an letzter Stelle.

public void openFile(int flags, String name);

public void openFileAsync(OnFileOpenedListener listener, String name, int flags);

public void setFlags(int mask, int flags);
public void openFile(String name, int flags);

public void openFileAsync(String name, int flags, OnFileOpenedListener listener);

public void setFlags(int flags, int mask);

Siehe auch: Optionale Parameter in Überladungen am Ende platzieren

Builders

Das Builder-Muster wird für die Erstellung komplexer Java-Objekte empfohlen und wird in Android häufig in folgenden Fällen verwendet:

  • Die Eigenschaften des resultierenden Objekts sollten unveränderlich sein.
  • Es gibt eine große Anzahl erforderlicher Eigenschaften, z. B. viele Konstruktorargumente.
  • Zwischen den Properties besteht beim Erstellen eine komplexe Beziehung, z. B. ist ein Bestätigungsschritt erforderlich. Diese Komplexität weist oft auf Probleme mit der Nutzerfreundlichkeit der API hin.

Überlegen Sie, ob Sie einen Builder benötigen. Builder sind in einer API-Oberfläche nützlich, wenn sie für Folgendes verwendet werden:

  • Nur einige der potenziell vielen optionalen Erstellungsparameter konfigurieren
  • Viele verschiedene optionale oder erforderliche Erstellungsparameter konfigurieren, manchmal von ähnlichen oder übereinstimmenden Typen, bei denen Aufruf-Websites sonst schwer zu lesen oder fehleranfällig beim Schreiben werden könnten
  • Sie können die Erstellung eines Objekts inkrementell konfigurieren, wobei mehrere verschiedene Konfigurationscode-Abschnitte jeweils den Builder als Implementierungsdetails aufrufen.
  • Einen Typ erweitern, indem in zukünftigen API-Versionen zusätzliche optionale Erstellungsparameter hinzugefügt werden

Wenn Sie einen Typ mit drei oder weniger erforderlichen Parametern und keinen optionalen Parametern haben, können Sie fast immer einen Builder überspringen und einen einfachen Konstruktor verwenden.

Bei Kotlin-Klassen sollten Sie @JvmOverloads-annotierte Konstruktoren mit Standardargumenten gegenüber Builders bevorzugen. Sie können jedoch die Nutzerfreundlichkeit für Java-Clients verbessern, indem Sie in den oben beschriebenen Fällen auch Builders bereitstellen.

class Tone @JvmOverloads constructor(
  val duration: Long = 1000,
  val frequency: Int = 2600,
  val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
  class Builder {
    // ...
  }
}

Builder-Klassen müssen den Builder zurückgeben

Builder-Klassen müssen die Methodenabfolge ermöglichen, indem sie das Builder-Objekt (z. B. this) von jeder Methode mit Ausnahme von build() zurückgeben. Zusätzliche erstellte Objekte sollten als Argumente übergeben werden. Geben Sie nicht den Konstruktor eines anderen Objekts zurück. Beispiel:

public static class Builder {
  public void setDuration(long);
  public void setFrequency(int);
  public DtmfConfigBuilder addDtmfConfig();
  public Tone build();
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}

In seltenen Fällen, in denen eine Basis-Builder-Klasse die Erweiterung unterstützen muss, verwenden Sie einen generischen Rückgabetyp:

public abstract class Builder<T extends Builder<T>> {
  abstract T setValue(int);
}

public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
  T setValue(int);
  T setTypeSpecificValue(long);
}

Builder-Klassen müssen über einen Konstruktor erstellt werden.

Damit die Erstellung von Buildern über die Android API-Oberfläche einheitlich erfolgt, müssen alle Builder über einen Konstruktor und nicht über eine statische Erstellungsmethode erstellt werden. Bei Kotlin-basierten APIs muss Builder öffentlich sein, auch wenn Kotlin-Nutzer den Builder implizit über einen Erstellungsmechanismus im Stil einer Fabrikmethode/DSL verwenden sollen. In Bibliotheken darf @PublishedApi internal nicht verwendet werden, um den Builder-Klassenkonstruktor selektiv vor Kotlin-Clients auszublenden.

public class Tone {
  public static Builder builder();
  public static class Builder {
  }
}
public class Tone {
  public static class Builder {
    public Builder();
  }
}

Alle Argumente für Builder-Konstruktoren müssen erforderlich sein (z. B. @NonNull).

Optionale Argumente wie @Nullable sollten in Setzermethoden verschoben werden. Der Builder-Konstruktor sollte eine NullPointerException auswerfen (Objects.requireNonNull wird empfohlen), wenn erforderliche Argumente nicht angegeben sind.

Builder-Klassen sollten finale statische innere Klassen ihrer erstellten Typen sein.

Aus Gründen der logischen Organisation innerhalb eines Pakets sollten Builder-Klassen in der Regel als finale innere Klassen ihrer erstellten Typen freigegeben werden, z. B. Tone.Builder anstelle von ToneBuilder.

Erbauer können einen Konstruktor enthalten, um eine neue Instanz aus einer vorhandenen Instanz zu erstellen.

Erbauer können einen Kopierkonstruktor enthalten, um eine neue Erbauerinstanz aus einem vorhandenen Erbauer oder einem erstellten Objekt zu erstellen. Sie sollten keine alternativen Methoden zum Erstellen von Builder-Instanzen aus vorhandenen Buildern oder Build-Objekten bereitstellen.

public class Tone {
  public static class Builder {
    public Builder clone();
  }

  public Builder toBuilder();
}
public class Tone {
  public static class Builder {
    public Builder(Builder original);
    public Builder(Tone original);
  }
}
Builder-Setter sollten @Nullable-Argumente annehmen, wenn der Builder einen Kopierkonstruktor hat.

Das Zurücksetzen ist unerlässlich, wenn eine neue Instanz eines Builders aus einer vorhandenen Instanz erstellt werden kann. Wenn kein Kopierkonstruktor verfügbar ist, kann der Builder entweder @Nullable- oder @NonNullable-Argumente haben.

public static class Builder {
  public Builder(Builder original);
  public Builder setObjectValue(@Nullable Object value);
}
Builder-Setter können @Nullable-Argumente für optionale Properties annehmen

Es ist oft einfacher, einen Nullwert für Eingaben vom zweiten Grad zu verwenden, insbesondere in Kotlin, wo Standardargumente anstelle von Buildern und Überladungen verwendet werden.

Außerdem werden @Nullable-Setter mit ihren Gettern abgeglichen, die für optionale Properties @Nullable sein müssen.

Value createValue(@Nullable OptionalValue optionalValue) {
  Value.Builder builder = new Value.Builder();
  if (optionalValue != null) {
    builder.setOptionalValue(optionalValue);
  }
  return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
  return new Value.Builder()
    .setOptionalValue(optionalValue);
    .build();
}

// Or in other cases:

Value createValue() {
  return new Value.Builder()
    .setOptionalValue(condition ? new OptionalValue() : null);
    .build();
}

Gängige Verwendung in Kotlin:

fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .apply { optionalValue?.let { setOptionalValue(it) } }
    .build()
fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .setOptionalValue(optionalValue)
    .build()

Der Standardwert (falls der Setter nicht aufgerufen wird) und die Bedeutung von null müssen sowohl im Setter als auch im Getter korrekt dokumentiert sein.

/**
 * ...
 *
 * <p>Defaults to {@code null}, which means the optional value won't be used.
 */

Für veränderliche Eigenschaften können Builder-Setter bereitgestellt werden, wenn Setter in der erstellten Klasse verfügbar sind.

Wenn Ihre Klasse mutable Properties hat und eine Builder-Klasse benötigt, sollten Sie sich zuerst fragen, ob Ihre Klasse tatsächlich mutable Properties haben sollte.

Wenn Sie sicher sind, dass Sie veränderbare Properties benötigen, entscheiden Sie als Nächstes, welches der folgenden Szenarien für Ihren erwarteten Anwendungsfall besser geeignet ist:

  1. Das erstellte Objekt sollte sofort verwendet werden können. Daher sollten für alle relevanten Eigenschaften Setter bereitgestellt werden, unabhängig davon, ob sie veränderbar oder unveränderlich sind.

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. Möglicherweise müssen einige zusätzliche Aufrufe erfolgen, bevor das erstellte Objekt nützlich ist. Daher sollten für veränderliche Eigenschaften keine Setter bereitgestellt werden.

    Value v = new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .build();
    v.setUsefulMutableProperty(usefulValue)
    Result r = v.performSomeAction();
    Key k = callSomeMethod(r);
    map.put(k, v);
    

Mischen Sie die beiden Szenarien nicht.

Value v = new Value.Builder(requiredValue)
    .setImmutableProperty(immutableValue)
    .setUsefulMutableProperty(usefulValue)
    .build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);

Erbauer sollten keine Getter haben

Getter sollten sich im erstellten Objekt befinden, nicht im Builder.

Die Builder-Setter müssen entsprechende Getter in der erstellten Klasse haben.

public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }

  public long getDuration();
  public int getFrequency();
  public @NonNull List<DtmfConfig> getDtmfConfigs();
}

Benennung der Builder-Methode

Die Namen von Builder-Methoden sollten den Stil setFoo(), addFoo() oder clearFoo() haben.

Builder-Klassen müssen eine build()-Methode deklarieren.

Builder-Klassen sollten eine build()-Methode deklarieren, die eine Instanz des erstellten Objekts zurückgibt.

Builder-build()-Methoden müssen @NonNull-Objekte zurückgeben

Die build()-Methode eines Builders sollte eine Nicht-NULL-Instanz des erstellten Objekts zurückgeben. Falls das Objekt aufgrund ungültiger Parameter nicht erstellt werden kann, kann die Validierung an die Build-Methode übergeben und eine IllegalStateException-Ausnahme ausgelöst werden.

Interne Sperren nicht offenlegen

Das Keyword synchronized darf in Methoden der öffentlichen API nicht verwendet werden. Dieses Keyword bewirkt, dass Ihr Objekt oder Ihre Klasse als Sperre verwendet wird. Da es für andere sichtbar ist, können unerwartete Nebenwirkungen auftreten, wenn anderer Code außerhalb Ihrer Klasse es zu Sperrzwecken verwendet.

Führen Sie stattdessen alle erforderlichen Sperrungen für ein internes, privates Objekt aus.

public synchronized void doThing() { ... }
private final Object mThingLock = new Object();

public void doThing() {
  synchronized (mThingLock) {
    ...
  }
}

Methoden im Accessor-Stil müssen den Kotlin-Richtlinien für Eigenschaften entsprechen

In Kotlin-Quellen sind Methoden im Accessor-Stil, die die Präfixe get, set oder is verwenden, auch als Kotlin-Properties verfügbar. Beispielsweise ist int getField(), das in Java definiert ist, in Kotlin als Property val field: Int verfügbar.

Aus diesem Grund und um die Erwartungen der Entwickler an das Verhalten von Zugriffsmethoden allgemein zu erfüllen, sollten sich Methoden mit Präfixen für Zugriffsmethoden ähnlich wie Java-Felder verhalten. Verwenden Sie keine Präfixe im Stil von Zugriffsfunktionen, wenn:

  • Die Methode hat Nebenwirkungen – verwenden Sie einen aussagekräftigeren Methodennamen.
  • Die Methode ist rechenintensiv – verwenden Sie stattdessen compute.
  • Die Methode umfasst blockierende oder anderweitig lang andauernde Arbeit, um einen Wert zurückzugeben, z. B. IPC oder andere E/A. Verwenden Sie fetch.
  • Die Methode blockiert den Thread, bis ein Wert zurückgegeben werden kann. Verwenden Sie stattdessen await.
  • Die Methode gibt bei jedem Aufruf eine neue Objektinstanz zurück. Verwenden Sie stattdessen create.
  • Die Methode gibt möglicherweise keinen Wert zurück. Verwenden Sie stattdessen request.

Beachten Sie, dass das Ausführen einer rechenintensiven Arbeit einmal und das Caching des Werts für nachfolgende Aufrufe immer noch als rechenintensive Arbeit gilt. Ruckler werden nicht über Frames hinweg abgebaut.

Verwenden Sie das Präfix „is“ für boolesche Zugriffsmethoden.

Das ist die Standardbenennungskonvention für boolesche Methoden und Felder in Java. Im Allgemeinen sollten boolesche Methoden- und Variablennamen als Fragen formuliert werden, die durch den Rückgabewert beantwortet werden.

Java-Boolesche Zugriffsmethoden sollten einem set/is-Namensschema folgen und Felder sollten vorzugsweise is verwenden, z. B.:

// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();

// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();

final boolean isAvailable;

Wenn Sie set/is für Java-Zugriffsmethoden oder is für Java-Felder verwenden, können sie als Eigenschaften aus Kotlin verwendet werden:

obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return

Für Properties und Zugriffsmethoden sollten im Allgemeinen positive Namen verwendet werden, z. B. Enabled anstelle von Disabled. Wenn Sie negative Terminologie verwenden, wird die Bedeutung von true und false umgekehrt und es wird schwieriger, das Verhalten zu analysieren.

// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);

Wenn die Boolesche Variable die Zugehörigkeit oder den Besitz einer Property beschreibt, können Sie anstelle von is auch has verwenden. Dies funktioniert jedoch nicht mit der Kotlin-Property-Syntax:

// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();

Einige alternative Präfixe, die möglicherweise besser geeignet sind, sind kann und sollte:

// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();

// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();

Für Methoden, mit denen Verhaltensweisen oder Funktionen aktiviert oder deaktiviert werden, können das Präfix is und das Suffix Enabled verwendet werden:

// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()

Bei Methoden, die die Abhängigkeit von anderen Verhaltensweisen oder Funktionen angeben, kann das Präfix is und das Suffix Supported oder Required verwendet werden:

// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()

Im Allgemeinen sollten Methodennamen als Fragen formuliert werden, die durch den Rückgabewert beantwortet werden.

Kotlin-Property-Methoden

Für eine Klasseneigenschaft var foo: Foo generiert Kotlin get/set-Methoden mit einer einheitlichen Regel: Vorangestelltes get und Großschreibung des ersten Zeichens für den Getter sowie vorangestelltes set und Großschreibung des ersten Zeichens für den Setter. Die Deklaration der Property führt zu Methoden mit den Namen public Foo getFoo() und public void setFoo(Foo foo).

Wenn die Property vom Typ Boolean ist, gilt bei der Namensgenerierung eine zusätzliche Regel: Wenn der Property-Name mit is beginnt, wird dem Namen der Get-Methode nicht get vorangestellt. Stattdessen wird der Property-Name selbst als Get-Methode verwendet. Benennen Sie Boolean-Properties daher vorzugsweise mit dem Präfix is, um der Benennungsrichtlinie zu folgen:

var isVisible: Boolean

Wenn Ihre Property zu einer der oben genannten Ausnahmen gehört und mit einem geeigneten Präfix beginnt, können Sie den entsprechenden Namen manuell mithilfe der Anmerkung @get:JvmName für die Property angeben:

@get:JvmName("hasTransientState")
var hasTransientState: Boolean

@get:JvmName("canRecord")
var canRecord: Boolean

@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean

Bitmasken-Accessors

API-Richtlinien zum Definieren von Bitmasken-Flags finden Sie unter @IntDef für Bitmasken-Flags verwenden.

Setter

Es sollten zwei Setzermethoden bereitgestellt werden: eine, die einen vollständigen Bitstring annimmt und alle vorhandenen Flags überschreibt, und eine andere, die eine benutzerdefinierte Bitmaske annimmt, um mehr Flexibilität zu ermöglichen.

/**
 * Sets the state of all scroll indicators.
 * <p>
 * See {@link #setScrollIndicators(int, int)} for usage information.
 *
 * @param indicators a bitmask of indicators that should be enabled, or
 *                   {@code 0} to disable all indicators
 * @see #setScrollIndicators(int, int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators);

/**
 * Sets the state of the scroll indicators specified by the mask. To change
 * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
 * <p>
 * When a scroll indicator is enabled, it will be displayed if the view
 * can scroll in the direction of the indicator.
 * <p>
 * Multiple indicator types may be enabled or disabled by passing the
 * logical OR of the specified types. If multiple types are specified, they
 * will all be set to the same enabled state.
 * <p>
 * For example, to enable the top scroll indicator:
 * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
 * <p>
 * To disable the top scroll indicator:
 * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
 *
 * @param indicators a bitmask of values to set; may be a single flag,
 *                   the logical OR of multiple flags, or 0 to clear
 * @param mask a bitmask indicating which indicator flags to modify
 * @see #setScrollIndicators(int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);

Getter

Es sollte ein Getter bereitgestellt werden, um die vollständige Bitmaske abzurufen.

/**
 * Returns a bitmask representing the enabled scroll indicators.
 * <p>
 * For example, if the top and left scroll indicators are enabled and all
 * other indicators are disabled, the return value will be
 * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
 * <p>
 * To check whether the bottom scroll indicator is enabled, use the value
 * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
 *
 * @return a bitmask representing the enabled scroll indicators
 */
@ScrollIndicators
public int getScrollIndicators();

„Öffentlich“ anstelle von „Geschützt“ verwenden

Verwenden Sie in der öffentlichen API immer public anstelle von protected. Der geschützte Zugriff ist auf lange Sicht mühsam, da die Implementierer öffentliche Zugriffsmethoden überschreiben müssen, wenn ein externer Zugriff standardmäßig genauso gut gewesen wäre.

Denken Sie daran, dass die protected Sichtbarkeit nicht verhindert, dass Entwickler eine API aufrufen. Sie macht es nur etwas aufdringlicher.

Weder equals() noch hashCode() implementieren

Wenn Sie eine überschreiben, müssen Sie auch die andere überschreiben.

toString() für Datenklassen implementieren

Bei Datenklassen wird empfohlen, toString() zu überschreiben, um Entwicklern bei der Fehlerbehebung zu helfen.

Dokumentieren Sie, ob die Ausgabe sich auf das Programmverhalten oder das Debuggen bezieht.

Entscheiden Sie, ob das Programmverhalten von Ihrer Implementierung abhängen soll oder nicht. Beispielsweise dokumentieren UUID.toString() und File.toString() ihr spezifisches Format für Programme. Wenn Sie Informationen nur zum Debuggen freigeben, z. B. Intent, sollten Sie angeben, dass Dokumente von der Superklasse übernommen werden.

Fügen Sie keine zusätzlichen Informationen hinzu.

Alle Informationen, die über toString() verfügbar sind, sollten auch über die öffentliche API des Objekts verfügbar sein. Andernfalls werden Entwickler dazu angehalten, Ihre toString()-Ausgabe zu parsen und zu verwenden, was zukünftige Änderungen verhindert. Es empfiehlt sich, toString() nur mit der öffentlichen API des Objekts zu implementieren.

Auf die Debug-Ausgabe nicht zu sehr verlassen

Es ist zwar nicht möglich, zu verhindern, dass Entwickler auf die Debug-Ausgabe angewiesen sind, aber wenn Sie die System.identityHashCode Ihres Objekts in die toString()-Ausgabe aufnehmen, ist es sehr unwahrscheinlich, dass zwei verschiedene Objekte dieselbe toString()-Ausgabe haben.

@Override
public String toString() {
  return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}

Das kann Entwickler davon abhalten, Testaussagen wie assertThat(a.toString()).isEqualTo(b.toString()) für Ihre Objekte zu schreiben.

„createFoo“ verwenden, um neu erstellte Objekte zurückzugeben

Verwenden Sie das Präfix create, nicht get oder new, für Methoden, die Rückgabewerte erstellen, z. B. durch Erstellen neuer Objekte.

Wenn die Methode ein Objekt zum Zurückgeben erstellt, muss dies im Methodennamen klar ersichtlich sein.

public FooThing getFooThing() {
  return new FooThing();
}
public FooThing createFooThing() {
  return new FooThing();
}

Methoden, die Dateiobjekte akzeptieren, sollten auch Streams akzeptieren

Speicherorte für Daten auf Android-Geräten sind nicht immer Dateien auf dem Laufwerk. Beispielsweise werden Inhalte, die über Nutzergrenzen hinweg übergeben werden, als content:// Uri dargestellt. Damit verschiedene Datenquellen verarbeitet werden können, sollten APIs, die File-Objekte akzeptieren, auch InputStream-, OutputStream-Objekte oder beide akzeptieren.

public void setDataSource(File file)
public void setDataSource(InputStream stream)

Rohe Primitive statt Boxversionen entgegennehmen und zurückgeben

Wenn Sie fehlende oder Nullwerte angeben möchten, können Sie -1, Integer.MAX_VALUE oder Integer.MIN_VALUE verwenden.

public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)

Wenn Sie Klassenäquivalente primitiver Typen vermeiden, vermeiden Sie den Arbeitsspeicher-Overhead dieser Klassen, den Methodenzugriff auf Werte und vor allem das Autoboxing, das durch das Casting zwischen primitiven und Objekttypen entsteht. Wenn Sie diese Verhaltensweisen vermeiden, sparen Sie Arbeitsspeicher und temporäre Zuweisungen, die zu teuren und häufigeren Garbage Collection-Vorgängen führen können.

Anmerkungen verwenden, um gültige Parameter- und Rückgabewerte zu verdeutlichen

Es wurden Anmerkungen für Entwickler hinzugefügt, um zulässige Werte in verschiedenen Situationen zu verdeutlichen. So können Tools Entwicklern leichter helfen, wenn sie falsche Werte angeben, z. B. einen beliebigen int übergeben, wenn das Framework einen bestimmten Satz von Konstantenwerten erfordert. Verwenden Sie nach Bedarf alle folgenden Anmerkungen:

Null-Zulässigkeit

Für Java-APIs sind explizite Anmerkungen zur Nullbarkeit erforderlich. Das Konzept der Nullbarkeit ist jedoch Teil der Kotlin-Sprache und Anmerkungen zur Nullbarkeit sollten in Kotlin-APIs niemals verwendet werden.

@Nullable:Gibt an, dass ein bestimmter Rückgabewert, Parameter oder ein Feld null sein kann:

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull:Gibt an, dass ein bestimmter Rückgabewert, Parameter oder ein Feld nicht null sein darf. Die Kennzeichnung von Elementen als @Nullable ist relativ neu bei Android. Daher sind die meisten API-Methoden von Android nicht einheitlich dokumentiert. Daher haben wir drei Status: „unbekannt“, „@Nullable“ und „@NonNull“. Deshalb ist @NonNull Teil der API-Richtlinien:

@NonNull
public String getName()

public void setName(@NonNull String name)

Wenn Sie die Methodenparameter in der Dokumentation für die Android-Plattform annotieren, wird automatisch eine Dokumentation in der Form „Dieser Wert kann null sein“ generiert, es sei denn, „null“ wird an anderer Stelle in der Parameterdokumentation explizit verwendet.

Bestehende Methoden, die „nicht wirklich null“ sind:Vorhandene Methoden in der API ohne deklarierte @Nullable-Anmerkung können mit @Nullable annotiert werden, wenn die Methode unter bestimmten, offensichtlichen Umständen (z. B. findViewById()) null zurückgeben kann. Für Entwickler, die keine Null-Prüfung durchführen möchten, sollten zugehörige @NotNull requireFoo()-Methoden hinzugefügt werden, die IllegalArgumentException auswerfen.

Schnittstellenmethoden:Neue APIs sollten bei der Implementierung von Schnittstellenmethoden wie Parcelable.writeToParcel() die richtige Anmerkung hinzufügen. Das bedeutet, dass die Methode in der implementierenden Klasse writeToParcel(@NonNull Parcel, int) und nicht writeToParcel(Parcel, int) sein sollte. Vorhandene APIs, bei denen die Anmerkungen fehlen, müssen jedoch nicht „repariert“ werden.

Erzwingung der Null-Zulässigkeit

In Java wird empfohlen, Methoden zur Eingabevalidierung für @NonNull-Parameter mit Objects.requireNonNull() auszuführen und eine NullPointerException zu werfen, wenn die Parameter null sind. In Kotlin geschieht dies automatisch.

Ressourcen

Ressourcen-IDs:Ganzzahlparameter, die IDs für bestimmte Ressourcen angeben, sollten mit der entsprechenden Ressourcentypdefinition versehen werden. Neben dem Allzweck-Label @AnyRes gibt es eine Annotationsanfrage für jeden Ressourcentyp, z. B. @StringRes, @ColorRes und @AnimRes. Beispiel:

public void setTitle(@StringRes int resId)

@IntDef für Konstantenmengen

Magische Konstanten:String- und int-Parameter, die einen von einer endlichen Anzahl möglicher Werte erhalten sollen, die durch öffentliche Konstanten angegeben werden, müssen entsprechend mit @StringDef oder @IntDef annotiert werden. Mit diesen Anmerkungen können Sie eine neue Anmerkung erstellen, die als Typdefinition für zulässige Parameter verwendet werden kann. Beispiel:

/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
  NAVIGATION_MODE_STANDARD,
  NAVIGATION_MODE_LIST,
  NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);

Es wird empfohlen, die Gültigkeit der annotierten Parameter zu prüfen und eine IllegalArgumentException auszugeben, wenn der Parameter nicht Teil der @IntDef ist.

@IntDef für Bitmasken-Flags

In der Anmerkung kann auch angegeben werden, dass die Konstanten Flags sind und mit „&“ und „I“ kombiniert werden können:

/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
  FLAG_USE_LOGO,
  FLAG_SHOW_HOME,
  FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

@StringDef für Sets von Stringkonstanten

Außerdem gibt es die Anmerkung @StringDef, die genau wie @IntDef im vorherigen Abschnitt funktioniert, aber für String-Konstanten. Sie können mehrere „prefix“-Werte angeben, anhand derer automatisch Dokumentationen für alle Werte generiert werden.

@SdkConstant für SDK-Konstanten

@SdkConstant: Fügen Sie öffentliche Felder mit einem der folgenden SdkConstant-Werte an: ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY und FEATURE.

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";

Kompatible Nullbarkeit für Überschreibungen bereitstellen

Aus Gründen der API-Kompatibilität muss die Nullbarkeit von Überschreibungen mit der aktuellen Nullbarkeit des übergeordneten Elements übereinstimmen. In der folgenden Tabelle sind die Kompatibilitätsanforderungen aufgeführt. Überschreibungen sollten nur so restriktiv oder restriktiver sein als das Element, das sie überschreiben.

Eingeben Elternteil Kind
Rückgabetyp Ohne Anmerkungen Nicht kommentiert oder nicht null
Rückgabetyp Nullwerte zulässig „Nullable“ oder „Nonnull“
Rückgabetyp Nonnull Nonnull
Spaßiges Argument Ohne Anmerkungen Nicht annotiert oder nullable
Spaßiges Argument Nullwerte zulässig Nullwerte zulässig
Spaßiges Argument Nonnull „Nullable“ oder „Nonnull“

Verwenden Sie nach Möglichkeit Argumente, die nicht null sein dürfen (z. B. @NonNull).

Bei überladenen Methoden sollten alle Argumente nicht null sein.

public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }

Diese Regel gilt auch für überladene Property-Setter. Das primäre Argument darf nicht null sein und das Löschen der Property sollte als separate Methode implementiert werden. So werden sinnlose Aufrufe verhindert, bei denen der Entwickler nachstellte Parameter festlegen muss, obwohl diese nicht erforderlich sind.

public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)

// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()

Verwenden Sie für Container Rückgabetypen, die nicht null sein können (z. B. @NonNull).

Gib für Containertypen wie Bundle oder Collection einen leeren und gegebenenfalls unveränderlichen Container zurück. Wenn null verwendet wird, um die Verfügbarkeit eines Containers zu unterscheiden, sollten Sie eine separate boolesche Methode angeben.

@NonNull
public Bundle getExtras() { ... }

Anmerkungen zur Nullbarkeit für Get- und Set-Paare müssen übereinstimmen

Die Anmerkungen zur Nullbarkeit von Methodenpaaren für eine einzelne logische Property müssen immer übereinstimmen. Wenn Sie diese Richtlinie nicht einhalten, wird die Property-Syntax von Kotlin beeinträchtigt. Das Hinzufügen von unterschiedlichen Anmerkungen zur Nullbarkeit zu vorhandenen Property-Methoden ist daher eine änderungsbedingte Änderung für Kotlin-Nutzer.

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

Rückgabewert bei Fehlern

Alle APIs sollten es Apps ermöglichen, auf Fehler zu reagieren. Wenn false, -1, null oder andere allgemeine Werte wie „Es ist ein Fehler aufgetreten“ zurückgegeben werden, erfahren Entwickler nicht genug darüber, ob die Erwartungen der Nutzer nicht erfüllt wurden oder ob die Zuverlässigkeit ihrer App in der Praxis nicht korrekt erfasst wurde. Stellen Sie sich beim Entwerfen einer API vor, dass Sie eine App entwickeln. Wenn ein Fehler auftritt, erhalten Sie von der API genügend Informationen, um ihn dem Nutzer zu präsentieren oder entsprechend zu reagieren?

  1. Es ist in Ordnung (und wird empfohlen), detaillierte Informationen in eine Ausnahmemeldung aufzunehmen. Entwickler sollten sie jedoch nicht analysieren müssen, um den Fehler angemessen zu behandeln. Ausführliche Fehlercodes oder andere Informationen sollten als Methoden freigegeben werden.
  2. Die von Ihnen ausgewählte Option zur Fehlerbehandlung sollte Ihnen die Flexibilität bieten, in Zukunft neue Fehlertypen einzuführen. Bei @IntDef bedeutet das, dass ein OTHER- oder UNKNOWN-Wert angegeben werden muss. Wenn Sie einen neuen Code zurückgeben, können Sie den targetSdkVersion des Aufrufers prüfen, um zu vermeiden, dass ein Fehlercode zurückgegeben wird, den die App nicht kennt. Verwenden Sie für Ausnahmen einen gemeinsamen Supertyp, den Ihre Ausnahmen implementieren, damit jeder Code, der diesen Typ verarbeitet, auch Untertypen abfangen und verarbeiten kann.
  3. Es sollte für Entwickler schwierig oder unmöglich sein, einen Fehler versehentlich zu ignorieren. Wenn der Fehler durch das Zurückgeben eines Werts mitgeteilt wird, kennzeichnen Sie Ihre Methode mit @CheckResult.

Sie sollten ? extends RuntimeException vorzugsweise dann werfen, wenn ein Fehler auftritt, weil der Entwickler etwas falsch gemacht hat, z. B. Einschränkungen für Eingabeparameter ignoriert oder den beobachtbaren Status nicht geprüft hat.

Setter- oder Aktionsmethoden (z. B. perform) können einen Ganzzahlstatuscode zurückgeben, wenn die Aktion aufgrund eines asynchron aktualisierten Status oder von Bedingungen fehlschlägt, die außerhalb der Kontrolle des Entwicklers liegen.

Statuscodes sollten in der übergeordneten Klasse als public static final-Felder definiert, mit ERROR_ vorangestellt und in einer @hide-@IntDef-Anmerkung aufgezählt werden.

Methodennamen sollten immer mit dem Verb beginnen, nicht mit dem Subjekt.

Der Name der Methode sollte immer mit dem Verb beginnen (z. B. get, create, reload usw.), nicht mit dem Objekt, auf das Sie eine Aktion ausführen.

public void tableReload() {
  mTable.reload();
}
public void reloadTable() {
  mTable.reload();
}

Verwenden Sie Sammlungstypen anstelle von Arrays als Rückgabe- oder Parametertyp.

Generisch typisierte Sammlungsschnittstellen bieten gegenüber Arrays mehrere Vorteile, darunter strengere API-Verträge in Bezug auf Eindeutigkeit und Sortierung, Unterstützung für generische Typen und eine Reihe von entwicklungsfreundlichen Methoden.

Ausnahme für primitive Typen

Wenn die Elemente primitive Typen sind, verwenden Sie stattdessen Arrays, um die Kosten für das automatische Boxen zu vermeiden. Weitere Informationen finden Sie unter Roh-Primitive statt Boxed-Versionen aufnehmen und zurückgeben.

Ausnahme für leistungsempfindlichen Code

In bestimmten Fällen, in denen die API in leistungssensiblem Code verwendet wird (z. B. Grafiken oder andere APIs für Messung, Layout oder Zeichnen), ist es akzeptabel, Arrays anstelle von Sammlungen zu verwenden, um Zuweisungen und Speicherbelegung zu reduzieren.

Ausnahme für Kotlin

Kotlin-Arrays sind unveränderlich und die Kotlin-Sprache bietet zahlreiche Dienstprogramm-APIs für Arrays. Daher sind Arrays für Kotlin-APIs, auf die von Kotlin aus zugegriffen werden soll, mit List und Collection vergleichbar.

@NonNull-Sammlungen bevorzugen

Verwenden Sie für Sammlungsobjekte immer @NonNull. Wenn Sie eine leere Sammlung zurückgeben, verwenden Sie die entsprechende Collections.empty-Methode, um ein kostengünstiges, korrekt typisiertes und unveränderliches Sammlungsobjekt zurückzugeben.

Wenn Typanmerkungen unterstützt werden, verwenden Sie für Sammlungselemente immer @NonNull.

Sie sollten @NonNull auch verwenden, wenn Sie Arrays anstelle von Sammlungen verwenden (siehe vorheriger Punkt). Wenn Sie Bedenken bezüglich der Objektzuweisung haben, erstellen Sie eine Konstante und geben Sie sie weiter. Ein leeres Array ist schließlich unveränderlich. Beispiel:

private static final int[] EMPTY_USER_IDS = new int[0];

@NonNull
public int[] getUserIds() {
  int [] userIds = mService.getUserIds();
  return userIds != null ? userIds : EMPTY_USER_IDS;
}

Änderbarkeit der Sammlung

In Kotlin-APIs sollten standardmäßig nur lesezugriffsfähige Rückgabetypen (nicht Mutable) für Sammlungen verwendet werden, es sei denn, der API-Vertrag erfordert ausdrücklich einen veränderbaren Rückgabetyp.

Bei Java-APIs sollten jedoch standardmäßig änderbare Rückgabetypen verwendet werden, da die Android-Plattformimplementierung von Java-APIs noch keine praktische Implementierung unveränderlicher Sammlungen bietet. Eine Ausnahme von dieser Regel sind Collections.empty-Rückgabetypen, die unveränderlich sind. Wenn die Mutabilität von Clients absichtlich oder versehentlich ausgenutzt werden könnte, um das beabsichtigte Nutzungsmuster der API zu brechen, sollten Java-APIs eine flache Kopie der Sammlung zurückgeben.

@Nullable
public PermissionInfo[] getGrantedPermissions() {
  return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
  if (mPermissions == null) {
    return Collections.emptySet();
  }
  return new ArraySet<>(mPermissions);
}

Explizit veränderbare Rückgabetypen

APIs, die Sammlungen zurückgeben, sollten das zurückgegebene Sammlungsobjekt nach der Rückgabe idealerweise nicht ändern. Wenn sich die zurückgegebene Sammlung ändern oder auf irgendeine Weise wiederverwendet werden muss, z. B. eine angepasste Ansicht eines veränderbaren Datensatzes, muss das genaue Verhalten festgelegt werden, wann sich die Inhalte ändern können, oder es müssen etablierte API-Namenskonventionen befolgt werden.

/**
 * Returns a view of this object as a list of [Item]s.
 */
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)

Die Kotlin-Konvention für .asFoo() wird unten beschrieben. Sie ermöglicht es, dass sich die von .asList() zurückgegebene Sammlung ändert, wenn sich die ursprüngliche Sammlung ändert.

Veränderbarkeit der zurückgegebenen Datentypobjekte

Ähnlich wie bei APIs, die Sammlungen zurückgeben, sollten APIs, die Objekte vom Typ „Datentyp“ zurückgeben, die Eigenschaften des zurückgegebenen Objekts nach der Rückgabe idealerweise nicht ändern.

val tempResult = DataContainer()

fun add(other: DataContainer): DataContainer {
  tempResult.innerValue = innerValue + other.innerValue
  return tempResult
}
fun add(other: DataContainer): DataContainer {
  return DataContainer(innerValue + other.innerValue)
}

In äußerst seltenen Fällen kann bei leistungskritischem Code ein Vorteil durch Objekt-Pooling oder ‑Wiederverwendung erzielt werden. Erstellen Sie keine eigene Objektpool-Datenstruktur und stellen Sie wiederverwendete Objekte nicht in öffentlichen APIs bereit. In beiden Fällen sollten Sie den gleichzeitigen Zugriff mit äußerster Sorgfalt verwalten.

Verwendung des Parametertyps „vararg“

Sowohl in Kotlin- als auch in Java-APIs wird empfohlen, vararg zu verwenden, wenn der Entwickler wahrscheinlich ein Array an der Aufrufstelle erstellt, um nur mehrere, verwandte Parameter desselben Typs zu übergeben.

public void setFeatures(Feature[] features) { ... }

// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }

// Developer code
setFeatures(Features.A, Features.B, Features.C);

Defensive Kopien

Sowohl Java- als auch Kotlin-Implementierungen von vararg-Parametern werden in denselben arraybasierten Bytecode kompiliert und können daher aus Java-Code mit einem veränderbaren Array aufgerufen werden. API-Entwicklern wird dringend empfohlen, eine defensive flache Kopie des Arrayparameters zu erstellen, wenn er in einem Feld oder einer anonymen inneren Klasse gespeichert wird.

public void setValues(SomeObject... values) {
   this.values = Arrays.copyOf(values, values.length);
}

Das Erstellen einer defensiven Kopie bietet keinen Schutz vor gleichzeitigen Änderungen zwischen dem ursprünglichen Methodenaufruf und dem Erstellen der Kopie. Außerdem bietet es keinen Schutz vor der Mutation der im Array enthaltenen Objekte.

Korrekte Semantik mit Parametern für Sammlungstypen oder Rückgabetypen angeben

List<Foo> ist die Standardoption. Es gibt aber auch andere Typen, die zusätzliche Bedeutungen haben:

  • Verwenden Sie Set<Foo>, wenn die Reihenfolge der Elemente in Ihrer API nicht wichtig ist und Duplikate nicht zulässig sind oder keine Bedeutung haben.

  • Collection<Foo>,, wenn Ihre API die Reihenfolge nicht berücksichtigt und Duplikate zulässt.

Kotlin-Konversionsfunktionen

In Kotlin werden .toFoo() und .asFoo() häufig verwendet, um ein Objekt eines anderen Typs aus einem vorhandenen Objekt abzurufen. Dabei ist Foo der Name des Rückgabetyps der Umwandlung. Dies entspricht dem bekannten JDKObject.toString(). In Kotlin wird dies noch weitergeführt und auch für primitive Konvertierungen wie 25.toFloat() verwendet.

Der Unterschied zwischen Conversions mit den Namen .toFoo() und .asFoo() ist erheblich:

.toFoo() zum Erstellen eines neuen, unabhängigen Objekts verwenden

Wie bei .toString() wird bei einer „zu“-Conversion ein neues, unabhängiges Objekt zurückgegeben. Wenn das ursprüngliche Objekt später geändert wird, werden diese Änderungen nicht auf das neue Objekt übertragen. Wenn das neue Objekt später geändert wird, werden diese Änderungen nicht im alten Objekt übernommen.

fun Foo.toBundle(): Bundle = Bundle().apply {
    putInt(FOO_VALUE_KEY, value)
}

.asFoo() zum Erstellen eines abhängigen Wrappers, eines verzierten Objekts oder eines Typs verwenden

In Kotlin wird das Casting mit dem Schlüsselwort as durchgeführt. Sie spiegelt eine Änderung der Benutzeroberfläche, aber keine Änderung der Identität wider. Wenn .asFoo() als Präfix in einer Erweiterungsfunktion verwendet wird, wird der Empfänger damit dekoriert. Eine Mutation im ursprünglichen Empfängerobjekt wird im von asFoo() zurückgegebenen Objekt berücksichtigt. Eine Mutation im neuen Foo-Objekt wird möglicherweise im ursprünglichen Objekt übernommen.

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

Konversionsfunktionen sollten als Erweiterungsfunktionen geschrieben werden

Wenn Sie Conversion-Funktionen außerhalb der Definitionen der Empfänger- und Ergebnisklasse schreiben, wird die Kopplung zwischen den Typen reduziert. Für eine ideale Umwandlung ist nur öffentlicher API-Zugriff auf das Originalobjekt erforderlich. Das zeigt, dass Entwickler auch analoge Conversions für ihre eigenen bevorzugten Typen erstellen können.

Entsprechende spezifische Ausnahmen auslösen

Methoden dürfen keine generischen Ausnahmen wie java.lang.Exception oder java.lang.Throwable auslösen. Stattdessen muss eine geeignete spezifische Ausnahme wie java.lang.NullPointerException verwendet werden, damit Entwickler Ausnahmen behandeln können, ohne zu allgemein zu sein.

Bei Fehlern, die nicht mit den Argumenten zusammenhängen, die direkt an die öffentlich aufgerufene Methode übergeben werden, sollte java.lang.IllegalStateException statt java.lang.IllegalArgumentException oder java.lang.NullPointerException geworfen werden.

Listener und Callbacks

Dies sind die Regeln für die Klassen und Methoden, die für Listener- und Rückrufmechanismen verwendet werden.

Callback-Klassennamen müssen im Singular stehen.

Verwenden Sie MyObjectCallback anstelle von MyObjectCallbacks.

Die Namen von Callback-Methoden müssen das Format on haben.

onFooEvent bedeutet, dass FooEvent auftritt und dass der Callback entsprechend reagieren soll.

Präteritum und Präsens sollten das Timing beschreiben

Callback-Methoden für Ereignisse sollten so benannt sein, dass sie angeben, ob das Ereignis bereits stattgefunden hat oder gerade stattfindet.

Wenn die Methode beispielsweise nach einer Klickaktion aufgerufen wird:

public void onClicked()

Wenn die Methode jedoch für die Ausführung der Klickaktion verantwortlich ist, gilt Folgendes:

public boolean onClick()

Callback-Registrierung

Wenn einem Objekt ein Listener oder Rückruf hinzugefügt oder daraus entfernt werden kann, sollten die zugehörigen Methoden „Hinzufügen“ und „Entfernen“ oder „Registrieren“ und „Abmelden“ heißen. Sie müssen der bestehenden Konvention entsprechen, die von der Klasse oder anderen Klassen im selben Paket verwendet wird. Wenn kein solcher Präzedenzfall vorliegt, verwenden Sie vorzugsweise „Hinzufügen“ und „Entfernen“.

Bei Methoden, bei denen Callbacks registriert oder deregistriert werden, muss der vollständige Name des Callback-Typs angegeben werden.

public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);

Vermeiden Sie Getter für Rückrufe

Fügen Sie getFooCallback()-Methoden nicht hinzu. Dies ist eine verlockende Notlösung für Fälle, in denen Entwickler einen vorhandenen Rückruf mit ihrem eigenen Ersatz verknüpfen möchten. Sie ist jedoch fehleranfällig und erschwert es Komponentenentwicklern, den aktuellen Status zu ermitteln. Beispiel:

  • Entwickler A ruft setFooCallback(a) an
  • Entwickler B ruft setFooCallback(new B(getFooCallback())) an
  • Entwickler A möchte seinen Rückruf a entfernen, kann dies aber nur tun, wenn er den Typ von B kennt. B wurde jedoch so entwickelt, dass solche Änderungen an seinem verpackten Rückruf zulässig sind.

Executor akzeptieren, um den Callback-Versand zu steuern

Bei der Registrierung von Callbacks, für die keine expliziten Anforderungen an die Threadverwaltung gelten (praktisch überall außerhalb des UI-Toolkits), wird dringend empfohlen, bei der Registrierung einen Executor-Parameter anzugeben, damit der Entwickler den Thread angeben kann, in dem die Callbacks aufgerufen werden.

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Als Ausnahme zu unseren üblichen Richtlinien zu optionalen Parametern ist es zulässig, eine Überladung anzugeben, bei der Executor weggelassen wird, auch wenn es sich nicht um das letzte Argument in der Parameterliste handelt. Wenn Executor nicht angegeben ist, sollte der Rückruf im Hauptthread mit Looper.getMainLooper() aufgerufen werden. Dies sollte in der zugehörigen überladenen Methode dokumentiert werden.

/**
 * ...
 * Note that the callback will be executed on the main thread using
 * {@link Looper.getMainLooper()}. To specify the execution thread, use
 * {@link registerFooCallback(Executor, FooCallback)}.
 * ...
 */
public void registerFooCallback(
    @NonNull FooCallback callback)

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Executor Tücken bei der Implementierung:Beachten Sie, dass das Folgende ein gültiger Executor ist.

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

Das bedeutet, dass bei der Implementierung von APIs dieses Typs die Implementierung des eingehenden Binder-Objekts auf App-Prozessseite Binder.clearCallingIdentity() aufrufen muss, bevor der Callback der App auf der von der App bereitgestellten Executor aufgerufen wird. So wird jeder App-Code, der die Binder-Identität (z. B. Binder.getCallingUid()) für Berechtigungsprüfungen verwendet, der App und nicht dem Systemprozess zugeordnet, der die App aufruft. Wenn Nutzer Ihrer API die UID- oder PID-Informationen des Aufrufers benötigen, sollten diese explizit Teil Ihrer API-Oberfläche sein und nicht implizit davon abhängen, wo die von ihnen angegebene Binder.getCallingUid() ausgeführt wurde.Executor

Die Angabe eines Executor sollte von Ihrer API unterstützt werden. In leistungskritischen Fällen müssen Apps möglicherweise Code entweder sofort oder synchron mit dem Feedback Ihrer API ausführen. Das Akzeptieren einer Executor ermöglicht dies. Wenn Sie aus defensiven Gründen eine zusätzliche HandlerThread oder ähnliches als Trampolin erstellen, wird dieser wünschenswerte Anwendungsfall untergraben.

Wenn eine App irgendwo in ihrem eigenen Prozess ressourcenintensiven Code ausführen soll, lassen Sie es zu. Die Umgehungslösungen, die App-Entwickler finden, um Ihre Einschränkungen zu umgehen, sind langfristig viel schwieriger zu unterstützen.

Ausnahme für einzelnen Callback:Wenn die Art der gemeldeten Ereignisse nur die Unterstützung einer einzelnen Callback-Instanz erfordert, verwenden Sie den folgenden Stil:

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Executor anstelle von Handler verwenden

Handler von Android wurde in der Vergangenheit als Standard für die Weiterleitung der Rückrufausführung an einen bestimmten Looper-Thread verwendet. Dieser Standard wurde geändert, um Executor zu bevorzugen, da die meisten App-Entwickler ihre eigenen Threadpools verwalten. Dadurch ist der Haupt- oder UI-Thread der einzige Looper-Thread, der für die App verfügbar ist. Mit Executor erhalten Entwickler die Kontrolle, die sie benötigen, um ihre vorhandenen/bevorzugten Ausführungskontexte wiederzuverwenden.

Moderne Bibliotheken für die Parallelität wie kotlinx.coroutines oder RxJava bieten eigene Planungsmechanismen, die bei Bedarf eine eigene Zuweisung ausführen. Daher ist es wichtig, die Möglichkeit zu bieten, einen direkten Executor (z. B. Runnable::run) zu verwenden, um Latenzen durch doppelte Thread-Hops zu vermeiden. Beispiel: Ein Hop, um mit einem Handler in einem Looper-Thread zu posten, gefolgt von einem weiteren Hop vom Concurrentitäts-Framework der App.

Ausnahmen von dieser Richtlinie sind selten. Häufige Gründe für eine Ausnahme:

Ich muss eine Looper verwenden, weil ich für die Veranstaltung eine Looper bis epoll benötige. Dieser Ausnahmeantrag wird gewährt, da die Vorteile von Executor in dieser Situation nicht genutzt werden können.

Ich möchte nicht, dass der App-Code die Veröffentlichung des Ereignisses in meinem Thread blockiert. Diese Ausnahmeanfrage wird in der Regel nicht für Code gewährt, der in einem App-Prozess ausgeführt wird. Apps, die dies falsch machen, schaden sich selbst, haben aber keine Auswirkungen auf die allgemeine Systemgesundheit. Bei Apps, die richtig implementiert sind oder ein gängiges Concurrent-Execution-Framework verwenden, sollten keine zusätzlichen Latenzprobleme auftreten.

Handler ist lokal mit anderen ähnlichen APIs in derselben Klasse konsistent. Diese Ausnahmegenehmigung wird je nach Situation gewährt. Es wird empfohlen, Executor-basierte Überladungen hinzuzufügen und Handler-Implementierungen auf die neue Executor-Implementierung umzustellen. (myHandler::post ist ein gültiger Executor!) Je nach Größe der Klasse, Anzahl der vorhandenen Handler-Methoden und Wahrscheinlichkeit, dass Entwickler neben der neuen Methode vorhandene Handler-basierte Methoden verwenden müssen, kann eine Ausnahme gewährt werden, um eine neue Handler-basierte Methode hinzuzufügen.

Symmetrie bei der Registrierung

Wenn es eine Möglichkeit gibt, etwas hinzuzufügen oder zu registrieren, sollte es auch eine Möglichkeit geben, es zu entfernen oder zu unregisteren. Die Methode

registerThing(Thing)

sollte eine übereinstimmende

unregisterThing(Thing)

Geben Sie eine Anfrage-ID an.

Wenn es für einen Entwickler sinnvoll ist, einen Callback wiederzuverwenden, geben Sie ein Identifier-Objekt an, um den Callback an die Anfrage zu binden.

class RequestParameters {
  public int getId() { ... }
}

class RequestExecutor {
  public void executeRequest(
    RequestParameters parameters,
    Consumer<RequestParameters> onRequestCompletedListener) { ... }
}

Callback-Objekte mit mehreren Methoden

Bei Callbacks mit mehreren Methoden sollten Sie interface bevorzugen und default-Methoden verwenden, wenn Sie sie zu zuvor veröffentlichten Schnittstellen hinzufügen. Bisher wurde in dieser Richtlinie abstract class empfohlen, da es in Java 7 keine default-Methoden gab.

public interface MostlyOptionalCallback {
  void onImportantAction();
  default void onOptionalInformation() {
    // Empty stub, this method is optional.
  }
}

android.os.OutcomeReceiver beim Modellieren eines nicht blockierenden Funktionsaufrufs verwenden

OutcomeReceiver<R,E> meldet bei Erfolg den Ergebniswert R oder andernfalls E : Throwable – genau wie ein einfacher Methodenaufruf. Verwenden Sie OutcomeReceiver als Rückgabetyp, wenn Sie eine blockierende Methode, die ein Ergebnis zurückgibt oder eine Ausnahme auslöst, in eine nicht blockierende asynchrone Methode umwandeln:

interface FooType {
  // Before:
  public FooResult requestFoo(FooRequest request);

  // After:
  public void requestFooAsync(FooRequest request, Executor executor,
      OutcomeReceiver<FooResult, Throwable> callback);
}

Auf diese Weise konvertierte asynchrone Methoden geben immer void zurück. Alle Ergebnisse, die requestFoo zurückgeben würde, werden stattdessen an den OutcomeReceiver.onResult des callback-Parameters von requestFooAsync gemeldet, indem er mit der angegebenen executor aufgerufen wird. Alle Ausnahmen, die von requestFoo ausgelöst würden, werden stattdessen auf die gleiche Weise an die Methode OutcomeReceiver.onError gemeldet.

Wenn Sie OutcomeReceiver zum Melden von Ergebnissen asynchroner Methoden verwenden, können Sie auch einen Kotlin-suspend fun-Wrapper für asynchrone Methoden mit der Continuation.asOutcomeReceiver-Erweiterung von androidx.core:core-ktx verwenden:

suspend fun FooType.requestFoo(request: FooRequest): FooResult =
  suspendCancellableCoroutine { continuation ->
    requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
  }

Mit solchen Erweiterungen können Kotlin-Clients nicht blockierende asynchrone Methoden mit der Bequemlichkeit eines einfachen Funktionsaufrufs aufrufen, ohne den aufrufenden Thread zu blockieren. Diese 1:1-Erweiterungen für Plattform-APIs können im Rahmen des androidx.core:core-ktx-Artefakts in Jetpack angeboten werden, wenn sie mit den Kompatibilitätstests und -überlegungen der Standardversion kombiniert werden. Weitere Informationen, Hinweise zur Kündigung und Beispiele findest du in der Dokumentation zu asOutcomeReceiver.

Bei asynchronen Methoden, die nicht der Semantik einer Methode entsprechen, die ein Ergebnis zurückgibt oder eine Ausnahme auslöst, wenn die Arbeit abgeschlossen ist, sollte OutcomeReceiver nicht als Callback-Typ verwendet werden. Verwenden Sie stattdessen eine der anderen Optionen, die im folgenden Abschnitt aufgeführt sind.

Funktionsschnittstellen statt neuer SAM-Typen (Single Abstract Method) verwenden

Mit API-Ebene 24 wurden die Typen java.util.function.* (Referenzdokumente) hinzugefügt. Sie bieten generische SAM-Schnittstellen wie Consumer<T>, die sich als Callback-Lambdas verwenden lassen. In vielen Fällen bietet das Erstellen neuer SAM-Schnittstellen wenig Mehrwert in Bezug auf die Typensicherheit oder die Kommunikation von Absichten, während die Android API-Oberfläche unnötig erweitert wird.

Verwenden Sie stattdessen diese generischen Schnittstellen, anstatt neue zu erstellen:

Platzierung von SAM-Parametern

SAM-Parameter sollten an letzter Stelle stehen, um eine idiomatische Verwendung in Kotlin zu ermöglichen, auch wenn die Methode mit zusätzlichen Parametern überladen ist.

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

Docs

Dies sind Regeln für die öffentlichen API-Dokumente (Javadoc).

Alle öffentlichen APIs müssen dokumentiert sein

Alle öffentlichen APIs müssen eine ausreichende Dokumentation enthalten, die erklärt, wie ein Entwickler die API verwenden würde. Angenommen, der Entwickler hat die Methode über die automatische Vervollständigung oder beim Durchsuchen der API-Referenzdokumente gefunden und hat nur einen minimalen Kontext aus der benachbarten API-Oberfläche (z. B. dieselbe Klasse).

Methoden

Methodenparameter und Rückgabewerte müssen jeweils mit @param- und @return-Docs-Anmerkungen dokumentiert werden. Formatieren Sie den Javadoc-Text so, als würde er von „Diese Methode…“ eingeleitet.

Wenn eine Methode keine Parameter annimmt, keine besonderen Überlegungen erfordert und das zurückgibt, was der Methodenname verspricht, können Sie @return auslassen und die Dokumentation so verfassen:

/**
 * Returns the priority of the thread.
 */
@IntRange(from = 1, to = 10)
public int getPriority() { ... }

Dokumente sollten Links zu anderen Dokumenten für zugehörige Konstanten, Methoden und andere Elemente enthalten. Verwenden Sie Javadoc-Tags (z. B. @see und {@link foo}) und nicht nur reine Textwörter.

Für das folgende Quellbeispiel:

public static final int FOO = 0;
public static final int BAR = 1;

Verwenden Sie keinen Rohtext oder Code-Schriftschnitt:

/**
 * Sets value to one of FOO or <code>BAR</code>.
 *
 * @param value the value being set, one of FOO or BAR
 */
public void setValue(int value) { ... }

Verwenden Sie stattdessen Links:

/**
 * Sets value to one of {@link #FOO} or {@link #BAR}.
 *
 * @param value the value being set
 */
public void setValue(@ValueType int value) { ... }

Wenn Sie eine IntDef-Anmerkung wie @ValueType auf einen Parameter anwenden, wird automatisch eine Dokumentation mit den zulässigen Typen generiert. Weitere Informationen zu IntDef finden Sie in der Anleitung zu Anmerkungen.

„update-api“ oder „docs“ als Ziel ausführen, wenn Javadoc hinzugefügt wird

Diese Regel ist besonders wichtig, wenn du @link- oder @see-Tags hinzufügst und dafür sorgen möchtest, dass die Ausgabe wie erwartet aussieht. ERROR-Ausgaben in Javadoc sind oft auf fehlerhafte Links zurückzuführen. Diese Prüfung wird entweder vom update-api- oder vom docs-Make-Ziel durchgeführt. Das docs-Ziel ist jedoch möglicherweise schneller, wenn Sie nur Javadoc ändern und das update-api-Ziel sonst nicht ausführen müssen.

{@code foo} verwenden, um Java-Werte zu unterscheiden

Umschließen Sie Java-Werte wie true, false und null in {@code...}, um sie vom Dokumentationstext zu unterscheiden.

Wenn Sie Dokumentationen in Kotlin-Quellen schreiben, können Sie Code wie bei Markdown in Backticks einschließen.

Zusammenfassungen für @param und @return sollten aus einem einzigen Satz bestehen.

Zusammenfassungen von Parametern und Rückgabewerten sollten mit einem Kleinbuchstaben beginnen und nur ein einziges Satzfragment enthalten. Wenn Sie zusätzliche Informationen haben, die über einen einzelnen Satz hinausgehen, verschieben Sie sie in den Javadoc-Textkörper der Methode:

/**
 * @param e The element to be appended to the list. This must not be
 *       null. If the list contains no entries, this element will
 *       be added at the beginning.
 * @return This method returns true on success.
 */

Sollte zu Folgendem geändert werden:

/**
 * @param e element to be appended to this list, must be non-{@code null}
 * @return {@code true} on success, {@code false} otherwise
 */

Anmerkungen in Google Docs müssen Erläuterungen enthalten

Beschreiben Sie, warum Anmerkungen @hide und @removed in der öffentlichen API ausgeblendet sind. Fügen Sie eine Anleitung zum Ersetzen von API-Elementen hinzu, die mit der @deprecated-Anmerkung gekennzeichnet sind.

Ausnahmen mit @throws dokumentieren

Wenn eine Methode eine geprüfte Ausnahme auslöst, z. B. IOException, dokumentieren Sie die Ausnahme mit @throws. Für Kotlin-APIs, die für Java-Clients bestimmt sind, müssen Funktionen mit @Throws kommentiert werden.

Wenn eine Methode eine nicht geprüfte Ausnahme auslöst, die auf einen vermeidbaren Fehler hinweist, z. B. IllegalArgumentException oder IllegalStateException, dokumentieren Sie die Ausnahme und erläutern Sie, warum sie ausgelöst wird. Die geworfene Ausnahme sollte auch angeben, warum sie geworfen wurde.

Bestimmte Fälle von nicht geprüften Ausnahmen gelten als implizit und müssen nicht dokumentiert werden, z. B. NullPointerException oder IllegalArgumentException, wenn ein Argument nicht mit einer @IntDef oder einer ähnlichen Anmerkung übereinstimmt, die den API-Vertrag in die Methodensignatur einbettet:

/**
 * ...
 * @throws IOException If it cannot find the schema for {@code toVersion}
 * @throws IllegalStateException If the schema validation fails
 */
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
    boolean validateDroppedTables, Migration... migrations) throws IOException {
  // ...
  if (!dbPath.exists()) {
    throw new IllegalStateException("Cannot find the database file for " + name
        + ". Before calling runMigrations, you must first create the database "
        + "using createDatabase.");
  }
  // ...

Oder in Kotlin:

/**
 * ...
 * @throws IOException If something goes wrong reading the file, such as a bad
 *                     database header or missing permissions
 */
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
  // ...
  val read = input.read(buffer)
    if (read != 4) {
      throw IOException("Bad database header, unable to read 4 bytes at " +
          "offset 60")
    }
  }
  // ...

Wenn die Methode asynchronen Code aufruft, der möglicherweise Ausnahmen auslöst, überlegen Sie, wie der Entwickler von solchen Ausnahmen erfährt und darauf reagiert. Normalerweise bedeutet das, die Ausnahme an einen Rückruf weiterzuleiten und die in der empfangenden Methode geworfenen Ausnahmen zu dokumentieren. Asynchrone Ausnahmen sollten nur mit @throws dokumentiert werden, wenn sie tatsächlich aus der kommentierten Methode neu geworfen werden.

Den ersten Satz von Dokumenten mit einem Punkt beenden

Das Doclava-Tool analysiert Dokumente auf einfache Weise und beendet das Synopsis-Dokument (den ersten Satz, der in der Kurzbeschreibung oben in den Kursdokumenten verwendet wird), sobald ein Punkt (.) gefolgt von einem Leerzeichen erkannt wird. Das führt zu zwei Problemen:

  • Wenn ein kurzes Dokument nicht mit einem Punkt endet und dieses Mitglied übernommene Dokumente hat, die vom Tool erfasst werden, werden diese übernommenen Dokumente auch in der Zusammenfassung berücksichtigt. Sehen Sie sich beispielsweise actionBarTabStyle in den R.attr-Dokumenten an. Dort ist die Beschreibung der Dimension in der Zusammenfassung enthalten.
  • Vermeiden Sie aus demselben Grund „z.B.“ im ersten Satz, da Doclava die Synopsis-Dokumente nach „g.“ beendet. Beispiel: TEXT_ALIGNMENT_CENTER in View.java Metalava korrigiert diesen Fehler automatisch, indem nach dem Punkt ein nicht trennendes Leerzeichen eingefügt wird. Versuchen Sie jedoch, diesen Fehler gar nicht erst zu machen.

Dokumente so formatieren, dass sie in HTML gerendert werden

Javadoc wird in HTML gerendert. Formatieren Sie diese Dokumente daher entsprechend:

  • Für Zeilenumbrüche sollte ein explizites <p>-Tag verwendet werden. Fügen Sie kein abschließendes </p>-Tag hinzu.

  • Verwenden Sie keine ASCII-Zeichen, um Listen oder Tabellen zu rendern.

  • Für Listen sollten Sie <ul> bzw. <ol> für unsortierte und sortierte Listen verwenden. Jedes Element sollte mit einem <li>-Tag beginnen, aber kein schließendes </li>-Tag haben. Nach dem letzten Element ist ein schließendes </ul>- oder </ol>-Tag erforderlich.

  • Für Tabellen sollten <table>, <tr> für Zeilen, <th> für Überschriften und <td> für Zellen verwendet werden. Alle Tabellen-Tags erfordern übereinstimmende End-Tags. Sie können class="deprecated" in jedem Tag verwenden, um die Einstellung zu kennzeichnen.

  • Verwenden Sie {@code foo}, um einen Inline-Code-Schriftschnitt zu erstellen.

  • Verwenden Sie <pre>, um Codeblöcke zu erstellen.

  • Der gesamte Text in einem <pre>-Block wird vom Browser geparst. Seien Sie also vorsichtig mit Klammern <>. Sie können sie mit den HTML-Entitäten &lt; und &gt; maskieren.

  • Alternativ können Sie rohe Klammern <> in Ihrem Code-Snippet belassen, wenn Sie die betreffenden Abschnitte in {@code foo} einschließen. Beispiel:

    <pre>{@code <manifest>}</pre>
    

Stilrichtlinien für die API-Referenz einhalten

Um für Klassenzusammenfassungen, Methodenbeschreibungen, Parameterbeschreibungen und andere Elemente einen einheitlichen Stil zu verwenden, folgen Sie den Empfehlungen in den offiziellen Java-Sprachrichtlinien unter How to Write Doc Comments for the Javadoc Tool (So schreiben Sie Doc-Kommentare für das Javadoc-Tool).

Android-Framework-spezifische Regeln

Diese Regeln beziehen sich auf APIs, Muster und Datenstrukturen, die für APIs und Verhaltensweisen spezifisch sind, die in das Android-Framework eingebunden sind (z. B. Bundle oder Parcelable).

Intent-Builder sollten das Muster „create*Intent()“ verwenden

Entwickler von Intents sollten Methoden mit dem Namen createFooIntent() verwenden.

Verwenden Sie Bundles, anstatt neue allgemeine Datenstrukturen zu erstellen.

Erstellen Sie keine neuen allgemeinen Datenstrukturen, um beliebige Zuordnungen von Schlüsseln zu typisierten Werten darzustellen. Verwenden Sie stattdessen Bundle.

Dies tritt in der Regel beim Erstellen von Plattform-APIs auf, die als Kommunikationskanäle zwischen nicht plattformgebundenen Apps und Diensten dienen. Dabei liest die Plattform die über den Kanal gesendeten Daten nicht und der API-Vertrag kann teilweise außerhalb der Plattform definiert sein (z. B. in einer Jetpack-Bibliothek).

Wenn die Plattform die Daten liest, sollten Sie Bundle nicht verwenden, sondern eine stark typisierte Datenklasse bevorzugen.

Implementierungen von Parcelable müssen ein öffentliches CREATOR-Feld haben

Die Infragestellung von Parcelable erfolgt über CREATOR, nicht über Rohkonstruktoren. Wenn eine Klasse Parcelable implementiert, muss ihr CREATOR-Feld auch eine öffentliche API sein und der Klassenkonstruktor, der ein Parcel-Argument annimmt, muss privat sein.

CharSequence für UI-Strings verwenden

Wenn ein String in einer Benutzeroberfläche angezeigt wird, verwenden Sie CharSequence, um Spannable-Instanzen zuzulassen.

Wenn es sich nur um einen Schlüssel oder ein anderes Label oder einen anderen Wert handelt, der für Nutzer nicht sichtbar ist, ist String in Ordnung.

Vermeiden Sie die Verwendung von Enums

IntDef muss in allen Plattform-APIs anstelle von Enumerationen verwendet werden und sollte bei nicht gebündelten Bibliotheks-APIs in Betracht gezogen werden. Verwenden Sie Enumerationen nur, wenn Sie sicher sind, dass keine neuen Werte hinzugefügt werden.

Vorteile vonIntDef:

  • Ermöglicht das Hinzufügen von Werten im Zeitverlauf
    • Kotlin-when-Anweisungen können bei der Laufzeit fehlschlagen, wenn sie aufgrund eines hinzugefügten Enum-Werts auf der Plattform nicht mehr vollständig sind.
  • Bei der Laufzeit werden keine Klassen oder Objekte, sondern nur primitive Typen verwendet.
    • Mit R8 oder Minimierung können Sie diese Kosten für nicht gebündelte Bibliotheks-APIs vermeiden. Diese Optimierung kann jedoch nicht auf Plattform-API-Klassen angewendet werden.

Vorteile von Enum

  • Idiomatische Sprachfunktion von Java, Kotlin
  • Aktiviert die Verwendung von Switch- und when-Anweisungen mit vollständiger Fallabdeckung.
    • Hinweis: Die Werte dürfen sich im Laufe der Zeit nicht ändern (siehe vorherige Liste).
  • Klar umrissene und leicht auffindbare Namen
  • Aktiviert die Überprüfung zur Kompilierungszeit.
    • Beispiel: Eine when-Anweisung in Kotlin, die einen Wert zurückgibt
  • Eine funktionsfähige Klasse, die Schnittstellen implementieren, statische Hilfsfunktionen haben, Mitglieds- oder Erweiterungsmethoden und Felder freigeben kann.

Beachten Sie die Android-Paket-Beschichtungshierarchie

Die android.*-Pakethierarchie hat eine implizite Reihenfolge, bei der Pakete auf niedrigerer Ebene nicht von Paketen auf höherer Ebene abhängen können.

Vermeiden Sie Verweise auf Google, andere Unternehmen und deren Produkte.

Die Android-Plattform ist ein Open-Source-Projekt und soll anbieterunabhängig sein. Die API sollte generisch sein und von Systemintegratoren oder Apps mit den erforderlichen Berechtigungen gleichermaßen verwendet werden können.

Implementierungen von Parcelable-Objekten sollten endgültig sein

Von der Plattform definierte Parcelable-Klassen werden immer von framework.jar geladen. Daher ist es unzulässig, wenn eine App versucht, eine Parcelable-Implementierung zu überschreiben.

Wenn die sendende App eine Parcelable erweitert, hat die empfangende App nicht die benutzerdefinierte Implementierung des Absenders, mit der sie entpackt werden kann. Hinweis zur Abwärtskompatibilität: Wenn Ihre Klasse bisher nicht final war, aber keinen öffentlich zugänglichen Konstruktor hatte, können Sie sie trotzdem als final kennzeichnen.

Methoden, die einen Systemprozess aufrufen, sollten RemoteException als RuntimeException wiedergeben.

RemoteException wird in der Regel von der internen AIDL ausgelöst und gibt an, dass der Systemprozess beendet wurde oder die App versucht, zu viele Daten zu senden. In beiden Fällen sollte die öffentliche API als RuntimeException neu gesendet werden, um zu verhindern, dass Apps Sicherheits- oder Richtlinienentscheidungen beibehalten.

Wenn Sie wissen, dass die andere Seite eines Binder-Aufrufs der Systemprozess ist, ist dieser Boilerplate-Code die Best Practice:

try {
    ...
} catch (RemoteException e) {
    throw e.rethrowFromSystemServer();
}

Bestimmte Ausnahmen für API-Änderungen auslösen

Das Verhalten öffentlicher APIs kann sich zwischen API-Ebenen ändern und zu App-Abstürzen führen, z. B. wenn neue Sicherheitsrichtlinien erzwungen werden.

Wenn die API für eine zuvor gültige Anfrage eine Ausnahme auslösen muss, werfen Sie eine neue spezifische Ausnahme anstelle einer generischen Ausnahme. Beispiel: ExportedFlagRequired anstelle von SecurityException (ExportedFlagRequired kann SecurityException überlappen).

So können App-Entwickler und -Tools Änderungen am API-Verhalten erkennen.

Kopierkonstruktor anstelle von Klon implementieren

Die Verwendung der Java-Methode clone() wird aufgrund des Fehlens von API-Verträgen durch die Klasse Object und der Schwierigkeiten bei der Erweiterung von Klassen, die clone() verwenden, dringend abgeraten. Verwenden Sie stattdessen einen Kopierkonstruktor, der ein Objekt desselben Typs annimmt.

/**
 * Constructs a shallow copy of {@code other}.
 */
public Foo(Foo other)

Klassen, die für die Erstellung auf einen Builder angewiesen sind, sollten einen Builder-Kopienkonstruktor hinzufügen, um Änderungen an der Kopie zu ermöglichen.

public class Foo {
    public static final class Builder {
        /**
         * Constructs a Foo builder using data from {@code other}.
         */
        public Builder(Foo other)

ParcelFileDescriptor anstelle von FileDescriptor verwenden

Die Inhaberschaft des java.io.FileDescriptor-Objekts ist nicht klar definiert, was zu schwer zu findenden Fehlern vom Typ „Verwendung nach dem Schließen“ führen kann. Stattdessen sollten APIs ParcelFileDescriptor-Instanzen zurückgeben oder akzeptieren. Legacy-Code kann bei Bedarf mithilfe von dup() oder getFileDescriptor() zwischen PFD und FD konvertieren.

Vermeiden Sie die Verwendung von Zahlenwerten mit ungerader Größe.

Vermeiden Sie es, short- oder byte-Werte direkt zu verwenden, da sie häufig einschränken, wie Sie die API in Zukunft weiterentwickeln können.

BitSet vermeiden

java.util.BitSet eignet sich hervorragend für die Implementierung, aber nicht für öffentliche APIs. Sie ist veränderlich, erfordert eine Zuweisung für häufige Methodenaufrufe und bietet keine semantische Bedeutung für die einzelnen Bits.

Verwenden Sie für Szenarien mit hoher Leistung int oder long mit @IntDef. Für Szenarien mit geringer Leistung sollten Sie eine Set<EnumType> in Betracht ziehen. Verwenden Sie byte[] für Roh-Binärdaten.

Verwenden Sie vorzugsweise android.net.Uri.

android.net.Uri ist die bevorzugte Kapselung für URIs in Android APIs.

Vermeiden Sie java.net.URI, da es beim Parsen von URIs zu streng ist, und verwenden Sie niemals java.net.URL, da die Definition von Gleichheit hier stark gestört ist.

Anmerkungen vom Typ @IntDef, @LongDef oder @StringDef ausblenden

Anmerkungen, die als @IntDef, @LongDef oder @StringDef gekennzeichnet sind, geben eine Reihe gültiger Konstanten an, die an eine API übergeben werden können. Wenn sie jedoch selbst als APIs exportiert werden, fügt der Compiler die Konstanten ein und nur die (jetzt nutzlosen) Werte bleiben im API-Stub (für die Plattform) oder JAR (für Bibliotheken) der Anmerkung.

Daher müssen die Verwendungen dieser Anmerkungen auf der Plattform mit der @hide-Anmerkung für Dokumente oder in Bibliotheken mit der @RestrictTo.Scope.LIBRARY)-Codeanmerkung gekennzeichnet werden. In beiden Fällen müssen sie mit @Retention(RetentionPolicy.SOURCE) gekennzeichnet werden, damit sie nicht in API-Stubs oder JARs erscheinen.

@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
  STREAM_TYPE_FULL_IMAGE_DATA,
  STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}

Beim Erstellen der AARs für das Plattform-SDK und die Bibliothek werden die Anmerkungen von einem Tool extrahiert und separat von den kompilierten Quellen gebündelt. Android Studio liest dieses gebündelte Format und erzwingt die Typdefinitionen.

Keine neuen Schlüssel für Anbieter von Einstellungen hinzufügen

Neue Schlüssel von Settings.Global, Settings.System oder Settings.Secure dürfen nicht freigegeben werden.

Fügen Sie stattdessen eine ordnungsgemäße Java-API für Getter und Setter in einer entsprechenden Klasse hinzu, in der Regel eine „Manager“-Klasse. Fügen Sie einen Listener-Mechanismus oder eine Übertragung hinzu, um Clients bei Bedarf über Änderungen zu benachrichtigen.

SettingsProvider-Einstellungen haben im Vergleich zu Gettern/Settern eine Reihe von Problemen:

  • Keine Typsicherheit.
  • Es gibt keine einheitliche Möglichkeit, einen Standardwert anzugeben.
  • Es gibt keine Möglichkeit, Berechtigungen anzupassen.
    • So ist es beispielsweise nicht möglich, Ihre Einstellung mit einer benutzerdefinierten Berechtigung zu schützen.
  • Es gibt keine Möglichkeit, benutzerdefinierte Logik richtig hinzuzufügen.
    • Es ist beispielsweise nicht möglich, den Wert von Einstellung A in Abhängigkeit vom Wert von Einstellung B zu ändern.

Beispiel: Settings.Secure.LOCATION_MODE gibt es schon lange, aber das Standortteam hat es zugunsten einer richtigen Java API LocationManager.isLocationEnabled() und der MODE_CHANGED_ACTION-Übertragung eingestellt. Das Team hat dadurch viel mehr Flexibilität gewonnen und die Semantik der APIs ist jetzt viel klarer.

Activity und AsyncTask nicht erweitern

AsyncTask ist ein Implementierungsdetail. Stellen Sie stattdessen einen Listener oder in androidx eine ListenableFuture API bereit.

Activity Unterklassen können nicht erstellt werden. Wenn Sie die Aktivität für Ihre Funktion verlängern, ist sie nicht mit anderen Funktionen kompatibel, bei denen Nutzer dasselbe tun müssen. Verwenden Sie stattdessen Tools wie LifecycleObserver, um die Zusammensetzung zu steuern.

getUser() des Kontexts verwenden

Für Klassen, die an eine Context gebunden sind, z. B. Elemente, die von Context.getSystemService() zurückgegeben werden, sollte der an die Context gebundene Nutzer verwendet werden, anstatt Mitglieder zu präsentieren, die auf bestimmte Nutzer ausgerichtet sind.

class FooManager {
  Context mContext;

  void fooBar() {
    mIFooBar.fooBarForUser(mContext.getUser());
  }
}
class FooManager {
  Context mContext;

  Foobar getFoobar() {
    // Bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBarForUser(Process.myUserHandle());
  }

  Foobar getFoobar() {
    // Also bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBar();
  }

  Foobar getFoobarForUser(UserHandle user) {
    mIFooBar.fooBarForUser(user);
  }
}

Ausnahme: Eine Methode kann ein Nutzerargument akzeptieren, wenn sie Werte akzeptiert, die keinen einzelnen Nutzer repräsentieren, z. B. UserHandle.ALL.

UserHandle anstelle von Ganzzahlen verwenden

UserHandle wird bevorzugt, um für Typsicherheit zu sorgen und Verwechslungen von Nutzer-IDs mit uids zu vermeiden.

Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);

Wenn es nicht anders geht, muss ein int, das eine Nutzer-ID darstellt, mit @UserIdInt gekennzeichnet werden.

Foobar getFoobarForUser(@UserIdInt int user);

Verwenden Sie Listener oder Callbacks anstelle von Intents zur Übertragung

Broadcast-Intents sind sehr leistungsfähig, haben aber zu neuen Verhaltensweisen geführt, die sich negativ auf die Systemintegrität auswirken können. Daher sollten neue Broadcast-Intents mit Bedacht hinzugefügt werden.

Hier sind einige konkrete Bedenken, die uns dazu veranlassen, die Einführung neuer Intents für die Übertragung zu erschweren:

  • Wenn Übertragungen ohne das Flag FLAG_RECEIVER_REGISTERED_ONLY gesendet werden, werden alle Apps erzwungen gestartet, die noch nicht ausgeführt werden. Das kann zwar manchmal beabsichtigt sein, aber auch dazu führen, dass Dutzende von Apps gleichzeitig gestartet werden, was sich negativ auf die Systemintegrität auswirken kann. Wir empfehlen, alternative Strategien wie JobScheduler zu verwenden, um besser zu koordinieren, wann verschiedene Voraussetzungen erfüllt sind.

  • Beim Senden von Übertragungen können die an Apps gesendeten Inhalte nur eingeschränkt gefiltert oder angepasst werden. Dies erschwert oder verhindert es, auf zukünftige Datenschutzbedenken zu reagieren oder Verhaltensänderungen basierend auf dem Ziel-SDK der empfangenden App vorzunehmen.

  • Da Broadcast-Warteschlangen eine gemeinsame Ressource sind, können sie überlastet werden und es kann zu Verzögerungen bei der Übermittlung des Ereignisses kommen. Wir haben mehrere Broadcast-Warteschlangen in der Praxis beobachtet, die eine End-to-End-Latenz von 10 Minuten oder länger haben.

Aus diesen Gründen empfehlen wir, für neue Funktionen Listener oder Callbacks oder andere Funktionen wie JobScheduler anstelle von Broadcast-Intents zu verwenden.

Wenn Broadcast-Intents weiterhin das ideale Design sind, sollten Sie die folgenden Best Practices beachten:

  • Verwenden Sie nach Möglichkeit Intent.FLAG_RECEIVER_REGISTERED_ONLY, um Ihren Stream auf bereits laufende Apps zu beschränken. ACTION_SCREEN_ON verwendet dieses Design beispielsweise, um das Aktivieren von Apps zu vermeiden.
  • Verwenden Sie nach Möglichkeit Intent.setPackage() oder Intent.setComponent(), um die Auslieferung auf eine bestimmte App auszurichten. In ACTION_MEDIA_BUTTON wird dieses Design beispielsweise verwendet, um den Fokus auf die Wiedergabesteuerung der aktuellen App zu legen.
  • Definieren Sie Ihre Übertragung nach Möglichkeit als <protected-broadcast>, um zu verhindern, dass schädliche Apps sich als Betriebssystem ausgeben.

Intents in systemgebundenen Entwicklerdiensten

Dienste, die vom Entwickler erweitert und vom System gebunden werden sollen, z. B. abstrakte Dienste wie NotificationListenerService, können auf eine Intent-Aktion des Systems reagieren. Solche Dienste sollten die folgenden Kriterien erfüllen:

  1. Definieren Sie in der Klasse eine SERVICE_INTERFACE-Stringkonstante mit dem voll qualifizierten Klassennamen des Dienstes. Diese Konstante muss mit @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) annotiert sein.
  2. Erläutern Sie in der Klasse, dass Entwickler ihrer AndroidManifest.xml eine <intent-filter> hinzufügen müssen, um Intents von der Plattform zu erhalten.
  3. Wir empfehlen Ihnen dringend, eine Berechtigung auf Systemebene hinzuzufügen, um zu verhindern, dass schädliche Apps Intents an Entwicklerdienste senden.

Kotlin-Java-Interoperabilität

Eine vollständige Liste der Richtlinien finden Sie im offiziellen Android-Leitfaden zur Kotlin-Java-Interoperabilität. Ausgewählte Richtlinien wurden in diesen Leitfaden kopiert, um die Auffindbarkeit zu verbessern.

API-Sichtbarkeit

Einige Kotlin-APIs wie suspend funs sind nicht für Java-Entwickler gedacht. Versuchen Sie jedoch nicht, die sprachspezifische Sichtbarkeit mit @JvmSynthetic zu steuern, da dies Auswirkungen auf die Darstellung der API in Debuggern hat, was das Debuggen erschwert.

Weitere Informationen finden Sie im Leitfaden zur Kotlin-Java-Interoperabilität oder im Leitfaden für asynchrone Funktionen.

Companion-Objekte

In Kotlin werden statische Mitglieder mit companion object freigegeben. In einigen Fällen werden diese von Java in einer inneren Klasse namens Companion und nicht in der enthaltenden Klasse angezeigt. Companion-Klassen werden in API-Textdateien möglicherweise als leere Klassen angezeigt. Das ist normal.

Um die Kompatibilität mit Java zu maximieren, sollten Sie die nicht konstanten Felder von Companion-Objekten mit @JvmField und die öffentlichen Funktionen mit @JvmStatic annotieren, um sie direkt in der enthaltenden Klasse verfügbar zu machen.

companion object {
  @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
  @JvmStatic fun fromPointF(pointf: PointF) {
    /* ... */
  }
}

Entwicklung der Android-Plattform-APIs

In diesem Abschnitt werden Richtlinien beschrieben, welche Arten von Änderungen Sie an vorhandenen Android APIs vornehmen können und wie Sie diese Änderungen implementieren sollten, um die Kompatibilität mit vorhandenen Apps und Codebases zu maximieren.

Nicht abwärtskompatible Änderungen

Vermeiden Sie Änderungen an den Oberflächen der finalisierten öffentlichen APIs, die zu Binärfehlern führen. Diese Arten von Änderungen führen in der Regel zu Fehlern, wenn make update-api ausgeführt wird. Es kann jedoch Grenzfälle geben, die von der API-Prüfung von Metalava nicht erkannt werden. Im Zweifelsfall finden Sie im Leitfaden Evolving Java-based APIs der Eclipse Foundation eine ausführliche Erklärung dazu, welche Arten von API-Änderungen in Java kompatibel sind. Änderungen an versteckten APIs (z. B. System-APIs), die zu Binärinkompatibilitäten führen, sollten dem Zyklus „Verwerfen/Ersetzen“ folgen.

Nicht abwärtskompatible Änderungen

Wir raten von Änderungen ab, die die Funktionsweise der Quelle beeinträchtigen, auch wenn sie nicht zu einer Binäränderung führen. Ein Beispiel für eine binärkompatible, aber quellenbrüchende Änderung ist das Hinzufügen eines Generics zu einer vorhandenen Klasse. Dies ist binärkompatibel, kann aber aufgrund von Vererbung oder mehrdeutigen Verweisfehlern zu Kompilierungsfehlern führen. Änderungen, die die Quelle brechen, führen beim Ausführen von make update-api nicht zu Fehlern. Sie müssen daher die Auswirkungen von Änderungen an vorhandenen API-Signaturen genau kennen.

In einigen Fällen sind Änderungen, die die Quellcode-Kompatibilität beeinträchtigen, erforderlich, um die Nutzerfreundlichkeit oder Coderichtigkeit zu verbessern. Wenn Sie Java-Quellen beispielsweise Anmerkungen zur Null-Zulässigkeit hinzufügen, wird die Interoperabilität mit Kotlin-Code verbessert und die Wahrscheinlichkeit von Fehlern verringert. Dies erfordert jedoch häufig Änderungen – manchmal erhebliche Änderungen – am Quellcode.

Änderungen an privaten APIs

Sie können APIs, die mit @TestApi gekennzeichnet sind, jederzeit ändern.

Sie müssen APIs, die mit @SystemApi annotiert sind, drei Jahre lang aufbewahren. Sie müssen eine System-API gemäß dem folgenden Zeitplan entfernen oder umstrukturieren:

  • API y – hinzugefügt
  • API y+1 – Einstellung
    • Markieren Sie den Code mit @Deprecated.
    • Fügen Sie Ersetzungen hinzu und verlinken Sie im Javadoc für den veralteten Code mithilfe der @deprecated-Anmerkung auf den Ersatz.
    • Melden Sie während des Entwicklungszyklus Fehler für interne Nutzer und teilen Sie ihnen mit, dass die API eingestellt wird. So lässt sich prüfen, ob die Ersatz-APIs geeignet sind.
  • API y+2 – Vorläufige Entfernung
    • Markieren Sie den Code mit @removed.
    • Optional: throw oder no-op für Apps, die auf die aktuelle SDK-Ebene für die Version ausgerichtet sind.
  • API y+3 – Forced removal
    • Entfernen Sie den Code vollständig aus dem Stammbaum.

Einstellung

Wir betrachten die Einstellung als API-Änderung und sie kann in einer Hauptversion (z. B. einer Buchstabenversion) erfolgen. Verwenden Sie die @Deprecated-Quellenannotierung und die @deprecated <summary>-Dokumentannotierung zusammen, wenn Sie APIs einstellen. Ihre Zusammenfassung muss eine Migrationsstrategie enthalten. Diese Strategie kann einen Link zu einer Ersatz-API enthalten oder erklären, warum Sie die API nicht verwenden sollten:

/**
 * Simple version of ...
 *
 * @deprecated Use the {@link androidx.fragment.app.DialogFragment}
 *             class with {@link androidx.fragment.app.FragmentManager}
 *             instead.
 */
@Deprecated
public final void showDialog(int id)

Sie müssen auch in XML definierte und in Java freigegebene APIs mit einer Zusammenfassung aufheben, einschließlich Attributen und stilbaren Eigenschaften, die in der Klasse android.R freigegeben sind:

<!-- Attribute whether the accessibility service ...
     {@deprecated Not used by the framework}
 -->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />

Wann sollte eine API eingestellt werden?

Eine Einstellung ist am nützlichsten, um die Verwendung einer API in neuem Code zu verhindern.

Außerdem müssen Sie APIs als @deprecated kennzeichnen, bevor sie @removed werden. Das ist jedoch kein starkes Argument für Entwickler, von einer API zu migrieren, die sie bereits verwenden.

Berücksichtigen Sie vor der Einstellung einer API die Auswirkungen auf Entwickler. Die Auswirkungen der Einstellung einer API sind:

  • javac gibt während der Kompilierung eine Warnung aus.
    • Warnungen zur Einstellung können nicht global unterdrückt oder als Baseline festgelegt werden. Entwickler, die -Werror verwenden, müssen daher jede Verwendung einer eingestellten API einzeln korrigieren oder unterdrücken, bevor sie ihre kompilierte SDK-Version aktualisieren können.
    • Warnungen zur Einstellung bei Importen von eingestellten Klassen können nicht unterdrückt werden. Daher müssen Entwickler den vollständig qualifizierten Klassennamen für jede Verwendung einer eingestellten Klasse einfügen, bevor sie ihre kompilierte SDK-Version aktualisieren können.
  • In der Dokumentation zu d.android.com wird eine entsprechende Ankündigung angezeigt.
  • In IDEs wie Android Studio wird auf der Website zur API-Nutzung eine Warnung angezeigt.
  • IDEs können die API im Autocomplete-Verfahren herabstufen oder ausblenden.

Daher kann die Einstellung einer API Entwickler, die am meisten auf die Codequalität achten (d. h. diejenigen, die -Werror verwenden), davon abhalten, neue SDKs zu verwenden. Entwickler, die sich nicht um Warnungen in ihrem vorhandenen Code kümmern, ignorieren die Einstellungsmeldungen wahrscheinlich vollständig.

Ein SDK, das eine große Anzahl von eingestellten Funktionen einführt, verschlimmert beide Fälle.

Aus diesem Grund empfehlen wir, APIs nur in folgenden Fällen einzustellen:

  • Wir planen, die API in einer zukünftigen Version @remove.
  • Die API-Nutzung führt zu einem falschen oder nicht definierten Verhalten, das wir nicht beheben können, ohne die Kompatibilität zu brechen.

Wenn Sie eine API einstellen und durch eine neue ersetzen, empfehlen wir Ihnen dringend, einer Jetpack-Bibliothek wie androidx.core eine entsprechende Kompatibilitäts-API hinzuzufügen, um die Unterstützung sowohl alter als auch neuer Geräte zu vereinfachen.

Wir empfehlen nicht, APIs einzustellen, die in aktuellen und zukünftigen Releases wie vorgesehen funktionieren:

/**
 * ...
 * @deprecated Use {@link #doThing(int, Bundle)} instead.
 */
@Deprecated
public void doThing(int action) {
  ...
}

public void doThing(int action, @Nullable Bundle extras) {
  ...
}

Eine Einstellung ist in folgenden Fällen angebracht:

/**
 * ...
 * @deprecated No longer displayed in the status bar as of API 21.
 */
@Deprecated
public RemoteViews tickerView;

Änderungen an veralteten APIs

Sie müssen das Verhalten eingestellter APIs beibehalten. Das bedeutet, dass die Testimplementierungen unverändert bleiben müssen und die Tests auch nach der Einstellung der API bestanden werden müssen. Wenn für die API keine Tests vorhanden sind, sollten Sie diese hinzufügen.

Erweitern Sie nicht eingestellte API-Oberflächen in zukünftigen Releases. Sie können einer vorhandenen eingestellten API Lint-Korrektheitsanmerkungen (z. B. @Nullable) hinzufügen, aber keine neuen APIs.

Fügen Sie neue APIs nicht als eingestellt hinzu. Wenn APIs hinzugefügt und anschließend innerhalb eines Prerelease-Zyklus eingestellt wurden (d. h. sie werden anfangs als eingestellt in der öffentlichen API-Oberfläche angezeigt), müssen Sie sie entfernen, bevor Sie die API fertigstellen.

Weiche Entfernung

Das vorläufige Entfernen ist eine quellenbrechende Änderung und sollte in öffentlichen APIs vermieden werden, es sei denn, der API-Rat genehmigt sie ausdrücklich. Bei System-APIs müssen Sie die API für die Dauer einer Hauptversion veraltigen, bevor sie schrittweise eingestellt wird. Entfernen Sie alle Verweise auf die APIs aus den Dokumenten und verwenden Sie die Anmerkung @removed <summary>, wenn Sie APIs weich entfernen. Ihre Zusammenfassung muss den Grund für die Entfernung enthalten und kann eine Migrationsstrategie umfassen, wie wir unter Einstellung erläutert haben.

Das Verhalten von APIs, die nur teilweise entfernt wurden, kann unverändert beibehalten werden, muss aber vor allem beibehalten werden, damit bestehende Aufrufer beim Aufruf der API nicht abstürzen. In einigen Fällen kann das bedeuten, dass das Verhalten beibehalten wird.

Die Testabdeckung muss beibehalten werden, aber der Inhalt der Tests muss möglicherweise geändert werden, um Verhaltensänderungen zu berücksichtigen. Bei Tests muss weiterhin geprüft werden, ob vorhandene Aufrufer zur Laufzeit nicht abstürzen. Sie können das Verhalten von APIs, die nur teilweise entfernt wurden, beibehalten. Wichtiger ist jedoch, dass Sie es so beibehalten, dass bestehende Aufrufer beim Aufruf der API nicht abstürzen. In einigen Fällen kann das bedeuten, dass das Verhalten beibehalten wird.

Sie müssen die Testabdeckung beibehalten, aber der Inhalt der Tests muss möglicherweise geändert werden, um Verhaltensänderungen zu berücksichtigen. Bei Tests muss weiterhin geprüft werden, ob vorhandene Aufrufer zur Laufzeit nicht abstürzen.

Auf technischer Ebene entfernen wir die API mithilfe der Javadoc-Anmerkung @remove aus dem SDK-Stub-JAR und dem Compilezeit-Classpath. Sie ist jedoch weiterhin im Laufzeit-Classpath vorhanden, ähnlich wie bei @hide APIs:

/**
 * Ringer volume. This is ...
 *
 * @removed Not functional since API 2.
 */
public static final String VOLUME_RING = ...

Für App-Entwickler wird die API nicht mehr im automatischen Vervollständigungsvorschlag angezeigt und Quellcode, der auf die API verweist, wird nicht kompiliert, wenn die compileSdk mit dem SDK übereinstimmt oder höher ist, aus dem die API entfernt wurde. Der Quellcode wird jedoch weiterhin erfolgreich mit früheren SDKs kompiliert und Binärdateien, die auf die API verweisen, funktionieren weiterhin.

Bestimmte API-Kategorien dürfen nicht vorläufig entfernt werden. Bestimmte API-Kategorien dürfen nicht per Soft-Deaktivierung entfernt werden.

Abstrakte Methoden

Abstrakte Methoden von Klassen, die Entwickler erweitern könnten, dürfen nicht vorrübergehend entfernt werden. Dadurch ist es Entwicklern nicht möglich, die Klasse auf alle SDK-Ebenen auszuweiten.

In seltenen Fällen, in denen es niemals und auch in Zukunft nicht möglich sein wird, dass Entwickler eine Klasse erweitern, können Sie abstrakte Methoden trotzdem „weich“ entfernen.

Hard Removal

Das vollständige Entfernen ist eine binäre Änderung und sollte in öffentlichen APIs niemals vorkommen.

Nicht empfohlene Anmerkung

Mit der Anmerkung @Discouraged geben wir an, dass wir in den meisten Fällen (> 95%) keine API empfehlen. APIs, die nicht mehr empfohlen werden, unterscheiden sich von veralteten APIs dadurch, dass es einen eng gefassten kritischen Anwendungsfall gibt, der eine Einstellung verhindert. Wenn Sie eine API als nicht empfohlen kennzeichnen, müssen Sie eine Erklärung und eine alternative Lösung angeben:

@Discouraged(message = "Use of this function is discouraged because resource
                        reflection makes it harder to perform build
                        optimizations and compile-time verification of code. It
                        is much more efficient to retrieve resources by
                        identifier (such as `R.foo.bar`) than by name (such as
                        `getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
    return mResourcesImpl.getIdentifier(name, defType, defPackage);
}

Sie sollten keine neuen APIs hinzufügen, die als nicht empfohlen gekennzeichnet sind.

Änderungen am Verhalten vorhandener APIs

In einigen Fällen kann es sinnvoll sein, das Implementierungsverhalten einer vorhandenen API zu ändern. In Android 7.0 haben wir beispielsweise DropBoxManager verbessert, um Entwicklern klar mitzuteilen, wenn sie versucht haben, Ereignisse zu posten, die zu groß sind, um über Binder gesendet zu werden.

Um Probleme mit bestehenden Apps zu vermeiden, empfehlen wir jedoch dringend, für ältere Apps ein sicheres Verhalten beizubehalten. Bisher haben wir diese Verhaltensänderungen basierend auf der ApplicationInfo.targetSdkVersion der App überwacht. Vor Kurzem haben wir jedoch die Verwendung des App Compatibility Frameworks eingeführt. Hier ist ein Beispiel dafür, wie Sie mit diesem neuen Framework eine Verhaltensänderung implementieren:

import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;

public class MyClass {
  @ChangeId
  // This means the change will be enabled for target SDK R and higher.
  @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
  // Use a bug number as the value, provide extra detail in the bug.
  // FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
  static final long FOO_NOW_DOES_X = 123456789L;

  public void doFoo() {
    if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
      // do the new thing
    } else {
      // do the old thing
    }
  }
}

Mit diesem App Compatibility Framework-Design können Entwickler bestimmte Verhaltensänderungen während der Vorab- und Betaversionen vorübergehend deaktivieren, um ihre Apps zu debuggen, anstatt sich gleichzeitig an Dutzende von Verhaltensänderungen anpassen zu müssen.

Vorwärtskompatibilität

Die Zukunftssicherheit ist eine Designeigenschaft, die es einem System ermöglicht, Eingaben zu akzeptieren, die für eine spätere Version des Systems bestimmt sind. Beim API-Design müssen Sie sowohl auf das ursprüngliche Design als auch auf zukünftige Änderungen achten, da Entwickler erwarten, dass Code einmal geschrieben, einmal getestet und überall problemlos ausgeführt wird.

Die folgenden Ursachen führen zu den häufigsten Problemen mit der zukünftigen Kompatibilität unter Android:

  • Einem Set (z. B. @IntDef oder enum), das zuvor als vollständig angenommen wurde, neue Konstanten hinzufügen (z. B. wenn switch eine default hat, die eine Ausnahme auslöst).
  • Unterstützung für eine Funktion hinzufügen, die nicht direkt in der API-Oberfläche erfasst wird (z. B. Unterstützung für die Zuweisung von Ressourcen vom Typ ColorStateList in XML, wo zuvor nur <color>-Ressourcen unterstützt wurden).
  • Lockerung der Einschränkungen für Laufzeitprüfungen, z. B. Entfernung einer requireNotNull()-Prüfung, die in niedrigeren Versionen vorhanden war.

In all diesen Fällen stellen Entwickler erst zur Laufzeit fest, dass etwas nicht stimmt. Schlimmer noch: Sie könnten es durch Absturzberichte von älteren Geräten im Einsatz erfahren.

Außerdem sind diese Fälle alle technisch gültige API-Änderungen. Sie beeinträchtigen nicht die Binär- oder Quellkompatibilität und API lint erkennt diese Probleme nicht.

Daher müssen API-Designer beim Ändern bestehender Klassen sehr vorsichtig vorgehen. Stellen Sie sich die Frage: „Wird diese Änderung dazu führen, dass Code, der nur mit der neuesten Version der Plattform geschrieben und getestet wurde, bei niedrigeren Versionen fehlschlägt?“

XML-Schemas

Wenn ein XML-Schema als stabile Schnittstelle zwischen Komponenten dient, muss dieses Schema explizit angegeben und ähnlich wie andere Android-APIs abwärtskompatibel weiterentwickelt werden. Beispielsweise muss die Struktur von XML-Elementen und ‑Attributen ähnlich wie bei Methoden und Variablen auf anderen Android API-Oberflächen beibehalten werden.

Einstellung von XML

Wenn Sie ein XML-Element oder -Attribut einstellen möchten, können Sie die Markierung xs:annotation hinzufügen. Sie müssen jedoch alle vorhandenen XML-Dateien weiterhin unterstützen, indem Sie dem üblichen @SystemApi-Entwicklungszyklus folgen.

<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Elementtypen müssen beibehalten werden

Schemas unterstützen die Elemente sequence, choice und all als untergeordnete Elemente des Elements complexType. Diese untergeordneten Elemente unterscheiden sich jedoch in Anzahl und Reihenfolge ihrer untergeordneten Elemente. Eine Änderung eines vorhandenen Typs wäre daher nicht kompatibel.

Wenn Sie einen vorhandenen Typ ändern möchten, sollten Sie den alten Typ verwerfen und einen neuen Typ einführen, um ihn zu ersetzen.

<!-- Original "sequence" value -->
<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- New "choice" value -->
<xs:element name="fooChoice">
    <xs:complexType>
        <xs:choice>
            <xs:element name="name" type="xs:string"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

Mainline-spezifische Muster

Mainline ist ein Projekt, mit dem Subsysteme („Mainline-Module“) des Android-Betriebssystems einzeln aktualisiert werden können, anstatt das gesamte System-Image zu aktualisieren.

Mainline-Module müssen von der Kernplattform getrennt werden. Das bedeutet, dass alle Interaktionen zwischen den einzelnen Modulen und dem Rest der Welt über formale (öffentliche oder systemspezifische) APIs erfolgen müssen.

Es gibt bestimmte Designmuster, die Mainline-Module einhalten sollten. In diesem Abschnitt werden sie beschrieben.

Das Muster <Module>FrameworkInitializer

Wenn ein Hauptmodul @SystemService-Klassen (z. B. JobScheduler) bereitstellen muss, verwenden Sie das folgende Muster:

  • Stellen Sie eine <YourModule>FrameworkInitializer-Klasse aus Ihrem Modul bereit. Diese Klasse muss sich in $BOOTCLASSPATH befinden. Beispiel: StatsFrameworkInitializer

  • Markieren Sie sie mit @SystemApi(client = MODULE_LIBRARIES).

  • Fügen Sie eine public static void registerServiceWrappers()-Methode hinzu.

  • Verwenden Sie SystemServiceRegistry.registerContextAwareService(), um eine Service Manager-Klasse zu registrieren, wenn eine Referenz auf eine Context erforderlich ist.

  • Verwenden Sie SystemServiceRegistry.registerStaticService(), um eine Dienstmanagerklasse zu registrieren, wenn keine Referenz auf eine Context erforderlich ist.

  • Rufen Sie die Methode registerServiceWrappers() über den statischen Initializer von SystemServiceRegistry auf.

Das Muster <Module>ServiceManager

Normalerweise wird ServiceManager verwendet, um Systemdienst-Binderobjekte zu registrieren oder Verweise darauf abzurufen. Mainline-Module können es jedoch nicht verwenden, da es ausgeblendet ist. Diese Klasse ist ausgeblendet, da Mainline-Module keine Systemdienst-Binder-Objekte registrieren oder darauf verweisen dürfen, die von der statischen Plattform oder anderen Modulen freigegeben werden.

Mainline-Module können stattdessen das folgende Muster verwenden, um Verweise auf Binderdienste zu registrieren und abzurufen, die im Modul implementiert sind.

  • Erstellen Sie eine <YourModule>ServiceManager-Klasse, die dem Design von TelephonyServiceManager folgt.

  • Stellen Sie die Klasse als @SystemApi bereit. Wenn Sie nur über $BOOTCLASSPATH- oder Systemserverklassen darauf zugreifen müssen, können Sie @SystemApi(client = MODULE_LIBRARIES) verwenden. Andernfalls ist auch @SystemApi(client = PRIVILEGED_APPS) möglich.

  • Dieser Kurs würde Folgendes umfassen:

    • Ein versteckter Konstruktor, der nur vom statischen Plattformcode instanziiert werden kann.
    • Öffentliche Gettermethoden, die eine ServiceRegisterer-Instanz für einen bestimmten Namen zurückgeben. Wenn Sie ein Binder-Objekt haben, benötigen Sie eine Get-Methode. Wenn Sie zwei haben, benötigen Sie zwei Getter.
    • Instanzieren Sie diese Klasse in ActivityThread.initializeMainlineModules() und übergeben Sie sie an eine statische Methode, die von Ihrem Modul freigegeben wird. Normalerweise fügen Sie Ihrer FrameworkInitializer-Klasse eine statische @SystemApi(client = MODULE_LIBRARIES)-API hinzu, die sie übernimmt.

Dieses Muster würde anderen Mainline-Modulen den Zugriff auf diese APIs verwehren, da andere Module keine Instanz von <YourModule>ServiceManager abrufen können, obwohl die get()- und register()-APIs für sie sichtbar sind.

So erhält die Telefonie eine Referenz auf den Telefoniedienst: Link zur Codesuche.

Wenn Sie ein Dienstbinderobjekt in nativem Code implementieren, verwenden Sie die AServiceManager nativen APIs. Diese APIs entsprechen den ServiceManager Java APIs, die nativen APIs sind jedoch direkt für Mainline-Module verfügbar. Verwenden Sie sie nicht, um Bindungsobjekte zu registrieren oder darauf zu verweisen, die nicht zu Ihrem Modul gehören. Wenn Sie ein Binder-Objekt aus nativen Code freigeben, ist für Ihre <YourModule>ServiceManager.ServiceRegisterer keine register()-Methode erforderlich.

Berechtigungsdefinitionen in Mainline-Modulen

Mainline-Module, die APKs enthalten, können (benutzerdefinierte) Berechtigungen in ihrer APK-AndroidManifest.xml auf die gleiche Weise wie ein reguläres APK definieren.

Wenn die definierte Berechtigung nur intern innerhalb eines Moduls verwendet wird, sollte der Name der Berechtigung mit dem Namen des APK-Pakets vorangestellt werden, z. B.:

<permission
    android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
    android:protectionLevel="signature" />

Wenn die definierte Berechtigung als Teil einer aktualisierbaren Plattform-API für andere Apps bereitgestellt werden soll, muss der Berechtigungsname mit „android.permission.“ beginnen. (wie bei jeder statischen Plattformberechtigung) und den Namen des Modulpakets, um anzuzeigen, dass es sich um eine Plattform-API aus einem Modul handelt, und gleichzeitig Namenskonflikte zu vermeiden, z. B.:

<permission
    android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
    android:label="@string/active_calories_burned_read_content_description"
    android:protectionLevel="dangerous"
    android:permissionGroup="android.permission-group.HEALTH" />

Das Modul kann diesen Berechtigungsnamen dann als API-Konstante in seiner API-Oberfläche verfügbar machen, z. B. HealthPermissions.READ_ACTIVE_CALORIES_BURNED.