Diese Seite soll Entwicklern als Leitfaden dienen, um die allgemeinen Grundsätze zu verstehen, die der API Council bei API-Überprüfungen durchsetzt.
Entwickler sollten nicht nur diese Richtlinien beim Schreiben von APIs befolgen, sondern auch das Tool API Lint ausführen, das viele dieser Regeln in Prüfungen codiert, die für APIs ausgeführt werden.
Das ist die Anleitung zu den Regeln, die von diesem Lint-Tool eingehalten werden, sowie allgemeine Hinweise zu Regeln, die nicht mit hoher Genauigkeit in diesem Tool codiert werden können.
API Lint-Tool
API Lint ist in das statische Analysetool Metalava integriert und wird automatisch während der Validierung in CI ausgeführt. Sie können den Test manuell über einen lokalen Plattform-Checkout mit m
checkapi
oder über einen lokalen AndroidX-Checkout 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 auf dieser Seite festgelegten Richtlinien werden fortlaufend 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 besser sein, wenn eine neue API mit vorhandenen APIs konsistent bleibt, anstatt sich strikt an die Richtlinien zu halten.
Wenden Sie Ihr Urteilsvermögen an und wenden Sie sich an den API Council, wenn schwierige Fragen zu einer API geklärt oder Richtlinien aktualisiert werden müssen.
API-Grundlagen
Diese Kategorie bezieht sich auf die Kernaspekte einer Android-API.
Alle APIs müssen implementiert werden
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 verfügbar gemacht werden. Führen Sie API-Stubs nicht mit der Implementierung zusammen, da diese erst später erfolgt.
API-Oberflächen ohne Implementierungen haben mehrere Probleme:
- Es gibt keine Garantie dafür, dass eine geeignete oder vollständige Oberfläche freigelegt wurde. Solange eine API nicht von Clients getestet oder verwendet wird, kann nicht überprüft werden, ob ein Client über die entsprechenden APIs verfügt, um die Funktion nutzen zu können.
- APIs ohne Implementierung können in Entwicklervorschauen nicht getestet werden.
- APIs ohne Implementierung können nicht im CTS getestet werden.
Alle APIs müssen getestet werden
Dies entspricht den CTS-Anforderungen der Plattform, den AndroidX-Richtlinien und dem allgemeinen Grundsatz, dass APIs implementiert werden müssen.
Durch das Testen von API-Oberflächen wird sichergestellt, dass die API-Oberfläche nutzbar ist und erwartete Anwendungsfälle berücksichtigt wurden. Es reicht nicht aus, nur zu prüfen, ob die API vorhanden ist. Das Verhalten der API selbst muss getestet werden.
Wenn Sie eine neue API hinzufügen, sollten Sie entsprechende Tests in derselben CL oder demselben Gerrit-Thema einfügen.
APIs sollten außerdem testbar sein. Sie sollten die Frage „Wie testet ein App-Entwickler Code, der Ihre API verwendet?“ beantworten können.
Alle APIs müssen dokumentiert werden
Die Dokumentation ist ein wichtiger Bestandteil der API-Nutzerfreundlichkeit. Die Syntax einer API-Oberfläche mag offensichtlich erscheinen, aber neue Clients verstehen die Semantik, das Verhalten oder den Kontext der API nicht.
Alle generierten APIs müssen den Richtlinien entsprechen.
APIs, die von Tools generiert werden, müssen denselben API-Richtlinien wie manuell geschriebener Code entsprechen.
Tools, die nicht für die Generierung von APIs empfohlen werden:
AutoValue
: verstößt auf verschiedene Weise gegen die Richtlinien, z. B. ist es mit der Funktionsweise von AutoValue nicht möglich, Klassen mit endgültigen Werten oder endgültige Builder zu implementieren.
Codestil
Diese Kategorie bezieht sich auf den allgemeinen Codestil, den Entwickler verwenden sollten, insbesondere beim Schreiben öffentlicher APIs.
Standard-Programmierkonventionen einhalten, sofern nicht anders angegeben
Die Android-Programmierkonventionen für externe Mitwirkende sind hier dokumentiert:
https://source.android.com/source/code-style.html
Im Allgemeinen halten wir uns an die Standard-Java- und Kotlin-Programmierkonventionen.
Akronyme dürfen in Methodennamen nicht großgeschrieben werden
Beispiel: Der Methodenname sollte runCtsTests
und nicht runCTSTests
lauten.
Namen sollten nicht mit „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 ableiten
Durch die Vererbung werden API-Elemente in Ihrer Unterklasse verfügbar gemacht, die möglicherweise nicht geeignet sind.
Eine neue öffentliche Unterklasse von FrameLayout
sieht beispielsweise so aus: FrameLayout
plus die neuen Verhaltensweisen und API-Elemente. Wenn die geerbte API für Ihren Anwendungsfall nicht geeignet ist, erben Sie von einer Klasse weiter oben im Baum, z. B. ViewGroup
oder View
.
Wenn Sie versucht sind, Methoden aus der Basisklasse zu überschreiben, um UnsupportedOperationException
auszulösen, sollten Sie überdenken, welche Basisklasse Sie verwenden.
Basissammlungsklassen verwenden
Unabhängig davon, ob eine Sammlung als Argument verwendet oder als Wert zurückgegeben wird, sollte immer die Basisklasse der spezifischen Implementierung vorgezogen werden (z. B. List<Foo>
anstelle von ArrayList<Foo>
zurückgeben).
Verwenden Sie eine Basisklasse, die geeignete 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 vorzugsweise unveränderliche Sammlungen. Weitere Informationen finden Sie unter Änderbarkeit von Sammlungen.
Abstrakte Klassen im Vergleich zu Schnittstellen
Java 8 bietet Unterstützung für Standard-Schnittstellenmethoden, sodass API-Entwickler Schnittstellen Methoden hinzufügen können, ohne die binäre Kompatibilität zu beeinträchtigen. Der 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 Standard-Schnittstellenmethoden als Aufrufe anderer Schnittstellenmethoden implementiert werden können.
Wenn für die Standardimplementierung ein Konstruktor oder ein interner Status 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 zur besseren Übersichtlichkeit FooService
genannt werden:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Generische Suffixe
Vermeiden Sie die Verwendung generischer Klassennamensuffixe wie Helper
und Util
für Sammlungen von Hilfsmethoden. Stattdessen sollten Sie die Methoden direkt in die zugehörigen Klassen oder in Kotlin-Erweiterungsfunktionen einfügen.
Wenn Methoden mehrere Klassen umfassen, geben Sie der enthaltenden Klasse einen aussagekräftigen Namen, der ihre Funktion erklärt.
In sehr wenigen Fällen kann die Verwendung des Suffixes Helper
angemessen sein:
- Wird für die Zusammensetzung des Standardverhaltens verwendet
- Möglicherweise wird vorhandenes Verhalten an neue Klassen delegiert.
- Möglicherweise ist ein persistenter Status erforderlich
- Dazu gehört in der Regel
View
Wenn beispielsweise für das Backporting von Tooltips der mit einem View
verknüpfte Status beibehalten und mehrere Methoden für das View
aufgerufen werden müssen, um das Backport zu installieren, wäre TooltipHelper
ein akzeptabler Klassenname.
IDL-generierten Code nicht direkt als öffentliche APIs verfügbar machen
IDL-generierter Code sollte als Implementierungsdetail betrachtet werden. Dazu gehören Protobuf, Sockets, FlatBuffers oder jede andere API-Oberfläche, die nicht Java oder NDK ist. Die meisten IDL in Android sind jedoch in AIDL, daher konzentriert sich diese Seite auf AIDL.
Generierte AIDL-Klassen entsprechen nicht den Anforderungen des API-Styleguides (z. B. kann keine Überladung verwendet werden) und das AIDL-Tool ist nicht explizit darauf ausgelegt, die Kompatibilität der Sprach-API aufrechtzuerhalten. Daher können Sie sie 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 Ebene ermöglicht die erforderliche Abwärtskompatibilität. Möglicherweise müssen Sie beispielsweise den internen Aufrufen neue Argumente hinzufügen oder den IPC-Traffic durch Batching oder Streaming, die Verwendung von gemeinsamem Speicher oder Ähnliches optimieren. Keine dieser Aktionen ist möglich, wenn Ihre AIDL-Schnittstelle auch die öffentliche API ist.
Stellen Sie beispielsweise FooService
nicht direkt als öffentliche API bereit:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
Umschließen Sie stattdessen die Binder
-Schnittstelle mit einer Manager- oder anderen Klasse:
/**
* @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 benötigt wird, kann die interne Schnittstelle minimal sein und der öffentlichen API können praktische Überladungen hinzugefügt werden. Mit der Wrapping-Ebene können Sie auch andere Probleme mit der Abwärtskompatibilität beheben, wenn sich die Implementierung weiterentwickelt:
/**
* @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), bedeutet die Anforderung einer stabilen, veröffentlichten und versionierten IPC-Schnittstelle, dass es viel schwieriger ist, die Schnittstelle selbst weiterzuentwickeln. Es ist jedoch weiterhin sinnvoll, eine Wrapper-Ebene zu verwenden, um anderen API-Richtlinien zu entsprechen und die Verwendung derselben öffentlichen API für eine neue Version der IPC-Schnittstelle zu vereinfachen, falls dies jemals erforderlich wird.
Keine Roh-Binder-Objekte in der öffentlichen API verwenden
Ein Binder
-Objekt hat keine eigenständige Bedeutung und sollte daher nicht in öffentlichen APIs verwendet werden. Ein häufiger Anwendungsfall ist die Verwendung von Binder
oder IBinder
als Token, da es Identitätssemantik hat. Verwenden Sie anstelle eines Rohobjekts vom Typ Binder
eine Wrapper-Tokenklasse.
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() {...}
}
Manager-Klassen müssen final sein
Manager-Klassen sollten als final
deklariert werden. Manager-Klassen kommunizieren mit Systemdiensten und sind der einzige Interaktionspunkt. Da keine Anpassung erforderlich ist, deklarieren Sie sie als final
.
CompletableFuture oder Future nicht verwenden
java.util.concurrent.CompletableFuture
hat eine große API-Oberfläche, die eine beliebige Änderung des zukünftigen Werts ermöglicht, und fehleranfällige Standardeinstellungen.
Umgekehrt fehlt java.util.concurrent.Future
das nicht blockierende Listening, was die Verwendung mit asynchronem Code erschwert.
Verwenden Sie im Plattformcode und in Low-Level-Bibliotheks-APIs, die sowohl von Kotlin als auch von Java verwendet werden, vorzugsweise eine Kombination aus einem Completion-Callback, Executor
und, falls die API die Abbrechen-Funktion unterstützt, CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
Wenn Sie Kotlin als Ziel verwenden, sollten Sie suspend
-Funktionen bevorzugen.
suspend fun asyncLoadFoo(): Foo
In Java-spezifische Integrationsbibliotheken können Sie die ListenableFuture
von Guava verwenden.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
„Optional“ nicht verwenden
Optional
kann in einigen API-Oberflächen Vorteile haben, ist aber nicht mit der vorhandenen Android-API-Oberfläche konsistent. @Nullable
und @NonNull
bieten Unterstützung für null
-Sicherheit und Kotlin erzwingt Nullable-Verträge auf Compilerebene, wodurch Optional
nicht erforderlich ist.
Verwenden Sie für optionale Primitiven die 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 instanziierbare Klassen verwenden
Klassen, die nur von Builder
erstellt werden können, Klassen, die nur Konstanten oder statische Methoden enthalten, oder andere nicht instanziierbare Klassen sollten mindestens einen privaten Konstruktor enthalten, um die Instanziierung mit dem standardmäßigen No-Arg-Konstruktor zu verhindern.
public final class Log {
// Not instantiable.
private Log() {}
}
Singletons
Singletons werden nicht empfohlen, da sie die folgenden Nachteile in Bezug auf Tests haben:
- Die Erstellung wird von der Klasse verwaltet, wodurch die Verwendung von Fälschungen verhindert wird.
- Tests können aufgrund der statischen Natur eines Singletons nicht hermetisch sein.
- Um diese Probleme zu umgehen, müssen Entwickler entweder die internen Details des Singletons kennen oder einen Wrapper darum erstellen.
Verwenden Sie das Einzelinstanzmuster, 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 zum Abrufen einer Instanz bereit. 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 Singleton dadurch, dass Entwickler eine gefälschte Version von SingleInstance
erstellen und ihr eigenes Framework für die Abhängigkeitsinjektion verwenden können, um die Implementierung zu verwalten, ohne einen Wrapper erstellen zu müssen. Alternativ kann die Bibliothek ein eigenes 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ühren Sie in der öffentlichen API der Plattform (d. h. in android.*
) keine neuen Klassen ein, die direkt oder indirekt von android.view.View
abgeleitet werden.
Das UI-Toolkit von Android ist jetzt Compose-first. Neue UI-Funktionen, die von der Plattform bereitgestellt werden, sollten als APIs auf niedrigerer Ebene bereitgestellt werden, mit denen Entwickler Jetpack Compose- und optional View-basierte UI-Komponenten in Jetpack-Bibliotheken implementieren können. Wenn diese Komponenten in Bibliotheken angeboten werden, können Implementierungen zurückportiert werden, wenn Plattformfunktionen nicht verfügbar sind.
Felder
In diesen Regeln geht es um öffentliche Felder in Klassen.
Rohfelder nicht verfügbar machen
In Java-Klassen sollten Felder nicht direkt verfügbar gemacht werden. 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 grundlegende Datenstrukturen, bei denen das Verhalten beim Angeben oder Abrufen eines Felds nicht verbessert werden muss. In solchen Fällen sollten die Felder mit standardmäßigen Variablennamen benannt werden, z. B. Point.x
und Point.y
.
Kotlin-Klassen können Attribute verfügbar machen.
Sichtbare Felder sollten als „final“ gekennzeichnet werden
Von Rohfeldern wird dringend abgeraten (@see Don't expose raw fields). In dem seltenen Fall, dass ein Feld als öffentliches Feld verfügbar gemacht wird, markieren Sie es mit final
.
Interne Felder sollten nicht offengelegt werden
Verweisen Sie in der öffentlichen API nicht auf interne Feldnamen.
public int mFlags;
„Öffentlich“ anstelle von „Geschützt“ verwenden
@see Use public instead of protected
Konstanten
Dies sind Regeln für öffentliche Konstanten.
Flag-Konstanten dürfen sich nicht mit int- oder long-Werten überschneiden
Flags bezieht sich auf Bits, die zu einem gemeinsamen Wert kombiniert werden können. Wenn das nicht der Fall ist, nennen Sie die Variable oder Konstante nicht flag
.
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 Flag-Konstanten finden Sie unter Bitmask-Flags.@IntDef
Für statische finale Konstanten sollte die Namenskonvention „ALLE GROSSBUCHSTABEN, durch Unterstriche getrennt“ verwendet werden.
Alle Wörter in der Konstanten sollten großgeschrieben und mehrere Wörter 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 können.
Intent-Extras sollten beispielsweise mit EXTRA_
beginnen. Intent-Aktionen sollten mit ACTION_
beginnen. Konstanten, die mit Context.bindService()
verwendet werden, sollten mit BIND_
beginnen.
Schlüsselkonstantennamen und ‑bereiche
Stringkonstantenwerte sollten mit dem Konstantennamen selbst übereinstimmen und in der Regel auf das Paket oder die Domain beschränkt sein. Beispiel:
public static final String FOO_THING = "foo"
weder einheitlich benannt noch angemessen begrenzt ist. Stattdessen sollten Sie Folgendes in Betracht ziehen:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Präfixe von android
in Stringkonstanten mit Bereich sind für das Android Open Source Project reserviert.
Intent-Aktionen und ‑Extras sowie Bundle-Einträge sollten mit dem Paketnamen versehen werden, 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 Use public instead of protected
Einheitliche Präfixe verwenden
Zugehörige Konstanten sollten alle mit demselben Präfix beginnen. Beispiel für eine Gruppe 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 Use standard prefixes for constants
Konsistente Ressourcennamen verwenden
Öffentliche IDs, Attribute und Werte müssen gemäß der Namenskonvention „camelCase“ benannt werden, z. B. @id/accessibilityActionPageUp
oder @attr/textAppearance
, ähnlich wie öffentliche Felder in Java.
In einigen Fällen enthält eine öffentliche ID oder ein 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 Themes und Designs 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 Drawables-Ressourcen sollten nicht als öffentliche APIs verfügbar gemacht werden. Wenn sie jedoch verfügbar gemacht werden müssen, müssen öffentliche Layouts und Drawables mit der Namenskonvention „unter_strich“ benannt werden, z. B. layout/simple_list_item_1.xml
oder drawable/title_bar_tall.xml
.
Konstanten, die sich ändern könnten, dynamisch gestalten
Der Compiler kann konstante Werte inline einfügen. Daher gilt es als Teil des API-Vertrags, Werte beizubehalten. Wenn sich der Wert einer MIN_FOO
- oder MAX_FOO
-Konstante in Zukunft ändern könnte, sollten Sie sie stattdessen in dynamische Methoden umwandeln.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Vorwärtskompatibilität für Callbacks berücksichtigen
Konstanten, die in zukünftigen API-Versionen definiert sind, sind Apps, die auf ältere APIs ausgerichtet sind, nicht bekannt. Aus diesem Grund sollten Konstanten, die an Apps übergeben werden, das Ziel-API-Level der App berücksichtigen und neuere Konstanten einem konsistenten 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 innerhalb der Einschränkungen von API-Level 22 entwickelt und es wurde eine (etwas) vernünftige Annahme getroffen, dass es nur zwei mögliche Status gibt. Wenn die App die neu hinzugefügte STATUS_FAILURE_RETRY
empfängt, wird dies jedoch als Erfolg interpretiert.
Methoden, die Konstanten zurückgeben, können solche Fälle sicher verarbeiten, indem sie ihre Ausgabe auf das von der App verwendete API-Level beschränken:
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 wird. Wenn Sie eine API mit einer UNKNOWN
- oder UNSPECIFIED
-Konstante definieren, die wie ein Catch-all aussieht, gehen Entwickler davon aus, dass die veröffentlichten Konstanten, als sie ihre App geschrieben haben, vollständig waren. Wenn Sie diese Erwartung nicht erfüllen möchten, sollten Sie überlegen, ob eine Catch-all-Konstante für Ihre API eine gute Idee ist.
Außerdem können Bibliotheken kein eigenes targetSdkVersion
getrennt von der App angeben. Die Verarbeitung von targetSdkVersion
-Verhaltensänderungen aus dem 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 erweiterbar ist. 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 genau definierte Gruppe von Hilfsfunktionen für die 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 sollte für Datenklassen ein Konstruktor bereitgestellt werden, wenn es nur wenige Attribute gibt. Bei vielen Attributen sollte das Builder
-Muster verwendet werden.
In Kotlin sollten Datenklassen unabhängig von der Anzahl der Eigenschaften einen Konstruktor mit Standardargumenten bereitstellen. Auch für in Kotlin definierte Datenklassen kann es sinnvoll sein, einen Builder bereitzustellen, wenn Java-Clients verwendet werden.
Änderung und Kopieren
Wenn Daten geändert werden müssen, stellen Sie entweder eine Builder
-Klasse mit einem Kopierkonstruktor (Java) oder eine copy()
-Memberfunktion (Kotlin) bereit, die ein neues Objekt zurückgibt.
Wenn Sie in Kotlin eine copy()
-Funktion bereitstellen, müssen die Argumente mit dem Konstruktor der Klasse übereinstimmen und die Standardwerte müssen mit den aktuellen Werten des Objekts gefü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ätzliches Verhalten
Datenklassen sollten sowohl equals()
als auch hashCode()
implementieren. Jede Property muss in den Implementierungen dieser Methoden berücksichtigt werden.
Datenklassen können toString()
mit einem empfohlenen Format implementieren, das der Implementierung der Kotlin-Datenklasse entspricht, z. B. User(var1=Alex, var2=42)
.
Methoden
Dies sind Regeln zu verschiedenen Details in Methoden, z. B. zu Parametern, Methodennamen, Rückgabetypen und Zugriffsspezifizierern.
Uhrzeit
Diese Regeln legen fest, wie Zeitkonzepte wie Datumsangaben und Dauer in APIs ausgedrückt werden sollen.
Nach Möglichkeit java.time.*-Typen verwenden
java.time.Duration
, java.time.Instant
und viele andere java.time.*
-Typen sind über Desugaring auf allen Plattformversionen verfügbar und sollten bevorzugt werden, wenn die 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 weg, es sei denn, die API-Domain ist eine, in der die Objektzuweisung in den vorgesehenen Nutzungsmustern eine unzumutbare Leistungseinbuße zur Folge hätte.
Methoden, die Zeiträume angeben, sollten „duration“ heißen.
Wenn ein Zeitwert die Dauer des Zeitraums angibt, nennen Sie den Parameter „duration“ (Dauer) und nicht „time“ (Zeit).
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Ausnahmen:
„timeout“ ist angemessen, wenn sich die Dauer speziell auf einen Zeitüberschreitungswert bezieht.
„time“ mit dem Typ java.time.Instant
ist geeignet, wenn Sie sich auf einen bestimmten Zeitpunkt und nicht auf einen Zeitraum beziehen.
Methoden, die Zeiträume oder Zeit als Primitiv ausdrücken, sollten nach ihrer Zeiteinheit benannt werden und „long“ verwenden.
Bei Methoden, die Zeiträume als Primitiv akzeptieren oder zurückgeben, sollte der Methodenname mit den zugehörigen Zeiteinheiten (z. B. Millis
, Nanos
, Seconds
) enden, um den nicht dekorierten Namen für die Verwendung mit java.time.Duration
zu reservieren. Siehe Zeit.
Methoden sollten auch entsprechend mit ihrer Einheit und Zeitbasis annotiert werden:
@CurrentTimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel, der als Anzahl der Millisekunden seit 1970-01-01T00:00:00Z gemessen wird.@CurrentTimeSecondsLong
: Der Wert ist ein nicht negativer Zeitstempel, der als Anzahl der Sekunden seit 1970-01-01T00:00:00Z gemessen wird.@DurationMillisLong
: Der Wert ist eine nicht negative Dauer in Millisekunden.@ElapsedRealtimeLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock.elapsedRealtime()
.@UptimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock.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 angeben, sollte die nicht abgekürzte Kurzform für Einheitsnamen bevorzugt werden.
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
Argumente mit langer Zeitdauer annotieren
Die Plattform enthält mehrere Anmerkungen, um eine stärkere Typisierung für Zeiteinheiten vom Typ long
zu ermöglichen:
@CurrentTimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel, der als Anzahl der Millisekunden seit1970-01-01T00:00:00Z
gemessen wird, also in der ZeitbasisSystem.currentTimeMillis()
.@CurrentTimeSecondsLong
: Der Wert ist ein nicht negativer Zeitstempel, der als Anzahl der Sekunden seit1970-01-01T00:00:00Z
gemessen wird.@DurationMillisLong
: Der Wert ist eine nicht negative Dauer in Millisekunden.@ElapsedRealtimeLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock#elapsedRealtime()
.@UptimeMillisLong
: Der Wert ist ein nicht negativer Zeitstempel in der ZeitbasisSystemClock#uptimeMillis()
.
Maßeinheiten
Verwenden Sie für alle Methoden, die eine andere Maßeinheit als Zeit angeben, SI-Einheitenpräfixe im CamelCase-Format.
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 platzieren und die Reihenfolge der anderen Parameter beibehalten:
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 ob Standardargumente für die komplexeren Methoden angegeben worden wären.
Folge: Überladen Sie Methoden nur, um optionale Argumente hinzuzufügen oder verschiedene Argumenttypen 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 werden (nur Kotlin).
Methoden und Konstruktoren mit Standardparametern müssen mit @JvmOverloads
annotiert werden, um die binäre Kompatibilität aufrechtzuerhalten.
Weitere Informationen finden Sie im offiziellen Kotlin-Java-Interop-Leitfaden unter Function overloads for defaults.
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 quellkompatible Änderung.
Die aussagekräftigsten und identifizierenden Methodenparameter sollten zuerst angegeben werden.
Wenn Sie eine Methode mit mehreren Parametern haben, platzieren Sie die relevantesten zuerst. Parameter, die Flags und andere Optionen angeben, sind weniger wichtig als Parameter, die das Objekt beschreiben, auf das sich die Aktion bezieht. Wenn es einen Completion-Callback gibt, fügen Sie ihn als Letztes ein.
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 ans Ende stellen
Builders
Das Builder-Muster wird für die Erstellung komplexer Java-Objekte empfohlen und wird häufig in Android verwendet, wenn:
- Die Eigenschaften des resultierenden Objekts sollten unveränderlich sein.
- Es gibt eine große Anzahl erforderlicher Eigenschaften, z. B. viele Konstruktorargumente.
- Es besteht eine komplexe Beziehung zwischen Properties zur Erstellungszeit, z. B. ist ein Bestätigungsschritt erforderlich. Ein so hohes Maß an Komplexität deutet oft auf Probleme mit der Benutzerfreundlichkeit 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 wenige von potenziell vielen optionalen Erstellungsparametern konfigurieren
- Viele verschiedene optionale oder erforderliche Erstellungsparameter konfigurieren, die manchmal ähnliche oder übereinstimmende Typen haben, wodurch Aufrufstellen ansonsten schwer zu lesen oder fehleranfällig zu schreiben wären
- Konfigurieren Sie die Erstellung eines Objekts inkrementell, wobei in mehreren verschiedenen Konfigurationscodeabschnitten jeweils der Builder aufgerufen werden kann.
- Ermöglichen, dass ein Typ durch Hinzufügen zusätzlicher optionaler Erstellungsparameter in zukünftigen API-Versionen erweitert werden kann
Wenn Sie einen Typ mit maximal drei erforderlichen Parametern und keinen optionalen Parametern haben, können Sie fast immer auf einen Builder verzichten und einen einfachen Konstruktor verwenden.
Für Klassen, die aus Kotlin stammen, sollten @JvmOverloads
-annotierte Konstruktoren mit Standardargumenten gegenüber Buildern bevorzugt werden. Es kann jedoch die Benutzerfreundlichkeit für Java-Clients verbessert werden, indem auch Builder in den oben beschriebenen Fällen bereitgestellt werden.
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 Method Chaining ermöglichen, indem sie das Builder-Objekt (z. B. this
) aus jeder Methode mit Ausnahme von build()
zurückgeben. Zusätzliche erstellte Objekte sollten als Argumente übergeben werden. Geben Sie nicht den Builder 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 hinweg einheitlich bleibt, 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 implizit auf den Builder über eine Factory-Methode oder einen DSL-ähnlichen Erstellungsmechanismus angewiesen sind. Bibliotheken dürfen @PublishedApi internal
nicht verwenden, um den Builder
-Klassenkonstruktor für Kotlin-Clients selektiv 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, z. B. @Nullable
, sollten in Setter-Methoden verschoben werden.
Der Builder-Konstruktor sollte eine NullPointerException
auslösen (erwägen Sie die Verwendung von Objects.requireNonNull
), wenn erforderliche Argumente nicht angegeben werden.
Builder-Klassen sollten 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 verfügbar gemacht werden, z. B. Tone.Builder
anstelle von ToneBuilder
.
Builder können einen Konstruktor enthalten, um eine neue Instanz aus einer vorhandenen Instanz zu erstellen.
Builder können einen Kopierkonstruktor enthalten, um eine neue Builder-Instanz aus einem vorhandenen Builder oder 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 akzeptieren, wenn der Builder einen Kopierkonstruktor hat.
Das Zurücksetzen ist wichtig, 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 akzeptieren.
Es ist oft einfacher, einen Nullable-Wert für die Eingabe zweiten Grades 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 Attribute @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();
}
Häufige 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 (wenn der Setter nicht aufgerufen wird) und die Bedeutung von null
müssen sowohl im Setter als auch im Getter richtig dokumentiert werden.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Für veränderliche Attribute, für die Setter in der erstellten Klasse verfügbar sind, können Builder-Setter bereitgestellt werden.
Wenn Ihre Klasse veränderliche Eigenschaften hat und eine Builder
-Klasse benötigt, sollten Sie sich zuerst fragen, ob Ihre Klasse tatsächlich veränderliche Eigenschaften haben sollte.
Wenn Sie sicher sind, dass Sie veränderbare Properties benötigen, entscheiden Sie als Nächstes, welches der folgenden Szenarien besser zu Ihrem erwarteten Anwendungsfall passt:
Das erstellte Objekt sollte sofort verwendet werden können. Daher sollten Setter für alle relevanten Properties bereitgestellt werden, unabhängig davon, ob sie veränderlich oder unveränderlich sind.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Möglicherweise sind einige zusätzliche Aufrufe erforderlich, bevor das erstellte Objekt verwendet werden kann. Daher sollten keine Setter für veränderliche Eigenschaften 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);
Vermischen 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);
Builder sollten keine Getter haben
Der Getter sollte sich im erstellten Objekt und nicht im Builder befinden.
Für Builder-Setter muss es entsprechende Getter in der erstellten Klasse geben.
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 von Builder-Methoden
Die Namen von Builder-Methoden sollten dem Stil setFoo()
, addFoo()
oder clearFoo()
entsprechen.
Builder-Klassen müssen eine build()-Methode deklarieren.
Builder-Klassen sollten eine build()
-Methode deklarieren, die eine Instanz des erstellten Objekts zurückgibt.
Die build()-Methoden von Builder müssen @NonNull-Objekte zurückgeben.
Die build()
-Methode eines Builders sollte eine nicht leere Instanz des erstellten Objekts zurückgeben. Wenn das Objekt aufgrund ungültiger Parameter nicht erstellt werden kann, kann die Validierung auf die Build-Methode verschoben und eine IllegalStateException
ausgelöst werden.
Interne Sperren nicht offenlegen
Methoden in der öffentlichen API dürfen das Keyword synchronized
nicht verwenden. Durch dieses Schlüsselwort wird Ihr Objekt oder Ihre Klasse als Sperre verwendet. Da es für andere verfügbar ist, kann es zu unerwarteten Nebeneffekten kommen, wenn anderer Code außerhalb Ihrer Klasse es für Sperrzwecke verwendet.
Führen Sie stattdessen alle erforderlichen Sperrvorgänge für ein internes, privates Objekt aus.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Accessor-Methoden sollten den Kotlin-Attributrichtlinien entsprechen
Wenn aus Kotlin-Quellen auf Methoden im Accessor-Stil zugegriffen wird, also auf Methoden mit den Präfixen get
, set
oder is
, sind diese auch als Kotlin-Properties verfügbar.
Beispielsweise ist int getField()
, das in Java definiert ist, in Kotlin als die Eigenschaft val field: Int
verfügbar.
Aus diesem Grund und um die Erwartungen von Entwicklern an das Verhalten von Accessormethoden zu erfüllen, sollten sich Methoden mit Accessormethodenpräfixen ähnlich wie Java-Felder verhalten. Vermeiden Sie die Verwendung von Präfixen im Stil von Zugriffsfunktionen, wenn:
- Die Methode hat Nebenwirkungen – verwenden Sie einen aussagekräftigeren Methodennamen.
- Die Methode erfordert rechenintensive Arbeit – bevorzugen Sie
compute
- Die Methode umfasst blockierende oder anderweitig zeitaufwendige Vorgänge, um einen Wert zurückzugeben, z. B. IPC oder andere E/A-Vorgänge. Verwenden Sie stattdessen
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
.
Wenn Sie rechenintensive Aufgaben einmal ausführen und den Wert für nachfolgende Aufrufe im Cache speichern, gilt das weiterhin als rechenintensive Aufgabe. Jank wird nicht über Frames hinweg amortisiert.
„is“ als Präfix für boolesche Accessormethoden verwenden
Dies ist die standardmäßige Namenskonvention 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-Accessor-Methoden für boolesche Werte sollten dem Benennungsschema set
/is
folgen und für Felder sollte is
bevorzugt werden, wie in diesem Beispiel:
// 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-Accessor-Methoden oder is
für Java-Felder verwenden, können sie in Kotlin als Attribute verwendet werden:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
Für Properties und Accessormethoden sollte im Allgemeinen eine positive Namensgebung verwendet werden, z. B. Enabled
anstelle von Disabled
. Durch die Verwendung negativer Begriffe 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 der boolesche Wert die Einbeziehung oder das Eigentum an einer Property beschreibt, können Sie has anstelle von is 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();
Methoden, die Verhaltensweisen oder Funktionen ein- oder ausschalten, verwenden möglicherweise das Präfix is und das Suffix Enabled:
// "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()
Methoden, die die Abhängigkeit von anderen Verhaltensweisen oder Funktionen angeben, können das Präfix is und das Suffix Supported oder Required verwenden:
// "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 Klassen-Property var foo: Foo
generiert Kotlin get
-/set
-Methoden nach einer einheitlichen Regel: Für den Getter wird get
vorangestellt und das erste Zeichen in Großbuchstaben geschrieben. Für den Setter wird set
vorangestellt und das erste Zeichen in Großbuchstaben geschrieben. Die Eigenschaftendeklaration erzeugt Methoden mit den Namen public Foo getFoo()
und public void setFoo(Foo foo)
.
Wenn die Property vom Typ Boolean
ist, gilt eine zusätzliche Regel für die Namensgenerierung: Wenn der Property-Name mit is
beginnt, wird get
nicht vor dem Getter-Methodennamen eingefügt. Der Property-Name selbst wird als Getter verwendet.
Daher sollten Boolean
-Properties mit dem Präfix is
benannt werden, um der Benennungsrichtlinie zu entsprechen:
var isVisible: Boolean
Wenn Ihre Property eine der oben genannten Ausnahmen ist und mit einem entsprechenden Präfix beginnt, verwenden Sie die Annotation @get:JvmName
für die Property, um den entsprechenden Namen manuell anzugeben:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
Bitmask-Accessor-Funktionen
@IntDef
für Bitmasken-Flags verwenden enthält API-Richtlinien zum Definieren von Bitmasken-Flags.
Setter
Es sollten zwei Setter-Methoden bereitgestellt werden: eine, die einen vollständigen Bitstring akzeptiert und alle vorhandenen Flags überschreibt, und eine, die eine benutzerdefinierte Bitmaske akzeptiert, 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
public
sollte in öffentlichen APIs immer protected
vorgezogen werden. Geschützter Zugriff ist auf lange Sicht mühsam, da Implementierer öffentliche Accessoren überschreiben müssen, wenn externer Zugriff standardmäßig genauso gut gewesen wäre.
protected
-Sichtbarkeit verhindert nicht, dass Entwickler eine API aufrufen. Sie macht es nur etwas schwieriger.
Implementieren Sie weder equals() noch hashCode() oder beide.
Wenn Sie eine überschreiben, müssen Sie auch die andere überschreiben.
toString() für Datenklassen implementieren
Es wird empfohlen, dass Datenklassen toString()
überschreiben, um Entwicklern beim Debuggen ihres Codes zu helfen.
Dokumentieren, ob die Ausgabe für das Programmverhalten oder das Debugging bestimmt ist
Entscheiden Sie, ob das Programmverhalten von Ihrer Implementierung abhängen soll. Beispielsweise wird mit UUID.toString() und File.toString() das spezifische Format für die Verwendung in Programmen dokumentiert. Wenn Sie Informationen nur zum Debuggen bereitstellen, z. B. Intent, können Sie die Dokumentation von der Superklasse übernehmen.
Keine zusätzlichen Informationen angeben
Alle Informationen, die über toString()
verfügbar sind, sollten auch über die öffentliche API des Objekts verfügbar sein. Andernfalls ermutigen Sie Entwickler, Ihre toString()
-Ausgabe zu parsen und sich darauf zu verlassen, was zukünftige Änderungen verhindert. Es empfiehlt sich, toString()
nur über die öffentliche API des Objekts zu implementieren.
Von der Verwendung der Debug-Ausgabe abraten
Es ist zwar unmöglich, zu verhindern, dass Entwickler von der Debug-Ausgabe abhängig sind, aber wenn Sie die System.identityHashCode
Ihres Objekts in die toString()
-Ausgabe einfügen, ist es sehr unwahrscheinlich, dass zwei verschiedene Objekte die gleiche toString()
-Ausgabe haben.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
So können Entwickler effektiv davon abgehalten werden, Testassertions wie assertThat(a.toString()).isEqualTo(b.toString())
für Ihre Objekte zu schreiben.
„createFoo“ verwenden, wenn neu erstellte Objekte zurückgegeben werden
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, machen Sie das im Methodennamen deutlich.
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
Methoden, die Datei-Objekte akzeptieren, sollten auch Streams akzeptieren
Datenspeicherorte auf Android sind nicht immer Dateien auf dem Laufwerk. Beispielsweise werden Inhalte, die über Nutzergrenzen hinweg weitergegeben werden, als content://
Uri
s dargestellt. Damit verschiedene Datenquellen verarbeitet werden können, sollten APIs, die File
-Objekte akzeptieren, auch InputStream
- und/oder OutputStream
-Objekte akzeptieren.
public void setDataSource(File file)
public void setDataSource(InputStream stream)
Rohe Primitiven anstelle von verpackten Versionen annehmen und zurückgeben
Wenn Sie fehlende oder Nullwerte kommunizieren müssen, sollten 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 die Klassenäquivalente primitiver Typen vermeiden, wird der Speicher-Overhead dieser Klassen, der Methodenzugriff auf Werte und vor allem das Autoboxing vermieden, das durch die Umwandlung zwischen primitiven und Objekttypen entsteht. Wenn Sie diese Verhaltensweisen vermeiden, sparen Sie Arbeitsspeicher und temporäre Zuweisungen, die zu teuren und häufigeren automatischen Speicherbereinigungen führen können.
Mit Anmerkungen gültige Parameter- und Rückgabewerte erläutern
Entwickleranmerkungen wurden 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. wenn sie ein beliebiges int
übergeben, während das Framework einen bestimmten Satz von konstanten Werten erfordert. Verwenden Sie bei Bedarf eine oder mehrere der folgenden Anmerkungen:
Null-Zulässigkeit
Für Java-APIs sind explizite Nullable-Annotationen erforderlich. Das Konzept der Nullable-Annotationen ist jedoch Teil der Kotlin-Sprache und Nullable-Annotationen sollten niemals in Kotlin-APIs verwendet werden.
@Nullable
:Gibt an, dass ein bestimmter Rückgabewert, Parameter oder ein bestimmtes 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 bestimmtes Feld nicht null sein kann. Die Kennzeichnung von Elementen als @Nullable
ist relativ neu für Android. Daher sind die meisten API-Methoden von Android nicht einheitlich dokumentiert. Daher gibt es drei Status: „unknown“, „@Nullable
“ und „@NonNull
“. Aus diesem Grund ist @NonNull
Teil der API-Richtlinien:
@NonNull
public String getName()
public void setName(@NonNull String name)
In der Android-Plattformdokumentation wird durch das Kommentieren Ihrer Methodenparameter automatisch die Dokumentation in der Form „Dieser Wert kann null sein.“ generiert, sofern „null“ nicht explizit an anderer Stelle in der Parameterdokumentation verwendet wird.
Vorhandene Methoden, die nicht wirklich „nullable“ sind:Vorhandene Methoden in der API ohne deklarierte @Nullable
-Annotation 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 Companion-Methoden @NotNull requireFoo()
hinzugefügt werden, die IllegalArgumentException
auslösen.
Schnittstellenmethoden:Bei neuen APIs sollte die richtige Annotation hinzugefügt werden, wenn Schnittstellenmethoden implementiert werden, z. B. Parcelable.writeToParcel()
(d. h., die Methode in der implementierenden Klasse sollte writeToParcel(@NonNull Parcel,
int)
und nicht writeToParcel(Parcel, int)
sein). Bei vorhandenen APIs, denen die Annotationen fehlen, ist keine Korrektur erforderlich.
Null-Zulässigkeit erzwingen
In Java wird empfohlen, Methoden zur Eingabevalidierung für @NonNull
-Parameter mit Objects.requireNonNull()
zu verwenden und eine NullPointerException
auszulösen, wenn die Parameter null sind. In Kotlin erfolgt dies automatisch.
Ressourcen
Ressourcen-IDs:Ganzzahlparameter, die IDs für bestimmte Ressourcen angeben, sollten mit der entsprechenden Ressourcentypdefinition annotiert werden.
Es gibt eine Annotation für jeden Ressourcentyp, z. B. @StringRes
, @ColorRes
und @AnimRes
, sowie die Sammelkategorie @AnyRes
. Beispiel:
public void setTitle(@StringRes int resId)
@IntDef für Konstantensätze
Magische Konstanten:Die Parameter String
und int
, für die einer aus einer endlichen Menge möglicher Werte vorgesehen ist, die durch öffentliche Konstanten angegeben werden, sollten entsprechend mit @StringDef
oder @IntDef
annotiert werden. Mit diesen Anmerkungen können Sie eine neue Anmerkung erstellen, die wie ein „typedef“ für zulässige Parameter funktioniert. 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, Methoden zu verwenden, um 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 Annotation 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
Es gibt auch die Annotation @StringDef
, die genau wie @IntDef
im vorherigen Abschnitt funktioniert, aber für String
-Konstanten verwendet wird. Sie können mehrere „prefix“-Werte angeben, mit denen automatisch die Dokumentation für alle Werte ausgegeben wird.
@SdkConstant für SDK-Konstanten
@SdkConstant: Annotieren Sie öffentliche Felder, wenn sie einen der folgenden SdkConstant
-Werte haben: ACTIVITY_INTENT_ACTION
, BROADCAST_INTENT_ACTION
, SERVICE_ACTION
, INTENT_CATEGORY
, FEATURE
.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";
Kompatible Nullable-Werte für Überschreibungen bereitstellen
Aus Gründen der API-Kompatibilität sollte die Nullable-Eigenschaft von Überschreibungen mit der aktuellen Nullable-Eigenschaft des übergeordneten Elements kompatibel sein. 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 | Nicht annotiert | Nicht annotiert oder nicht null |
Rückgabetyp | Nullwerte zulässig | Nullable oder nonnull |
Rückgabetyp | Nonnull | Nonnull |
Fun-Argument | Nicht annotiert | Nicht annotiert oder nullfähig |
Fun-Argument | Nullwerte zulässig | Nullwerte zulässig |
Fun-Argument | Nonnull | Nullable oder nonnull |
Nach Möglichkeit nicht nullable Argumente (z. B. @NonNull) verwenden
Wenn Methoden überladen werden, 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 sollte nicht null sein und das Löschen der Property sollte als separate Methode implementiert werden. So werden „sinnlose“ Aufrufe verhindert, bei denen der Entwickler nachfolgende Parameter festlegen muss, obwohl sie 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()
Nicht nullable Rückgabetypen für Container bevorzugen (z. B. @NonNull)
Geben Sie für Containertypen wie Bundle
oder Collection
einen leeren und gegebenenfalls unveränderlichen Container zurück. In Fällen, in denen null
verwendet würde, um die Verfügbarkeit eines Containers zu unterscheiden, sollten Sie eine separate boolesche Methode bereitstellen.
@NonNull
public Bundle getExtras() { ... }
Nullable-Anmerkungen für Get- und Set-Paare müssen übereinstimmen
Die Nullable-Annotationen von Get- und Set-Methodenpaaren für eine einzelne logische Eigenschaft sollten immer übereinstimmen. Wenn Sie diese Richtlinie nicht befolgen, wird die Attributsyntax von Kotlin nicht unterstützt. Das Hinzufügen von Nullable-Anmerkungen zu vorhandenen Attributmethoden, die nicht übereinstimmen, ist daher eine quellkompatible Änderung für Kotlin-Nutzer.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Rückgabewert bei Fehler- oder Fehlersituationen
Alle APIs sollten es Apps ermöglichen, auf Fehler zu reagieren. Wenn Sie false
, -1
, null
oder andere allgemeine Werte für „etwas ist schiefgelaufen“ zurückgeben, erfahren Entwickler nicht genug über den Fehler, um die Erwartungen der Nutzer zu erfüllen oder die Zuverlässigkeit ihrer App im Einsatz genau zu verfolgen. Stellen Sie sich beim Entwerfen einer API vor, dass Sie eine App entwickeln. Wenn ein Fehler auftritt, liefert die API genügend Informationen, um ihn dem Nutzer zu präsentieren oder angemessen darauf zu reagieren?
- Es ist in Ordnung (und sogar erwünscht), detaillierte Informationen in eine Ausnahme-Meldung aufzunehmen. Entwickler sollten sie jedoch nicht parsen müssen, um den Fehler angemessen zu beheben. Ausführliche Fehlercodes oder andere Informationen sollten als Methoden verfügbar gemacht werden.
- Achten Sie darauf, dass die von Ihnen gewählte Option zur Fehlerbehandlung Ihnen die Flexibilität gibt, in Zukunft neue Fehlertypen einzuführen. Für
@IntDef
bedeutet das, dass einOTHER
- oderUNKNOWN
-Wert enthalten sein muss. Wenn Sie einen neuen Code zurückgeben, können Sie dietargetSdkVersion
des Aufrufers prüfen, um zu vermeiden, dass ein Fehlercode zurückgegeben wird, den die App nicht kennt. Für Ausnahmen sollten Sie eine gemeinsame Superklasse verwenden, die von Ihren Ausnahmen implementiert wird. So kann jeder Code, der diesen Typ verarbeitet, auch Untertypen abfangen und verarbeiten. - Es sollte für einen Entwickler schwierig oder unmöglich sein, einen Fehler versehentlich zu ignorieren. Wenn Ihr Fehler durch die Rückgabe eines Werts kommuniziert wird, annotieren Sie Ihre Methode mit
@CheckResult
.
Wir empfehlen, eine ? extends RuntimeException
auszulösen, wenn ein Fehler oder eine Fehlerbedingung aufgrund eines Fehlers des Entwicklers erreicht wird, z. B. wenn Einschränkungen für Eingabeparameter ignoriert oder der beobachtbare Status nicht geprüft wird.
Setter- oder Aktionsmethoden (z. B. perform
) können einen ganzzahligen Statuscode zurückgeben, wenn die Aktion aufgrund eines asynchron aktualisierten Status oder von Bedingungen, die außerhalb der Kontrolle des Entwicklers liegen, fehlschlagen kann.
Statuscodes sollten in der enthaltenden Klasse als public static final
-Felder definiert werden, mit ERROR_
beginnen und in einer @hide
-@IntDef
-Annotation aufgelistet werden.
Methodennamen sollten immer mit dem Verb und nicht mit dem Subjekt beginnen.
Der Name der Methode sollte immer mit dem Verb (z. B. get
, create
, reload
usw.) beginnen, nicht mit dem Objekt, auf das Sie sich beziehen.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Collection-Typen als Rückgabe- oder Parametertyp bevorzugen
Generisch typisierte Sammlungsschnittstellen bieten mehrere Vorteile gegenüber Arrays, darunter stärkere API-Verträge in Bezug auf Eindeutigkeit und Reihenfolge, Unterstützung für Generics und eine Reihe von entwicklerfreundlichen Convenience-Methoden.
Ausnahme für Primitiven
Wenn die Elemente Primitiven sind, sollten Sie Arrays verwenden, um die Kosten für das automatische Boxing zu vermeiden. Weitere Informationen finden Sie unter Rohprimitive anstelle von boxed-Versionen verwenden.
Ausnahme für leistungsempfindlichen Code
In bestimmten Szenarien, in denen die API in leistungssensitivem Code verwendet wird (z. B. Grafiken oder andere APIs für Messung, Layout oder Zeichnung), ist es akzeptabel, Arrays anstelle von Sammlungen zu verwenden, um Zuweisungen und Speicher-Churn zu reduzieren.
Ausnahme für Kotlin
Kotlin-Arrays sind invariant und die Kotlin-Sprache bietet viele Utility-APIs für Arrays. Daher sind Arrays für Kotlin-APIs, die über Kotlin aufgerufen werden sollen, 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.
Wo Typannotationen unterstützt werden, sollte für Sammlungselemente immer @NonNull
verwendet werden.
Sie sollten auch @NonNull
verwenden, wenn Sie Arrays anstelle von Sammlungen verwenden (siehe vorheriger Eintrag). Wenn die Objektzuweisung ein Problem darstellt, erstellen Sie eine Konstante und übergeben Sie sie. 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 von Sammlungen
In Kotlin-APIs sollten standardmäßig schreibgeschützte (nicht Mutable
) Rückgabetypen für Sammlungen verwendet werden, es sei denn der API-Vertrag erfordert ausdrücklich einen veränderlichen Rückgabetyp.
Bei Java-APIs sollten jedoch standardmäßig veränderliche Rückgabetypen bevorzugt werden, da die Android-Plattformimplementierung von Java-APIs noch keine praktische Implementierung unveränderlicher Sammlungen bietet. Ausgenommen von dieser Regel sind Collections.empty
-Rückgabetypen, die unveränderlich sind. In Fällen, in denen die Veränderbarkeit von Clients – absichtlich oder versehentlich – ausgenutzt werden könnte, um das beabsichtigte Nutzungsmuster der API zu unterbrechen, sollten Java-APIs unbedingt 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 idealerweise nicht mehr ändern, nachdem es zurückgegeben wurde. Wenn die zurückgegebene Sammlung geändert oder auf irgendeine Weise wiederverwendet werden muss, z. B. eine angepasste Ansicht eines veränderlichen Datasets, muss das genaue Verhalten von when (wann) die Inhalte geändert werden können, explizit dokumentiert werden oder etablierten API-Namenskonventionen folgen.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
Die Kotlin-Konvention .asFoo()
wird unten beschrieben und ermöglicht, dass sich die von .asList()
zurückgegebene Sammlung ändert, wenn sich die ursprüngliche Sammlung ändert.
Veränderbarkeit zurückgegebener Datentypobjekte
Ähnlich wie bei APIs, die Sammlungen zurückgeben, sollten APIs, die Objekte vom Datentyp zurückgeben, die Eigenschaften des zurückgegebenen Objekts idealerweise nicht nach der Rückgabe ä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 extrem seltenen Fällen kann es bei leistungssensitivem Code sinnvoll sein, Objekt-Pooling oder ‑Wiederverwendung zu nutzen. Schreiben Sie keine eigene Datenstruktur für den Objektpool und stellen Sie wiederverwendete Objekte nicht in öffentlichen APIs zur Verfügung. In beiden Fällen ist es äußerst wichtig, den gleichzeitigen Zugriff sorgfältig zu verwalten.
Verwendung des Vararg-Parametertyps
Sowohl für Kotlin- als auch für Java-APIs wird empfohlen, vararg
zu verwenden, wenn der Entwickler wahrscheinlich ein Array am Aufrufort erstellen würde, nur um mehrere zusammengehörige 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änderlichen Array aufgerufen werden. API-Entwickler werden dringend aufgefordert, eine defensive flache Kopie des Array-Parameters 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 schützt es nicht vor Änderungen an den Objekten im Array.
Korrekte Semantik mit Parametern oder Rückgabetypen vom Sammlungstyp bereitstellen
List<Foo>
ist die Standardoption. Sie sollten jedoch auch andere Typen in Betracht ziehen, um zusätzliche Informationen zu liefern:
Verwenden Sie
Set<Foo>
, wenn die Reihenfolge der Elemente für Ihre API irrelevant ist und keine Duplikate zulässig sind oder Duplikate keine Bedeutung haben.Collection<Foo>,
, wenn die Reihenfolge für Ihre API keine Rolle spielt und Duplikate zulässig sind.
Kotlin-Konvertierungsfunktionen
In Kotlin werden .toFoo()
und .asFoo()
häufig verwendet, um ein Objekt eines anderen Typs aus einem vorhandenen Objekt abzurufen, wobei Foo
der Name des Rückgabetyps der Konvertierung ist. Dies entspricht dem bekannten JDK Object.toString()
. In Kotlin wird das Konzept noch weiter gefasst und auch für primitive Typkonvertierungen wie 25.toFloat()
verwendet.
Die Unterscheidung zwischen Conversions mit dem Namen .toFoo()
und .asFoo()
ist wichtig:
Verwenden Sie .toFoo(), wenn Sie ein neues, unabhängiges Objekt erstellen.
Wie bei .toString()
wird bei einer „to“-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 new-Objekt später geändert wird, werden diese Änderungen nicht im old-Objekt berücksichtigt.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
.asFoo() beim Erstellen eines abhängigen Wrappers, eines dekorierten Objekts oder einer Umwandlung verwenden
Das Casting in Kotlin erfolgt mit dem Schlüsselwort as
. Sie spiegelt eine Änderung der Schnittstelle, aber nicht der Identität wider. Wenn .asFoo()
als Präfix in einer Erweiterungsfunktion verwendet wird, wird der Empfänger damit dekoriert. Eine Änderung am ursprünglichen Empfängerobjekt wird im von asFoo()
zurückgegebenen Objekt berücksichtigt.
Eine Mutation im neuen Foo
-Objekt kann sich im Originalobjekt widerspiegeln.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
Konversionsfunktionen sollten als Erweiterungsfunktionen geschrieben werden
Wenn Sie Konvertierungsfunktionen außerhalb der Definitionen der Empfänger- und Ergebnisklasse schreiben, wird die Kopplung zwischen Typen reduziert. Für eine ideale Konvertierung ist nur öffentlicher API-Zugriff auf das ursprüngliche Objekt erforderlich. Das zeigt beispielhaft, dass ein Entwickler analoge Konvertierungen auch für seine eigenen bevorzugten Typen schreiben kann.
Geeignete 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 ohne zu große Einschränkungen behandeln können.
Bei Fehlern, die nicht mit den Argumenten zusammenhängen, die direkt an die öffentlich aufgerufene Methode übergeben werden, sollte java.lang.IllegalStateException
anstelle von java.lang.IllegalArgumentException
oder java.lang.NullPointerException
ausgelöst werden.
Listener und Callbacks
Dies sind die Regeln für die Klassen und Methoden, die für Listener- und Callback-Mechanismen verwendet werden.
Callback-Klassennamen sollten im Singular stehen
Verwenden Sie MyObjectCallback
anstelle von MyObjectCallbacks
.
Callback-Methodennamen sollten das Format on haben.
onFooEvent
bedeutet, dass FooEvent
stattfindet und der Callback entsprechend reagieren sollte.
Vergangenheits- und Präsensformen sollten das Timing-Verhalten beschreiben.
Callback-Methoden für Ereignisse sollten so benannt werden, dass angegeben wird, 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:
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 „add“ und „remove“ oder „register“ und „unregister“ heißen. Halten Sie sich an die vorhandene Konvention, die von der Klasse oder von anderen Klassen im selben Paket verwendet wird. Wenn es keinen solchen Präzedenzfall gibt, sollten Sie „hinzufügen“ und „entfernen“ verwenden.
Bei Methoden, die das Registrieren oder Aufheben der Registrierung von Callbacks umfassen, sollte 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);
Getter für Callbacks vermeiden
Fügen Sie keine getFooCallback()
-Methoden hinzu. Dies ist eine verlockende Möglichkeit für Fälle, in denen Entwickler einen vorhandenen Callback mit ihrem eigenen Ersatz verketten möchten. Sie ist jedoch anfällig und erschwert es Komponentenentwicklern, den aktuellen Zustand nachzuvollziehen. Beispiel:
- Entwickler A ruft
setFooCallback(a)
an - Entwickler B ruft
setFooCallback(new B(getFooCallback()))
auf - Entwickler A möchte seinen Callback
a
entfernen, kann dies aber nicht ohne Kenntnis des Typs vonB
, undB
wurde so entwickelt, dass solche Änderungen am umschlossenen Callback möglich sind.
Executor akzeptieren, um den Callback-Versand zu steuern
Beim Registrieren von Callbacks ohne explizite Threading-Erwartungen (so gut wie überall außerhalb des UI-Toolkits) wird dringend empfohlen, einen Executor
-Parameter als Teil der Registrierung einzufügen, 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 von unseren üblichen Richtlinien zu optionalen Parametern ist es zulässig, eine Überladung ohne Executor
anzugeben, auch wenn es nicht das letzte Argument in der Parameterliste ist. Wenn Executor
nicht angegeben ist, sollte der Callback mit Looper.getMainLooper()
im Hauptthread 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
Implementierungsfallen:Beachten Sie, dass der folgende Executor gültig ist.
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
Das bedeutet, dass bei der Implementierung von APIs, die diese Form annehmen, Ihre eingehende Binder-Objektimplementierung auf der App-Prozessseite Binder.clearCallingIdentity()
aufrufen muss, bevor der Callback der App für die von der App bereitgestellte 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, der die App aufruft, korrekt zugeordnet. Wenn Nutzer Ihrer API die UID- oder PID-Informationen des Aufrufers benötigen, sollte dies ein expliziter Teil Ihrer API sein und nicht implizit davon abhängen, wo die von ihnen bereitgestellte Executor
ausgeführt wurde.
Die Angabe von Executor
sollte von Ihrer API unterstützt werden. In leistungskritischen Fällen müssen Apps möglicherweise Code entweder sofort oder synchron mit Feedback von Ihrer API ausführen. Dies ist möglich, wenn Sie eine Executor
akzeptieren.
Wenn Sie defensiv ein zusätzliches HandlerThread
oder Ähnliches wie Trampolin erstellen, wird dieser wünschenswerte Anwendungsfall zunichte gemacht.
Wenn eine App teuren Code in einem eigenen Prozess ausführen muss, sollte sie das tun dürfen. Die Umgehungen, 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 eine einzelne 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
Die Handler
von Android wurde in der Vergangenheit als Standard verwendet, um die Ausführung von Callbacks an einen bestimmten Looper
-Thread weiterzuleiten. Dieser Standard wurde geändert, um Executor
zu bevorzugen, da die meisten App-Entwickler ihre eigenen Thread-Pools verwalten. Dadurch ist der Haupt- oder UI-Thread der einzige Looper
-Thread, der für die App verfügbar ist. Verwenden Sie Executor
, um Entwicklern die Kontrolle zu geben, die sie benötigen, um ihre vorhandenen/bevorzugten Ausführungskontexte wiederzuverwenden.
Moderne Bibliotheken für die gleichzeitige Ausführung wie kotlinx.coroutines oder RxJava bieten eigene Scheduling-Mechanismen, die bei Bedarf einen eigenen Dispatch ausführen. Daher ist es wichtig, die Möglichkeit zu bieten, einen direkten Executor (z. B. Runnable::run
) zu verwenden, um Latenz durch doppelte Thread-Hops zu vermeiden. Beispielsweise ein Hop, um mit einem Handler
in einem Looper
-Thread zu posten, gefolgt von einem weiteren Hop aus dem Concurrency-Framework der App.
Ausnahmen von dieser Richtlinie sind selten. Häufige Einsprüche für eine Ausnahme sind:
Ich muss ein Looper
verwenden, weil ich ein Looper
benötige, um epoll
für die Veranstaltung zu nutzen.
Dieser Antrag auf Ausnahme wird genehmigt, da die Vorteile von Executor
in dieser Situation nicht genutzt werden können.
Ich möchte nicht, dass App-Code die Veröffentlichung des Ereignisses in meinem Thread blockiert. Diese Ausnahme wird in der Regel nicht für Code gewährt, der in einem App-Prozess ausgeführt wird. Apps, die das falsch machen, schaden nur sich selbst und beeinträchtigen nicht die allgemeine Systemintegrität. Apps, die es richtig machen oder ein gängiges Framework für die Parallelverarbeitung verwenden, sollten keine zusätzlichen Latenzstrafen zahlen.
Handler
ist lokal konsistent mit anderen ähnlichen APIs in derselben Klasse.
Dieser Antrag auf Ausnahme wird situationsabhängig genehmigt. Es wird bevorzugt, Executor
-basierte Überladungen hinzuzufügen und Handler
-Implementierungen zu migrieren, damit die neue Executor
-Implementierung verwendet wird. (myHandler::post
ist ein gültiges Executor
.) Je nach Größe der Klasse, Anzahl der vorhandenen Handler
-Methoden und Wahrscheinlichkeit, dass Entwickler vorhandene Handler
-basierte Methoden zusammen mit der neuen Methode 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 die Registrierung aufzuheben. Die Methode
registerThing(Thing)
sollte eine entsprechende
unregisterThing(Thing)
Anfragekennung angeben
Wenn es für einen Entwickler sinnvoll ist, einen Callback wiederzuverwenden, stellen Sie ein Identifier-Objekt bereit, um den Callback mit der Anfrage zu verknüpfen.
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 sollte interface
bevorzugt und default
-Methoden verwendet werden, wenn Schnittstellen hinzugefügt werden, die bereits veröffentlicht wurden. Bisher wurde in dieser Richtlinie abstract class
empfohlen, da in Java 7 keine default
-Methoden vorhanden waren.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
„android.os.OutcomeReceiver“ zum Modellieren eines nicht blockierenden Funktionsaufrufs verwenden
OutcomeReceiver<R,E>
gibt bei Erfolg den Ergebniswert R
zurück, andernfalls E : Throwable
– genau wie ein einfacher Methodenaufruf. Verwenden Sie OutcomeReceiver
als Callback-Typ, 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. Jedes Ergebnis, das requestFoo
zurückgeben würde, wird stattdessen an den callback
-Parameter von requestFooAsync
gemeldet, indem es für den bereitgestellten executor
aufgerufen wird.OutcomeReceiver.onResult
Jede Ausnahme, die von requestFoo
ausgelöst wird, wird stattdessen auf dieselbe Weise an die Methode OutcomeReceiver.onError
gemeldet.
Wenn Sie OutcomeReceiver
für das Melden von Ergebnissen asynchroner Methoden verwenden, erhalten Sie auch einen Kotlin-suspend fun
-Wrapper für asynchrone Methoden, die die Erweiterung Continuation.asOutcomeReceiver
aus 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 dem Komfort eines einfachen Funktionsaufrufs aufrufen, ohne den aufrufenden Thread zu blockieren. Diese 1:1-Erweiterungen für Plattform-APIs können als Teil des androidx.core:core-ktx
-Artefakts in Jetpack angeboten werden, wenn sie mit standardmäßigen Versionskompatibilitätsprüfungen und ‑überlegungen kombiniert werden. Weitere Informationen, Überlegungen zur Kündigung und Beispiele finden Sie in der Dokumentation zu asOutcomeReceiver.
Asynchrone Methoden, die nicht der Semantik einer Methode entsprechen, die ein Ergebnis zurückgibt oder eine Ausnahme auslöst, wenn ihre Arbeit abgeschlossen ist, sollten OutcomeReceiver
nicht als Callback-Typ verwenden. Verwenden Sie stattdessen eine der anderen Optionen, die im folgenden Abschnitt aufgeführt sind.
Funktionale Schnittstellen gegenüber neuen SAM-Typen (Single Abstract Method) bevorzugen
Mit API-Level 24 wurden die java.util.function.*
-Typen (Referenzdokumentation) hinzugefügt, die generische SAM-Schnittstellen wie Consumer<T>
bieten, die sich als Callback-Lambdas eignen. In vielen Fällen bietet das Erstellen neuer SAM-Schnittstellen nur wenig Vorteile in Bezug auf Typsicherheit oder die Kommunikation von Intentionen, während die Android-API-Oberfläche unnötig erweitert wird.
Verwenden Sie diese generischen Schnittstellen, anstatt neue zu erstellen:
Runnable
:() -> Unit
Supplier<R>
:() -> R
Consumer<T>
:(T) -> Unit
Function<T,R>
:(T) -> R
Predicate<T>
:(T) -> Boolean
- Viele weitere in der Referenzdokumentation verfügbar
Platzierung von SAM-Parametern
SAM-Parameter sollten zuletzt platziert werden, um eine idiomatische Verwendung in Kotlin zu ermöglichen, auch wenn die Methode mit zusätzlichen Parametern überladen wird.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
Docs
Dies sind Regeln für die öffentliche Dokumentation (Javadoc) für APIs.
Alle öffentlichen APIs müssen dokumentiert werden
Für alle öffentlichen APIs muss eine ausreichende Dokumentation vorhanden sein, in der erläutert wird, wie ein Entwickler die API verwenden kann. Angenommen, der Entwickler hat die Methode über die automatische Vervollständigung oder beim Durchsuchen der API-Referenzdokumentation gefunden und hat nur wenige Informationen aus der angrenzenden API-Oberfläche (z. B. derselben Klasse).
Methoden
Methodenparameter und Rückgabewerte müssen mit den Dokumentationsanmerkungen @param
bzw. @return
dokumentiert werden. Formatiere den Javadoc-Text so, als ob er mit „Diese Methode…“ beginnt.
Wenn eine Methode keine Parameter akzeptiert, keine besonderen Überlegungen erfordert und das zurückgibt, was der Methodenname besagt, können Sie @return
weglassen und die Dokumentation so schreiben:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Immer Links in Javadoc verwenden
In der Dokumentation sollte auf andere Dokumente für zugehörige Konstanten, Methoden und andere Elemente verwiesen werden. Verwenden Sie Javadoc-Tags (z. B. @see
und {@link foo}
) und nicht nur Wörter im Nur-Text-Format.
Für das folgende Quellbeispiel:
public static final int FOO = 0;
public static final int BAR = 1;
Keinen Roh- oder Code-Text verwenden:
/**
* 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
-Annotation wie @ValueType
für einen Parameter verwenden, wird automatisch eine Dokumentation mit den zulässigen Typen generiert. Weitere Informationen zu IntDef
finden Sie in der Anleitung zu Annotationen.
„update-api“- oder „docs“-Ziel beim Hinzufügen von Javadoc ausführen
Diese Regel ist besonders wichtig, wenn Sie @link
- oder @see
-Tags hinzufügen. Achten Sie darauf, dass die Ausgabe wie erwartet aussieht. ERROR-Ausgaben in Javadoc sind oft auf fehlerhafte Links zurückzuführen. Entweder das Zielvorhaben update-api
oder docs
führt diese Prüfung durch. Das Zielvorhaben docs
ist jedoch möglicherweise schneller, wenn Sie nur Javadoc ändern und das Zielvorhaben update-api
nicht anderweitig ausführen müssen.
{@code foo} zum Unterscheiden von Java-Werten verwenden
Schließen Sie Java-Werte wie true
, false
und null
in {@code...}
ein, um sie vom Dokumentationstext zu unterscheiden.
Wenn Sie Dokumentation in Kotlin-Quellen schreiben, können Sie Code mit Graviszeichen umschließen, wie Sie es bei Markdown tun würden.
@param- und @return-Zusammenfassungen sollten ein einzelnes Satzfragment sein
Zusammenfassungen von Parametern und Rückgabewerten sollten mit einem Kleinbuchstaben beginnen und nur einen einzelnen Satzfragment enthalten. Wenn Sie zusätzliche Informationen haben, die über einen einzelnen Satz hinausgehen, verschieben Sie sie in den Javadoc-Text 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.
*/
Richtig:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
Erklärungen für Anmerkungen in Google Docs erforderlich
Dokumentieren Sie, warum die Anmerkungen @hide
und @removed
in der öffentlichen API ausgeblendet sind.
Fügen Sie eine Anleitung dazu ein, wie API-Elemente, die mit der Annotation @deprecated
gekennzeichnet sind, ersetzt werden.
@throws zum Dokumentieren von Ausnahmen verwenden
Wenn eine Methode eine geprüfte Ausnahme wie IOException
auslöst, dokumentieren Sie die Ausnahme mit @throws
. Für Kotlin-basierte APIs, die von Java-Clients verwendet werden sollen, müssen Funktionen mit @Throws
annotiert werden.
Wenn eine Methode eine nicht abgefangene Ausnahme auslöst, die auf einen vermeidbaren Fehler hinweist, z. B. IllegalArgumentException
oder IllegalStateException
, dokumentieren Sie die Ausnahme mit einer Erklärung, warum sie ausgelöst wird. Die ausgelöste Ausnahme sollte auch angeben, warum sie ausgelöst wurde.
Bestimmte Fälle ungeprüfter 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 Annotation ü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, sollten Sie überlegen, wie der Entwickler von solchen Ausnahmen erfährt und darauf reagiert. Normalerweise wird die Ausnahme an einen Callback weitergeleitet und die ausgelösten Ausnahmen werden in der Methode dokumentiert, die sie empfängt. Asynchrone Ausnahmen sollten nicht mit @throws
dokumentiert werden, es sei denn, sie werden tatsächlich von der annotierten Methode neu ausgelöst.
Ersten Satz von Dokumenten mit einem Punkt beenden
Das Doclava-Tool parst Dokumente auf einfache Weise und beendet das Synopsis-Dokument (den ersten Satz, der in der Kurzbeschreibung oben in den Klassendokumenten verwendet wird), sobald es einen Punkt (.) gefolgt von einem Leerzeichen sieht. Das führt zu zwei Problemen:
- Wenn ein kurzes Dokument nicht mit einem Punkt endet und das Mitglied Dokumente geerbt hat, die vom Tool erfasst werden, werden diese geerbten Dokumente auch in der Zusammenfassung berücksichtigt. Ein Beispiel finden Sie unter
actionBarTabStyle
in derR.attr
-Dokumentation. Dort wurde die Beschreibung der Dimension in die Zusammenfassung aufgenommen. - Vermeiden Sie „z.B.“ im ersten Satz aus demselben Grund, da Doclava die Synopsis-Dokumente nach „g.“ beendet. Ein Beispiel finden Sie unter
TEXT_ALIGNMENT_CENTER
inView.java
. Metalava korrigiert diesen Fehler automatisch, indem nach dem Punkt ein geschütztes Leerzeichen eingefügt wird. Sie sollten diesen Fehler jedoch von vornherein vermeiden.
Dokumente für die Darstellung in HTML formatieren
Javadoc wird in HTML gerendert. Formatieren Sie diese Dokumente also entsprechend:
Für Zeilenumbrüche sollte ein explizites
<p>
-Tag verwendet werden. Fügen Sie kein schließendes</p>
-Tag hinzu.Verwenden Sie kein ASCII, um Listen oder Tabellen zu rendern.
Für Listen sollten
<ul>
für ungeordnete und<ol>
für geordnete Listen verwendet werden. Jedes Element sollte mit einem<li>
-Tag beginnen, aber kein schließendes</li>
-Tag benötigen. Nach dem letzten Element ist ein schließendes</ul>
- oder</ol>
-Tag erforderlich.In Tabellen sollten
<table>
,<tr>
für Zeilen,<th>
für Überschriften und<td>
für Zellen verwendet werden. Für alle Tabellentags sind entsprechende schließende Tags erforderlich. Sie könnenclass="deprecated"
für jedes Tag verwenden, um die Einstellung zu kennzeichnen.Verwenden Sie
{@code foo}
, um Inline-Code-Schriftart zu erstellen.Verwenden Sie zum Erstellen von Codeblöcken
<pre>
.Der gesamte Text in einem
<pre>
-Block wird vom Browser geparst. Achten Sie daher auf Klammern<>
. Sie können sie mit den HTML-Entitäten<
und>
maskieren.Alternativ können Sie die geschweiften Klammern
<>
in Ihrem Code-Snippet belassen, wenn Sie die betreffenden Abschnitte in{@code foo}
einschließen. Beispiel:<pre>{@code <manifest>}</pre>
API-Referenz-Stilrichtlinien einhalten
Um einen einheitlichen Stil für Klassenzusammenfassungen, Methodenbeschreibungen, Parameterbeschreibungen und andere Elemente zu gewährleisten, folgen Sie den Empfehlungen in den offiziellen Java-Sprachrichtlinien unter How to Write Doc Comments for the Javadoc Tool.
Android Framework-spezifische Regeln
Diese Regeln beziehen sich auf APIs, Muster und Datenstrukturen, die für APIs und Verhaltensweisen im Android-Framework spezifisch sind (z. B. Bundle
oder Parcelable
).
Intent-Builder sollten das Muster „create*Intent()“ verwenden.
Für Intents sollten Methoden mit dem Namen createFooIntent()
verwendet werden.
Bundle verwenden, anstatt neue Allzweck-Datenstrukturen zu erstellen
Vermeiden Sie das Erstellen neuer Allzweck-Datenstrukturen zur Darstellung beliebiger Zuordnungen von Schlüssel zu typisierten Werten. Verwenden Sie stattdessen Bundle
.
Dies ist in der Regel der Fall, wenn Plattform-APIs geschrieben werden, die als Kommunikationskanäle zwischen Nicht-Plattform-Apps und ‑Diensten dienen, wobei die Plattform die über den Kanal gesendeten Daten nicht liest und der API-Vertrag teilweise außerhalb der Plattform definiert werden kann (z. B. in einer Jetpack-Bibliothek).
In Fällen, in denen die Plattform die Daten liest, sollten Sie Bundle
vermeiden und eine stark typisierte Datenklasse verwenden.
Parcelable-Implementierungen müssen ein öffentliches CREATOR-Feld haben
Die Parcelable-Inflation wird über CREATOR
und nicht über Rohkonstruktoren bereitgestellt. Wenn eine Klasse Parcelable
implementiert, muss ihr CREATOR
-Feld auch eine öffentliche API sein und der Klassenkonstruktor, der ein Parcel
-Argument akzeptiert, muss privat sein.
CharSequence für UI-Strings verwenden
Wenn ein String in einer Benutzeroberfläche angezeigt wird, verwenden Sie CharSequence
, um Spannable
-Instanzen zu ermöglichen.
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.
Enums vermeiden
IntDef
muss in allen Plattform-APIs anstelle von Enums verwendet werden und sollte auch in nicht gebündelten Bibliotheks-APIs in Betracht gezogen werden. Verwenden Sie Enums nur, wenn Sie sicher sind, dass keine neuen Werte hinzugefügt werden.
Vorteile von IntDef
:
- Ermöglicht das Hinzufügen von Werten im Zeitverlauf
- Kotlin-
when
-Anweisungen können zur Laufzeit fehlschlagen, wenn sie aufgrund eines hinzugefügten Enum-Werts auf der Plattform nicht mehr vollständig sind.
- Kotlin-
- Keine Klassen oder Objekte, die zur Laufzeit verwendet werden, nur Primitiven
- Mit R8 oder der Minimierung können diese Kosten für nicht gebündelte Bibliotheks-APIs vermieden werden. Diese Optimierung kann sich jedoch nicht auf Plattform-API-Klassen auswirken.
Vorteile von Enum
- Idiomatische Sprachfunktion von Java, Kotlin
- Ermöglicht die Verwendung der
when
-Anweisung für umfassende Switches.- Hinweis: Werte dürfen sich im Laufe der Zeit nicht ändern (siehe vorherige Liste).
- Klar abgegrenzter und leicht auffindbarer Name
- Aktiviert die Überprüfung zur Kompilierzeit
- Beispiel: Eine
when
-Anweisung in Kotlin, die einen Wert zurückgibt
- Beispiel: Eine
- Eine funktionierende Klasse, die Schnittstellen implementieren, statische Hilfsprogramme haben, Member- oder Erweiterungsmethoden und Felder verfügbar machen kann.
Hierarchie der Android-Paketschichten einhalten
Die Paketstruktur von android.*
hat eine implizite Reihenfolge, in der Pakete auf niedrigerer Ebene nicht von Paketen auf höherer Ebene abhängen können.
Vermeiden Sie es, auf Google, andere Unternehmen und deren Produkte zu verweisen.
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.
Parcelable-Implementierungen sollten final sein
Von der Plattform definierte Parcelable-Klassen werden immer aus framework.jar
geladen. Daher ist es ungültig, 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 zum Entpacken. Hinweis zur Abwärtskompatibilität: Wenn Ihre Klasse in der Vergangenheit nicht final war, aber keinen öffentlich verfügbaren Konstruktor hatte, können Sie sie trotzdem mit final
kennzeichnen.
Methoden, die den Systemprozess aufrufen, sollten RemoteException als RuntimeException neu auslösen.
RemoteException
wird in der Regel von internem 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 ausgelöst 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();
}
Spezifische Ausnahmen für API-Änderungen auslösen
Das Verhalten öffentlicher APIs kann sich je nach API-Level ändern und zu App-Abstürzen führen (z. B. um neue Sicherheitsrichtlinien zu erzwingen).
Wenn die API für eine zuvor gültige Anfrage eine Ausnahme auslösen muss, lösen Sie eine neue spezifische Ausnahme anstelle einer generischen aus. Beispiel: ExportedFlagRequired
anstelle von SecurityException
(und ExportedFlagRequired
kann SecurityException
erweitern).
So können App-Entwickler und Tools Änderungen am API-Verhalten erkennen.
Kopierkonstruktor anstelle von „clone“ implementieren
Die Verwendung der Java-Methode clone()
wird dringend abgeraten, da die Klasse Object
keine API-Verträge bereitstellt und die Erweiterung von Klassen, die clone()
verwenden, schwierig ist. Verwenden Sie stattdessen einen Kopierkonstruktor, der ein Objekt desselben Typs akzeptiert.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
Bei Klassen, die zum Erstellen einen Builder verwenden, sollte ein Builder-Kopierkonstruktor hinzugefügt werden, 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
Das java.io.FileDescriptor
-Objekt hat eine schlechte Definition der Inhaberschaft, was zu schwer nachvollziehbaren Use-After-Close-Bugs führen kann. Stattdessen sollten APIs ParcelFileDescriptor
-Instanzen zurückgeben oder akzeptieren. Im Legacy-Code kann bei Bedarf mit dup() oder getFileDescriptor() zwischen PFD und FD konvertiert werden.
Ungerade numerische Werte vermeiden
Vermeiden Sie die direkte Verwendung von short
- oder byte
-Werten, da diese oft einschränken, wie Sie die API in Zukunft weiterentwickeln können.
BitSet nicht verwenden
java.util.BitSet
eignet sich hervorragend für die Implementierung, aber nicht für die öffentliche API. Sie ist veränderlich, erfordert eine Zuweisung für Methodenaufrufe mit hoher Häufigkeit und bietet keine semantische Bedeutung für die einzelnen Bits.
Verwenden Sie für leistungsstarke Szenarien eine int
- oder long
-Instanz mit @IntDef
. Bei Szenarien mit geringer Leistung sollten Sie eine Set<EnumType>
in Betracht ziehen. Verwenden Sie byte[]
für binäre Rohdaten.
android.net.Uri bevorzugen
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 darin fehlerhaft ist.
Mit @IntDef, @LongDef oder @StringDef gekennzeichnete Anmerkungen ausblenden
Mit @IntDef
, @LongDef
oder @StringDef
gekennzeichnete Anmerkungen geben eine Reihe gültiger Konstanten an, die an eine API übergeben werden können. Wenn sie jedoch als APIs exportiert werden, fügt der Compiler die Konstanten inline ein und nur die (jetzt nutzlosen) Werte bleiben im API-Stub der Annotation (für die Plattform) oder im JAR (für Bibliotheken) erhalten.
Daher muss die Verwendung dieser Annotationen in der Plattform mit der Dokumentationsannotation @hide
oder in Bibliotheken mit der Codeannotation @RestrictTo.Scope.LIBRARY)
gekennzeichnet werden. Sie müssen in beiden Fällen 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 des Plattform-SDK und der Bibliotheks-AARs werden die Annotationen 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 Einstellungsanbieter hinzufügen
Stellen Sie keine neuen Schlüssel aus Settings.Global
, Settings.System
oder Settings.Secure
bereit.
Fügen Sie stattdessen eine geeignete Getter- und Setter-Java-API in einer relevanten Klasse hinzu, in der Regel eine „Manager“-Klasse. Fügen Sie bei Bedarf einen Listener-Mechanismus oder einen Broadcast hinzu, um Clients über Änderungen zu informieren.
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 des
MODE_CHANGED_ACTION
-Broadcasts eingestellt. Das Team hat dadurch viel mehr Flexibilität 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 zusammengesetzt werden. Wenn Sie die Aktivität für Ihre Funktion verlängern, ist sie nicht mit anderen Funktionen kompatibel, für die Nutzer dasselbe tun müssen. Verlassen Sie sich stattdessen auf die Komposition mit Tools wie LifecycleObserver.
getUser() des Kontexts verwenden
Klassen, die an ein Context
gebunden sind, z. B. alles, was von Context.getSystemService()
zurückgegeben wird, sollten den Nutzer verwenden, der an das Context
gebunden ist, anstatt Mitglieder zu verwenden, 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 nicht einen einzelnen Nutzer darstellen, z. B. UserHandle.ALL
.
UserHandle anstelle von einfachen Ganzzahlen verwenden
UserHandle
wird bevorzugt, um Typsicherheit zu gewährleisten und zu vermeiden, dass Nutzer-IDs mit UIDs verwechselt werden.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Wenn es unvermeidlich ist, muss ein int
, das eine Nutzer-ID darstellt, mit @UserIdInt
annotiert werden.
Foobar getFoobarForUser(@UserIdInt int user);
Listener oder Callbacks anstelle von Broadcast-Intents verwenden
Broadcast-Intents sind sehr leistungsstark, haben aber zu emergenten Verhaltensweisen geführt, die sich negativ auf die Systemintegrität auswirken können. Daher sollten neue Broadcast-Intents nur mit Bedacht hinzugefügt werden.
Hier sind einige konkrete Bedenken, die uns davon abhalten, neue Broadcast-Intents einzuführen:
Wenn Sie Broadcasts ohne das Flag
FLAG_RECEIVER_REGISTERED_ONLY
senden, werden alle Apps, die noch nicht ausgeführt werden, zwangsweise gestartet. Das kann zwar manchmal beabsichtigt sein, kann aber auch dazu führen, dass Dutzende von Apps gleichzeitig gestartet werden, was sich negativ auf den Zustand des Systems auswirkt. Wir empfehlen, alternative Strategien wieJobScheduler
zu verwenden, um besser zu koordinieren, wann verschiedene Voraussetzungen erfüllt sind.Beim Senden von Broadcasts gibt es nur wenige Möglichkeiten, die an Apps gesendeten Inhalte zu filtern oder anzupassen. Das macht es schwierig oder unmöglich, auf zukünftige Datenschutzbedenken zu reagieren oder Verhaltensänderungen basierend auf dem Ziel-SDK der empfangenden App einzuführen.
Da Broadcast-Warteschlangen eine gemeinsam genutzte Ressource sind, können sie überlastet sein und Ihr Event wird möglicherweise nicht rechtzeitig übertragen. Wir haben mehrere Broadcast-Warteschlangen mit einer End-to-End-Latenz von 10 Minuten oder mehr beobachtet.
Aus diesen Gründen empfehlen wir, für neue Funktionen Listener, 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 den Broadcast auf Apps zu beschränken, die bereits ausgeführt werden.ACTION_SCREEN_ON
verwendet dieses Design beispielsweise, um zu vermeiden, dass Apps aktiviert werden. - Verwenden Sie nach Möglichkeit
Intent.setPackage()
oderIntent.setComponent()
, um die Übertragung auf eine bestimmte App auszurichten. InACTION_MEDIA_BUTTON
wird dieses Design beispielsweise verwendet, um den Fokus auf die aktuelle App zu legen, die die Wiedergabesteuerung übernimmt. - Definieren Sie Ihren Broadcast nach Möglichkeit als
<protected-broadcast>
, um zu verhindern, dass sich schädliche Apps als Betriebssystem ausgeben.
Intents in systemgebundenen Entwicklerdiensten
Dienste, die vom Entwickler erweitert werden sollen und an das System gebunden sind, z. B. abstrakte Dienste wie NotificationListenerService
, können auf eine Intent
-Aktion des Systems reagieren. Solche Dienste sollten die folgenden Kriterien erfüllen:
- Definieren Sie eine
SERVICE_INTERFACE
-Stringkonstante in der Klasse, die den vollständig qualifizierten Klassennamen des Dienstes enthält. Diese Konstante muss mit@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
annotiert werden. - Dokument zur Klasse, die ein Entwickler seinem
AndroidManifest.xml
hinzufügen muss, um Intents von der Plattform zu empfangen.<intent-filter>
- Sie sollten unbedingt eine Berechtigung auf Systemebene hinzufügen, um zu verhindern, dass schädliche Apps
Intent
an Entwicklerdienste senden.
Kotlin-Java-Interop
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 fun
s sind nicht für die Verwendung durch Java-Entwickler vorgesehen. Versuchen Sie jedoch nicht, die sprachspezifische Sichtbarkeit mit @JvmSynthetic
zu steuern, da dies Auswirkungen auf die Darstellung der API in Debuggern hat und das Debugging erschwert.
Weitere Informationen finden Sie im Leitfaden zur Kotlin-Java-Interoperabilität oder im Leitfaden zu asynchronen Vorgängen.
Companion-Objekte
In Kotlin werden statische Elemente mit companion object
verfügbar gemacht. In einigen Fällen werden diese in Java für eine innere Klasse mit dem Namen Companion
anstelle der enthaltenden Klasse angezeigt. Companion
-Klassen werden in API-Textdateien möglicherweise als leere Klassen angezeigt. Das ist so vorgesehen.
Um die Kompatibilität mit Java zu maximieren, annotieren Sie nicht konstante Felder von Companion-Objekten mit @JvmField
und öffentliche Funktionen mit @JvmStatic
, 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 von Android-Plattform-APIs
In diesem Abschnitt werden Richtlinien dazu 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 Codebasen zu maximieren.
Binärkompatibilität gefährdende Änderungen
Vermeiden Sie binärkompatible Änderungen an finalisierten öffentlichen APIs. Diese Arten von Änderungen führen in der Regel zu Fehlern beim Ausführen von make update-api
. Es kann jedoch Grenzfälle geben, die vom API-Check von Metalava nicht erkannt werden. Im Zweifelsfall finden Sie im Leitfaden Evolving Java-based APIs der Eclipse Foundation eine detaillierte Erklärung dazu, welche Arten von API-Änderungen in Java kompatibel sind. Binärinkompatible Änderungen an verborgenen (z. B. System-)APIs sollten dem Zyklus für die Einstellung/Ersetzung folgen.
Nicht abwärtskompatible Änderungen an der Quelle
Wir raten von Änderungen ab, die die Quelle betreffen, auch wenn sie nicht binär sind. Ein Beispiel für eine binärkompatible, aber quellcodeinkompatible Änderung ist das Hinzufügen eines generischen Typs zu einer vorhandenen Klasse. Dies ist binärkompatibel, kann aber aufgrund von Vererbung oder mehrdeutigen Verweisen zu Kompilierungsfehlern führen.
Bei quellbezogenen Änderungen werden beim Ausführen von make update-api
keine Fehler ausgegeben. Sie müssen sich daher selbst über die Auswirkungen von Änderungen an vorhandenen API-Signaturen informieren.
In einigen Fällen sind quellcodebezogene Änderungen erforderlich, um die Entwicklerfreundlichkeit oder die Richtigkeit des Codes zu verbessern. Wenn Sie beispielsweise Anmerkungen zur Null-Zulässigkeit zu Java-Quellen hinzufügen, wird die Interoperabilität mit Kotlin-Code verbessert und die Wahrscheinlichkeit von Fehlern verringert. Dies erfordert jedoch häufig Änderungen am Quellcode, manchmal sogar erhebliche Änderungen.
Änderungen an privaten APIs
Sie können APIs, die mit @TestApi
gekennzeichnet sind, jederzeit ändern.
APIs, die mit @SystemApi
annotiert sind, müssen drei Jahre lang aufbewahrt werden. Sie müssen eine System-API gemäß dem folgenden Zeitplan entfernen oder umgestalten:
- API y – hinzugefügt
- API y+1 – Einstellung
- Markieren Sie den Code mit
@Deprecated
. - Fügen Sie Ersatz hinzu und verlinken Sie in der Javadoc für den eingestellten Code mit der
@deprecated
-Dokumentationsanmerkung auf den Ersatz. - Melden Sie während des Entwicklungszyklus Fehler an interne Nutzer und informieren Sie sie darüber, dass die API eingestellt wird. So lässt sich prüfen, ob die Ersatz-APIs angemessen sind.
- Markieren Sie den Code mit
- API y+2 – Vorläufige Entfernung
- Markieren Sie den Code mit
@removed
. - Optional: Ausnahme auslösen oder keine Aktion für Apps ausführen, die auf die aktuelle SDK-Ebene für das Release ausgerichtet sind.
- Markieren Sie den Code mit
- API y+3 – Endgültige Entfernung
- Entfernen Sie den Code vollständig aus dem Quellbaum.
Einstellung
Die Einstellung einer API betrachten wir als API-Änderung. Sie kann in einer Hauptversion (z. B. mit einem Buchstaben) erfolgen. Verwenden Sie die Quellannotation @Deprecated
und die Dokumentationsannotation @deprecated
<summary>
zusammen, wenn Sie APIs einstellen. Ihre Zusammenfassung muss eine Migrationsstrategie enthalten. Diese Strategie kann auf eine Ersatz-API verweisen 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 APIs einstellen, die in XML definiert und in Java verfügbar gemacht werden, einschließlich Attributen und formatierbaren Eigenschaften, die in der Klasse android.R
verfügbar gemacht werden, mit einer Zusammenfassung:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Wann sollte eine API eingestellt werden?
Die Einstellung ist am nützlichsten, um die Einführung einer API in neuem Code zu verhindern.
Außerdem müssen Sie APIs als @deprecated
kennzeichnen, bevor sie @removed
werden. Dies bietet jedoch keine starke Motivation für Entwickler, von einer API zu migrieren, die sie bereits verwenden.
Bevor Sie eine API einstellen, sollten Sie die Auswirkungen auf Entwickler berücksichtigen. Die Auswirkungen der Einstellung einer API sind unter anderem:
javac
gibt während der Kompilierung eine Warnung aus.- Hinweise 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 die Version des Compile SDK aktualisieren können. - Warnungen zur Einstellung beim Importieren eingestellter Klassen können nicht unterdrückt werden. Daher müssen Entwickler den vollständig qualifizierten Klassennamen für jede Verwendung einer verworfenen Klasse inline einfügen, bevor sie ihre Compile-SDK-Version aktualisieren können.
- Hinweise zur Einstellung können nicht global unterdrückt oder als Baseline festgelegt werden. Entwickler, die
- Die Dokumentation zu
d.android.com
enthält einen Hinweis zur Einstellung. - In IDEs wie Android Studio wird eine Warnung an der Stelle angezeigt, an der die API verwendet wird.
- IDEs können die API in der automatischen Vervollständigung herabstufen oder ausblenden.
Wenn eine API eingestellt wird, kann das dazu führen, dass Entwickler, die sich am meisten um die Codequalität kümmern (diejenigen, die -Werror
verwenden), keine neuen SDKs einführen.
Entwickler, die sich nicht um Warnungen in ihrem vorhandenen Code kümmern, werden wahrscheinlich auch keine Änderungen vornehmen, wenn Funktionen eingestellt werden.
Ein SDK, das eine große Anzahl von Einstellungen als veraltet kennzeichnet, verschlimmert beide Fälle.
Aus diesem Grund empfehlen wir, APIs nur in den folgenden Fällen einzustellen:
- Wir planen, die API in einer zukünftigen Version zu
@remove
. - Die API-Nutzung führt zu einem falschen oder nicht definierten Verhalten, das wir nicht beheben können, ohne die Kompatibilität zu beeinträchtigen.
Wenn Sie eine API einstellen und durch eine neue API ersetzen, empfehlen wir 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 raten davon ab, 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) {
...
}
Die Einstellung ist in Fällen angebracht, in denen APIs ihr dokumentiertes Verhalten nicht mehr beibehalten können:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Änderungen an verworfenen APIs
Sie müssen das Verhalten eingestellter APIs beibehalten. Das bedeutet, dass Testimplementierungen gleich bleiben müssen und Tests auch nach der Einstellung der API weiterhin erfolgreich sein müssen. Wenn für die API keine Tests vorhanden sind, sollten Sie Tests hinzufügen.
Erweitern Sie eingestellte API-Oberflächen in zukünftigen Releases nicht. Sie können einer vorhandenen eingestellten API Lint-Korrektur-Annotationen (z. B. @Nullable
) hinzufügen, aber keine neuen APIs.
Fügen Sie neue APIs nicht als eingestellt hinzu. Wenn APIs in einem Vorabveröffentlichungszyklus hinzugefügt und anschließend eingestellt wurden (sie würden also zunächst als eingestellt in die öffentliche API aufgenommen), müssen Sie sie entfernen, bevor Sie die API fertigstellen.
Vorläufiges Entfernen
Das vorläufige Entfernen ist eine quellbezogene Änderung, die Sie in öffentlichen APIs vermeiden sollten, es sei denn, der API Council genehmigt sie ausdrücklich.
Bei System-APIs müssen Sie die API für die Dauer einer Hauptversion einstellen, bevor Sie sie entfernen. Entfernen Sie alle Dokumentverweise auf die APIs und verwenden Sie die Dokumentanmerkung @removed <summary>
, wenn Sie APIs vorläufig entfernen. Ihre Zusammenfassung muss den Grund für die Entfernung enthalten und kann eine Migrationsstrategie enthalten, wie in Einstellung beschrieben.
Das Verhalten von APIs, die vorläufig entfernt wurden, kann beibehalten werden. Wichtiger ist jedoch, dass es beibehalten werden muss, damit vorhandene Aufrufer beim Aufrufen 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 an Verhaltensänderungen angepasst werden. Tests müssen weiterhin bestätigen, dass vorhandene Aufrufer zur Laufzeit nicht abstürzen. Sie können das Verhalten von APIs, die vorläufig entfernt wurden, beibehalten. Wichtiger ist jedoch, dass Sie es so beibehalten, dass vorhandene Aufrufer nicht abstürzen, wenn sie die API aufrufen. 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 an Verhaltensänderungen angepasst werden. Tests müssen weiterhin bestätigen, dass vorhandene Aufrufer zur Laufzeit nicht abstürzen.
Auf technischer Ebene entfernen wir die API aus dem SDK-Stub-JAR und dem Kompilierzeit-Classpath mit der Javadoc-Annotation @remove
. 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 = ...
Aus Sicht eines App-Entwicklers wird die API nicht mehr in der automatischen Vervollständigung angezeigt und Quellcode, der auf die API verweist, wird nicht kompiliert, wenn compileSdk
gleich oder höher als das SDK ist, in dem die API entfernt wurde. Quellcode wird jedoch weiterhin erfolgreich für frühere 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 vorläufig entfernt werden.
Abstrakte Methoden
Sie dürfen abstrakte Methoden in Klassen, die von Entwicklern erweitert werden können, nicht vorläufig entfernen. Dadurch ist es für Entwickler unmöglich, die Klasse über alle SDK-Ebenen hinweg zu erweitern.
In seltenen Fällen, in denen es nie möglich war und nicht möglich sein wird, dass Entwickler eine Klasse erweitern, können Sie abstrakte Methoden trotzdem vorläufig entfernen.
Endgültiges Entfernen
Das endgültige Entfernen ist eine binäre Änderung und sollte niemals in öffentlichen APIs erfolgen.
Unerwünschte Anmerkung
Wir verwenden die Anmerkung @Discouraged
, um anzugeben, dass wir eine API in den meisten (>95%) Fällen nicht empfehlen. Abgeratene APIs unterscheiden sich von verworfenen APIs dadurch, dass es einen eingeschränkten kritischen Anwendungsfall gibt, der die Einstellung verhindert. Wenn Sie eine API als nicht empfehlenswert 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, da dies nicht empfohlen wird.
Ä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 klar zu kommunizieren, wenn Entwickler versucht haben, Ereignisse zu posten, die zu groß waren, um über Binder
gesendet zu werden.
Um Probleme für bestehende Apps zu vermeiden, empfehlen wir jedoch dringend, das sichere Verhalten für ältere Apps beizubehalten. Bisher haben wir diese Verhaltensänderungen anhand der ApplicationInfo.targetSdkVersion
der App geschützt. Vor Kurzem haben wir jedoch die Verwendung des App Compatibility Framework eingeführt. Hier ist ein Beispiel für die Implementierung einer Verhaltensänderung mit diesem neuen Framework:
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 Design des App Compatibility Framework können Entwickler bestimmte Verhaltensänderungen während der Preview- und Beta-Releases vorübergehend deaktivieren, um ihre Apps zu debuggen. So müssen sie nicht Dutzende von Verhaltensänderungen gleichzeitig anpassen.
Vorwärtskompatibilität
Vorwärtskompatibilität ist eine Designeigenschaft, die es einem System ermöglicht, Eingaben zu akzeptieren, die für eine spätere Version des Systems vorgesehen sind. Beim API-Design müssen Sie sowohl auf das ursprüngliche Design als auch auf zukünftige Änderungen achten, da Entwickler erwarten, dass sie Code einmal schreiben, einmal testen und er dann überall problemlos ausgeführt wird.
Die folgenden Ursachen sind für die häufigsten Probleme mit der Vorwärtskompatibilität in Android verantwortlich:
- Hinzufügen neuer Konstanten zu einer Menge (z. B.
@IntDef
oderenum
), die zuvor als vollständig angenommen wurde (z. B. wennswitch
eindefault
hat, das eine Ausnahme auslöst). - Unterstützung für ein Feature, das nicht direkt in der API-Oberfläche erfasst wird (z. B. Unterstützung für die Zuweisung von Ressourcen des Typs
ColorStateList
in XML, wo zuvor nur<color>
-Ressourcen unterstützt wurden). - Lockerung der Einschränkungen für Laufzeitprüfungen, z. B. Entfernen 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 erfahren es möglicherweise erst durch Absturzberichte von älteren Geräten.
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 keine dieser Probleme.
Daher müssen API-Designer beim Ändern vorhandener Klassen besonders vorsichtig sein. Fragen Sie sich: „Wird diese Änderung dazu führen, dass Code, der nur für die neueste Version der Plattform geschrieben und getestet wurde, in niedrigeren Versionen fehlschlägt?“
XML-Schemas
Wenn ein XML-Schema als stabile Schnittstelle zwischen Komponenten dient, muss es explizit angegeben werden und sich abwärtskompatibel entwickeln, ähnlich wie andere Android-APIs. Beispielsweise muss die Struktur von XML-Elementen und ‑Attributen beibehalten werden, ähnlich wie Methoden und Variablen auf anderen Android-API-Oberflächen.
Einstellung von XML
Wenn Sie ein XML-Element oder -Attribut einstellen möchten, können Sie das xs:annotation
-Markierung hinzufügen. Sie müssen jedoch alle vorhandenen XML-Dateien weiterhin unterstützen, indem Sie den typischen @SystemApi
-Entwicklungszyklus befolgen.
<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 das sequence
-Element, das choice
-Element und die all
-Elemente als untergeordnete Elemente des complexType
-Elements. Diese untergeordneten Elemente unterscheiden sich jedoch in der Anzahl und Reihenfolge ihrer untergeordneten Elemente. Das Ändern eines vorhandenen Typs wäre also eine inkompatible Änderung.
Wenn Sie einen vorhandenen Typ ändern möchten, empfiehlt es sich, den alten Typ einzustellen und einen neuen Typ einzuführen, der ihn ersetzt.
<!-- 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 sich Subsysteme („Mainline-Module“) des Android-Betriebssystems einzeln aktualisieren lassen, anstatt das gesamte Systemimage zu aktualisieren.
Mainline-Module müssen von der Kernplattform „entkoppelt“ werden. Das bedeutet, dass alle Interaktionen zwischen den einzelnen Modulen und der Außenwelt über formale (öffentliche oder System-)APIs erfolgen müssen.
Mainline-Module sollten bestimmten Designmustern folgen. In diesem Abschnitt werden sie beschrieben.
Das <Module>FrameworkInitializer-Muster
Wenn ein Mainline-Modul @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 in$BOOTCLASSPATH
enthalten sein. Beispiel: StatsFrameworkInitializerMarkieren Sie sie mit
@SystemApi(client = MODULE_LIBRARIES)
.Fügen Sie eine
public static void registerServiceWrappers()
-Methode hinzu.Verwenden Sie
SystemServiceRegistry.registerContextAwareService()
, um eine Dienstmanagerklasse zu registrieren, wenn sie einen Verweis auf einContext
benötigt.Verwenden Sie
SystemServiceRegistry.registerStaticService()
, um eine Dienstmanagerklasse zu registrieren, wenn sie keine Referenz zu einemContext
benötigt.Rufen Sie die Methode
registerServiceWrappers()
über den statischen Initialisierer vonSystemServiceRegistry
auf.
Das <Module>ServiceManager-Muster
Normalerweise würde man ServiceManager
verwenden, um Binder-Objekte für Systemdienste zu registrieren oder Referenzen darauf zu erhalten. 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 von anderen Modulen bereitgestellt werden.
Hauptmodule können stattdessen das folgende Muster verwenden, um Binder-Dienste zu registrieren und Referenzen darauf zu erhalten, die im Modul implementiert sind.
Erstellen Sie eine
<YourModule>ServiceManager
-Klasse, die dem Design von TelephonyServiceManager entspricht.Stellen Sie die Klasse als
@SystemApi
bereit. Wenn Sie nur über$BOOTCLASSPATH
-Klassen oder Systemserverklassen darauf zugreifen müssen, können Sie@SystemApi(client = MODULE_LIBRARIES)
verwenden. Andernfalls ist@SystemApi(client = PRIVILEGED_APPS)
geeignet.Dieser Kurs umfasst:
- Ein verborgener Konstruktor, sodass nur der statische Plattformcode ihn instanziieren kann.
- Öffentliche Getter-Methoden, die eine
ServiceRegisterer
-Instanz für einen bestimmten Namen zurückgeben. Wenn Sie ein Binder-Objekt haben, benötigen Sie eine Getter-Methode. Wenn Sie zwei haben, benötigen Sie zwei Getter. - Instanziieren Sie diese Klasse in
ActivityThread.initializeMainlineModules()
und übergeben Sie sie an eine statische Methode, die von Ihrem Modul bereitgestellt wird. Normalerweise fügen Sie in IhrerFrameworkInitializer
-Klasse eine statische@SystemApi(client = MODULE_LIBRARIES)
-API hinzu, die sie übernimmt.
Dieses Muster würde verhindern, dass andere Mainline-Module auf diese APIs zugreifen, da es für andere Module keine Möglichkeit gibt, eine Instanz von <YourModule>ServiceManager
zu erhalten, obwohl die APIs get()
und register()
für sie sichtbar sind.
So erhält die Telefonie einen Verweis auf den Telefoniedienst: Link zur Codesuche.
Wenn Sie ein Dienstbinderobjekt im nativen Code implementieren, verwenden Sie die nativen APIs von AServiceManager
.
Diese APIs entsprechen den ServiceManager
-Java-APIs, die nativen APIs werden jedoch direkt für Mainline-Module bereitgestellt. Verwenden Sie sie nicht, um Binder-Objekte zu registrieren oder darauf zu verweisen, die nicht zu Ihrem Modul gehören. Wenn Sie ein Binder-Objekt aus dem nativen Code verfügbar machen, benötigt Ihr <YourModule>ServiceManager.ServiceRegisterer
keine register()
-Methode.
Berechtigungsdefinitionen in Mainline-Modulen
In Mainline-Modulen mit APKs können (benutzerdefinierte) Berechtigungen in der APK-Datei AndroidManifest.xml
auf dieselbe Weise wie in einem regulären APK definiert werden.
Wenn die definierte Berechtigung nur intern in einem Modul verwendet wird, sollte ihrem Berechtigungsnamen der APK-Paketname 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 jede statische Plattformberechtigung) plus dem Paketnamen des Moduls, um zu signalisieren, dass es sich um eine Plattform-API aus einem Modul handelt, und um 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
.